From 59e87701eb44099811640c041a267cb52ebb82c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 4 Mar 2024 09:38:59 -0500 Subject: [PATCH 1/4] minor code change --- packages/block-editor/src/hooks/use-bindings-attributes.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 5cd8cb46b3b7e7..13fbc0517d0ce3 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -75,10 +75,8 @@ const BindingConnector = ( { source, onPropValueChange, } ) => { - const { placeholder, value: propValue } = source.useSource( - blockProps, - args - ); + const { useSource } = source; + const { placeholder, value: propValue } = useSource( blockProps, args ); const { name: blockName } = blockProps; const attrValue = blockProps.attributes[ attrName ]; From 22e6c47c394d07242effc5b24ebd2a343a3a5e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 4 Mar 2024 10:22:53 -0500 Subject: [PATCH 2/4] move logic to check type to local state --- .../src/hooks/use-bindings-attributes.js | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 13fbc0517d0ce3..81f91dbafdfd4b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -83,27 +83,6 @@ const BindingConnector = ( { const updateBoundAttibute = useCallback( ( newAttrValue, prevAttrValue ) => { - /* - * If the attribute is a RichTextData instance, - * (core/paragraph, core/heading, core/button, etc.) - * compare its HTML representation with the new value. - * - * To do: it looks like a workaround. - * Consider improving the attribute and metadata fields types. - */ - if ( prevAttrValue instanceof RichTextData ) { - // Bail early if the Rich Text value is the same. - if ( prevAttrValue.toHTMLString() === newAttrValue ) { - return; - } - - /* - * To preserve the value type, - * convert the new value to a RichTextData instance. - */ - newAttrValue = RichTextData.fromHTMLString( newAttrValue ); - } - if ( prevAttrValue === newAttrValue ) { return; } @@ -195,15 +174,40 @@ const withBlockBindingSupport = createHigherOrderComponent( /* * Collect and update the bound attributes * in a separate state. + * Also, it checks the attribute type. */ const [ boundAttributes, setBoundAttributes ] = useState( {} ); const updateBoundAttributes = useCallback( - ( newAttributes ) => - setBoundAttributes( ( prev ) => ( { + ( newAttributes ) => { + const nextAttributes = Object.fromEntries( + Object.entries( newAttributes ).map( + ( [ attrName, attrValue ] ) => { + /* + * If the original attribute is a RichTextData instance, + * (core/paragraph, core/heading, core/button, etc.), + * convert the new value to a RichTextData instance, too. + * + * To do: it looks like a workaround. + * Consider improving the attribute and metadata fields types. + */ + const originalAttr = props.attributes[ attrName ]; + if ( originalAttr instanceof RichTextData ) { + return [ + attrName, + RichTextData.fromHTMLString( attrValue ), + ]; + } + return [ attrName, attrValue ]; + } + ) + ); + + return setBoundAttributes( ( prev ) => ( { ...prev, - ...newAttributes, - } ) ), - [] + ...nextAttributes, + } ) ); + }, + [ props.attributes ] ); /* From 8df79a96ed5b9a035849e3cbf94033a2bb089394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 4 Mar 2024 11:11:03 -0500 Subject: [PATCH 3/4] enable post meta editing --- packages/editor/src/bindings/post-meta.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 0d0c737d0eaf77..c2ecdbbd0b3103 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -41,4 +41,5 @@ export default { updateValue: updateMetaValue, }; }, + lockAttributesEditing: false, }; From 97d72463cbff209df3fb24c843eebe4c3fdb544b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 4 Mar 2024 11:11:21 -0500 Subject: [PATCH 4/4] introduce a second state to store incoming changes --- .../src/hooks/use-bindings-attributes.js | 95 +++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 81f91dbafdfd4b..268e2d92cea5e8 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -66,17 +66,23 @@ export function canBindAttribute( blockName, attributeName ) { * @param {Object} props.source - Source handler. * @param {Object} props.args - The arguments to pass to the source. * @param {Function} props.onPropValueChange - The function to call when the attribute value changes. + * @param {string} props.incomingAttrValue - The incoming attribute value. * @return {null} Data-handling component. Render nothing. */ const BindingConnector = ( { args, attrName, + incomingAttrValue, blockProps, source, onPropValueChange, } ) => { const { useSource } = source; - const { placeholder, value: propValue } = useSource( blockProps, args ); + const { + placeholder, + value: propValue, + updateValue: updatePropValue, + } = useSource( blockProps, args ); const { name: blockName } = blockProps; const attrValue = blockProps.attributes[ attrName ]; @@ -92,6 +98,11 @@ const BindingConnector = ( { [ attrName, onPropValueChange ] ); + /* + * Source prop => Block Attribute + * Sync from source to the block attribute. + * It also handles the placeholder fallback. + */ useLayoutEffect( () => { if ( typeof propValue !== 'undefined' ) { updateBoundAttibute( propValue, attrValue ); @@ -122,6 +133,22 @@ const BindingConnector = ( { attrName, ] ); + /* + * Block Attribute => Source prop + * Sync from incomming attribute value to the source. + */ + useLayoutEffect( () => { + if ( incomingAttrValue === undefined ) { + return; + } + + if ( incomingAttrValue === propValue ) { + return; + } + + updatePropValue( incomingAttrValue ); + }, [ incomingAttrValue, propValue, updatePropValue ] ); + return null; }; @@ -131,13 +158,19 @@ const BindingConnector = ( { * to the source handlers. * For this, it creates a BindingConnector for each bound attribute. * - * @param {Object} props - The component props. - * @param {Object} props.blockProps - The BlockEdit props object. - * @param {Object} props.bindings - The block bindings settings. - * @param {Function} props.onPropValueChange - The function to call when the attribute value changes. + * @param {Object} props - The component props. + * @param {Object} props.blockProps - The BlockEdit props object. + * @param {Object} props.bindings - The block bindings settings. + * @param {Function} props.onPropValueChange - The function to call when the attribute value changes. + * @param {Object} props.incomingBoundAttributes - The incoming bound attributes. * @return {null} Data-handling component. Render nothing. */ -function BlockBindingBridge( { blockProps, bindings, onPropValueChange } ) { +function BlockBindingBridge( { + blockProps, + bindings, + onPropValueChange, + incomingBoundAttributes, +} ) { const blockBindingsSources = unlock( useSelect( blocksStore ) ).getAllBlockBindingsSources(); @@ -157,6 +190,9 @@ function BlockBindingBridge( { blockProps, bindings, onPropValueChange } ) { { + // Collect the unbound and bound attributes. + const unboundAttributes = {}; + const nextBoundAttributes = {}; + Object.entries( nextAttributes ).reduce( + ( acc, [ key, value ] ) => { + if ( ! ( key in bindings ) ) { + acc.unbound[ key ] = value; + } else { + acc.bound[ key ] = value; + } + return acc; + }, + { unbound: unboundAttributes, bound: nextBoundAttributes } + ); + + // Update the unbound attributes only if there are any. + if ( Object.keys( unboundAttributes ).length ) { + setAttributes( unboundAttributes ); + } + + // Update the bound attributes. + setIncomingBoundAttributes( ( prev ) => ( { + ...prev, + ...nextBoundAttributes, + } ) ); + }, + [ bindings, setAttributes ] + ); + return ( <> { Object.keys( bindings ).length > 0 && ( @@ -227,12 +308,14 @@ const withBlockBindingSupport = createHigherOrderComponent( blockProps={ props } bindings={ bindings } onPropValueChange={ updateBoundAttributes } + incomingBoundAttributes={ incomingBoundAttributes } /> ) } );