-
Notifications
You must be signed in to change notification settings - Fork 11
Support BlockContext between different blocks #7
Changes from 7 commits
2e4bf34
d4242ab
cb839e1
3dddb88
809e690
59b6cce
fb4b06b
7d2ecf6
bed7517
f71d477
039e1f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"$schema": "https://schemas.wp.org/trunk/block.json", | ||
"apiVersion": 2, | ||
"name": "luisherranz/block-hydration-experiments-child", | ||
"version": "0.1.0", | ||
"title": "BHE - Child", | ||
"category": "text", | ||
"icon": "flag", | ||
"description": "", | ||
"usesContext": ["message"], | ||
"supports": { | ||
"color": { | ||
"text": true | ||
}, | ||
"html": true | ||
}, | ||
"textdomain": "block-hydration-experiments-child", | ||
"editorScript": "file:./index.js", | ||
"style": "file:./style-index.css", | ||
"viewScript": "file:./view.js" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// This import is needed to ensure that the `wp.blockEditor` global is available | ||
// by the time this component gets loaded. The `Title` component consumes the | ||
// global but cannot import it because it shouldn't be loaded on the frontend of | ||
// the site. | ||
import '@wordpress/block-editor'; | ||
import { useBlockProps } from '@wordpress/block-editor'; | ||
|
||
const Text = ( { context } ) => { | ||
const blockProps = useBlockProps(); | ||
|
||
return ( | ||
<div {...blockProps}> | ||
<p>Child element</p> | ||
{context?.message} | ||
</div> | ||
); | ||
}; | ||
|
||
export default Text; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Text from '../../frontend/text'; | ||
import { registerBlockType } from '../../gutenberg-packages/wordpress-blocks'; | ||
import Edit from './edit'; | ||
import './style.scss'; | ||
|
||
registerBlockType( 'luisherranz/block-hydration-experiments-child', { | ||
edit: Edit, | ||
// The Save component is derived from the Frontend component. | ||
frontend: Text, | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import Text from '../../frontend/text'; | ||
import { registerBlockType } from '../../gutenberg-packages/frontend'; | ||
|
||
registerBlockType( 'luisherranz/block-hydration-experiments-child', Text ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import Frontend from '../../frontend'; | ||
import { registerBlockType } from '../../gutenberg-packages/wordpress-blocks'; | ||
import Edit from './edit'; | ||
import './style.scss'; | ||
|
||
registerBlockType( 'luisherranz/block-hydration-experiments-parent', { | ||
edit: Edit, | ||
// The Save component is derived from the Frontend component. | ||
frontend: Frontend, | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.wp-block-luisherranz-block-hydration-experiments-parent { | ||
padding: 15px 10px 15px 50px; | ||
background-color: rgb(238, 237, 237); | ||
} | ||
|
||
gutenberg-block, | ||
gutenberg-inner-blocks { | ||
display: contents; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import Frontend from '../../frontend'; | ||
import { registerBlockType } from '../../gutenberg-packages/frontend'; | ||
|
||
registerBlockType( 'luisherranz/block-hydration-experiments-parent', Frontend ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const Text = ( { blockProps, context } ) => { | ||
return ( | ||
<div {...blockProps}> | ||
<p>Child element</p> | ||
{context?.message} | ||
</div> | ||
); | ||
}; | ||
|
||
export default Text; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,37 @@ | ||
import { pickKeys } from './utils'; | ||
import { EnvContext, hydrate } from './wordpress-element'; | ||
|
||
const blockTypes = new Map(); | ||
// We assign `blockTypes` to window to make sure it's a global singleton. | ||
// | ||
// Have to do this because of the way we are currently bundling the code | ||
// in this repo, each block gets its own copy of this file. | ||
// | ||
// We COULD fix this by doing some webpack magic to spit out the code in | ||
// `gutenberg-packages` to a shared chunk but assigning `blockTypes` to window | ||
// is a cheap hack for now that will be fixed once we can merge this code into Gutenberg. | ||
Comment on lines
+4
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the comment I explain why I'm doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, this should be part of the Gutenberg APIs, so we can keep the hack for now 🙂 |
||
if ( typeof window.blockTypes === 'undefined' ) { | ||
window.blockTypes = new Map(); | ||
} | ||
|
||
export const registerBlockType = ( name, Comp ) => { | ||
blockTypes.set( name, Comp ); | ||
window.blockTypes.set( name, Comp ); | ||
}; | ||
|
||
const Children = ( { value } ) => { | ||
const Children = ( { value, providedContext } ) => { | ||
if ( !value ) { | ||
return null; | ||
} | ||
return ( | ||
<gutenberg-inner-blocks | ||
ref={( el ) => { | ||
if ( el !== null ) { | ||
// listen for the ping from the child | ||
el.addEventListener( 'gutenberg-context', ( event ) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not saying this is right or wrong, but what's your reasoning for adding the event listener to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this is the only option we have if I'm not mistaken. Lemme explain in a video: 2022-05-23_20-24-04.mp4There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, thanks for the video, Michal 🙂 I think those problems can be circumvented, but we shouldn't worry for now, this is perfectly fine as it is. If there's a simpler solution, it will present itself once we do more complex stuff! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for completeness, I also thought we could wrap the Comp like in a "useless" div like: <div
style={{ display: 'contents' }}
ref={el => { /* add the eventListener here */ } }
>
<Comp
attributes={attributes}
blockProps={blockProps}
suppressHydrationWarning={true}
context={context}
>
<Children
value={innerBlocks && innerBlocks.innerHTML}
suppressHydrationWarning={true}
/>
</Comp>
</div>
but that doesn't work well - it was e.g. breaking some styles. And that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the benefit of using an extra
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There would be no benefit, that's what I was trying to say. But the issue is that you only want to ask the parent that is one level up for the context: I've realized that we could also do this: // inside of the connectedCallback()
this.addEventListener( 'gutenberg-context', ( event ) => {
if ( this !== event.target ) { // <- only set the context if we're in the parent.
event.stopPropagation();
event.detail.context = providedContext;
}
} );
// ping the parent for the context
const event = new CustomEvent( 'gutenberg-context', {
detail: {},
bubbles: true,
cancelable: true,
} );
this.dispatchEvent( event ); So, this way we can both listen to and fire the event on the same element (the Was this what you were suggesting or did you have another idea in mind? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think so 🙂 I think you could also add the event listener after you've dispatched the event so they don't interfere. |
||
event.stopPropagation(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason to stop the propagation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes! I explain below in the video 🙂 |
||
event.detail.context = providedContext; | ||
} ); | ||
} | ||
}} | ||
suppressHydrationWarning={true} | ||
dangerouslySetInnerHTML={{ __html: value }} | ||
/> | ||
|
@@ -22,17 +42,42 @@ Children.shouldComponentUpdate = () => false; | |
class GutenbergBlock extends HTMLElement { | ||
connectedCallback() { | ||
setTimeout( () => { | ||
const blockType = this.getAttribute( 'data-gutenberg-block-type' ); | ||
// ping the parent for the context | ||
const event = new CustomEvent( 'gutenberg-context', { | ||
detail: {}, | ||
bubbles: true, | ||
cancelable: true, | ||
} ); | ||
this.dispatchEvent( event ); | ||
|
||
const usesContext = JSON.parse( | ||
this.getAttribute( 'data-gutenberg-context-used' ), | ||
); | ||
const providesContext = JSON.parse( | ||
this.getAttribute( 'data-gutenberg-context-provided' ), | ||
); | ||
const attributes = JSON.parse( | ||
this.getAttribute( 'data-gutenberg-attributes' ), | ||
); | ||
|
||
// pass the context to children if needed | ||
const providedContext = pickKeys( | ||
attributes, | ||
Object.keys( providesContext ), | ||
); | ||
|
||
// select only the parts of the context that the block declared in | ||
// the `usesContext` of its block.json | ||
const context = pickKeys( event.detail.context, usesContext ); | ||
|
||
const blockType = this.getAttribute( 'data-gutenberg-block-type' ); | ||
const blockProps = JSON.parse( | ||
this.getAttribute( 'data-gutenberg-block-props' ), | ||
); | ||
const innerBlocks = this.querySelector( | ||
'template.gutenberg-inner-blocks', | ||
); | ||
const Comp = blockTypes.get( blockType ); | ||
const Comp = window.blockTypes.get( blockType ); | ||
const technique = this.getAttribute( 'data-gutenberg-hydrate' ); | ||
const media = this.getAttribute( 'data-gutenberg-media' ); | ||
const hydrationOptions = { technique, media }; | ||
|
@@ -42,10 +87,12 @@ class GutenbergBlock extends HTMLElement { | |
attributes={attributes} | ||
blockProps={blockProps} | ||
suppressHydrationWarning={true} | ||
context={context} | ||
> | ||
<Children | ||
value={innerBlocks && innerBlocks.innerHTML} | ||
suppressHydrationWarning={true} | ||
providedContext={providedContext} | ||
/> | ||
</Comp> | ||
<template | ||
|
@@ -60,4 +107,11 @@ class GutenbergBlock extends HTMLElement { | |
} | ||
} | ||
|
||
customElements.define( 'gutenberg-interactive-block', GutenbergBlock ); | ||
// We need to wrap the element registration code in a conditional for the same | ||
// reason we assing `blockTypes` to window (see top of the file). | ||
// | ||
// We need to ensure that the component registration code is only run once | ||
// because it throws if you try to register an element with the same name twice. | ||
if ( customElements.get( 'gutenberg-interactive-block' ) === undefined ) { | ||
customElements.define( 'gutenberg-interactive-block', GutenbergBlock ); | ||
} | ||
Comment on lines
-63
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Directing your attention to the comment 🙂 |
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this a little bit? This block doesn't use the
Title
component, does it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it seems that it does work fine even if I remove the
import '@wordpress/block-editor';
which makes sense.I have added it because it seemed make the warning about
wp.RichText
go away, but looks like I don't need it after all.