From 37a7f5a23e73d258ff294b69115c39e3b251fca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 08:58:08 -0300 Subject: [PATCH 01/79] replace use-binding-attributes with block-binding-support --- .../components/block-binding-support/index.js | 49 +++++++ .../with-block-binding-support.js | 128 ++++++++++++++++++ packages/block-editor/src/components/index.js | 2 + 3 files changed, 179 insertions(+) create mode 100644 packages/block-editor/src/components/block-binding-support/index.js create mode 100644 packages/block-editor/src/components/block-binding-support/with-block-binding-support.js diff --git a/packages/block-editor/src/components/block-binding-support/index.js b/packages/block-editor/src/components/block-binding-support/index.js new file mode 100644 index 00000000000000..39cfcd2cf7a088 --- /dev/null +++ b/packages/block-editor/src/components/block-binding-support/index.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import withBlockBindingSupport from './with-block-binding-support'; + +export const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'url', 'title', 'alt' ], + 'core/button': [ 'url', 'text', 'linkTarget' ], +}; + +export function isItPossibleToBindBlock( blockName ) { + return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; +} + +export default function extendBlockWithBoundAttributes( settings, name ) { + if ( ! isItPossibleToBindBlock( name ) ) { + return settings; + } + + return { + ...settings, + /* + * Expose relevant data through + * the block context. + */ + usesContext: [ + ...new Set( [ + ...( settings.usesContext || [] ), + 'postId', + 'postType', + 'queryId', + ] ), + ], + edit: withBlockBindingSupport( settings.edit ), + }; +} + +addFilter( + 'blocks.registerBlockType', + 'block-edit-with-binding-attributes', + extendBlockWithBoundAttributes +); diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js new file mode 100644 index 00000000000000..040f2a49ab1ef0 --- /dev/null +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -0,0 +1,128 @@ +/** + * External dependencies + */ +/** + * WordPress dependencies + */ +import { createHigherOrderComponent } from '@wordpress/compose'; +import { useEffect, useCallback, useRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import { useSelect } from '@wordpress/data'; + +/** + * Conponent to bind an attribute to a prop. + * + * @param {Object} props - The component props. + * @param {any} props.propValue - The prop value. + * @param {Function} props.onAttributeChange - The function to call when the attribute changes. + * @param {any} props.attrValue - The attribute value. + * @param {Function} props.onPropValueChange - The function to call when the prop value changes. + * @return {null} The component. + */ +const BlockBindingConnector = ( { + propValue, + onAttributeChange, + + attrValue, + onPropValueChange = () => {}, +} ) => { + const lastPropValue = useRef(); + const lastAttrValue = useRef(); + + useEffect( () => { + /* + * When the prop value changes, update the attribute value. + */ + if ( propValue === lastPropValue.current ) { + return; + } + + lastPropValue.current = propValue; + onAttributeChange( propValue ); + }, [ onAttributeChange, propValue ] ); + + useEffect( () => { + /* + * When the attribute value changes, update the prop value. + */ + if ( attrValue === lastAttrValue.current ) { + return; + } + + lastAttrValue.current = attrValue; + onPropValueChange( attrValue ); + }, [ onPropValueChange, attrValue ] ); + + return null; +}; + +const withBlockBindingSupport = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes, setAttributes } = props; + + const { getBlockBindingsSource } = useSelect( blockEditorStore ); + + const bindings = attributes?.metadata?.bindings; + const BindingConnectorInstances = []; + + if ( bindings ) { + Object.entries( bindings ).forEach( ( [ attrName, settings ] ) => { + const source = getBlockBindingsSource( settings.source ); + const { useSource } = source; + + if ( source ) { + const attrValue = attributes[ attrName ]; + + /* + * Pick the prop value and setter + * from the source custom hook. + */ + const { useValue: [ propValue, setPropValue ] = [] } = + useSource( props, settings.args ); + + // Create a unique key for the connector instance + const key = `${ settings.source.replace( + /\//gi, + '-' + ) }-${ attrName }`; + + BindingConnectorInstances.push( + { + setAttributes( { + [ attrName ]: newAttrValue, + } ); + }, + [ attrName ] + ) } + propValue={ propValue } + onPropValueChange={ useCallback( + ( newPropValue ) => { + setPropValue?.( newPropValue ); + }, + [ setPropValue ] + ) } + /> + ); + } + } ); + } + + return ( + <> + { BindingConnectorInstances } + + + ); + }, + 'withBlockBindingSupport' +); + +export default withBlockBindingSupport; diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 5263ca3332b250..79bce267ed8931 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -174,3 +174,5 @@ export { useBlockCommands } from './use-block-commands'; * The following rename hint component can be removed in 6.4. */ export { default as ReusableBlocksRenameHint } from './inserter/reusable-block-rename-hint'; + +export { default as __experimentalExtendBlockWithBoundAttributes } from './block-binding-support/'; From 09f85656448afc5946d1819cb1069bc6dc856898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sat, 10 Feb 2024 14:57:44 -0300 Subject: [PATCH 02/79] minor enhancement --- .../with-block-binding-support.js | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index 040f2a49ab1ef0..aa97f4a6ae5160 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -66,54 +66,57 @@ const withBlockBindingSupport = createHigherOrderComponent( const { getBlockBindingsSource } = useSelect( blockEditorStore ); + // Bail early if there are no bindings. const bindings = attributes?.metadata?.bindings; + if ( ! bindings ) { + return ; + } + const BindingConnectorInstances = []; - if ( bindings ) { - Object.entries( bindings ).forEach( ( [ attrName, settings ] ) => { - const source = getBlockBindingsSource( settings.source ); - const { useSource } = source; - - if ( source ) { - const attrValue = attributes[ attrName ]; - - /* - * Pick the prop value and setter - * from the source custom hook. - */ - const { useValue: [ propValue, setPropValue ] = [] } = - useSource( props, settings.args ); - - // Create a unique key for the connector instance - const key = `${ settings.source.replace( - /\//gi, - '-' - ) }-${ attrName }`; - - BindingConnectorInstances.push( - { - setAttributes( { - [ attrName ]: newAttrValue, - } ); - }, - [ attrName ] - ) } - propValue={ propValue } - onPropValueChange={ useCallback( - ( newPropValue ) => { - setPropValue?.( newPropValue ); - }, - [ setPropValue ] - ) } - /> - ); - } - } ); - } + Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { + const source = getBlockBindingsSource( settings.source ); + const { useSource } = source; + + if ( source ) { + const attrValue = attributes[ attrName ]; + + /* + * Pick the prop value and setter + * from the source custom hook. + */ + const { useValue: [ propValue, setPropValue ] = [] } = + useSource( props, settings.args ); + + // Create a unique key for the connector instance + const key = `${ settings.source.replace( + /\//gi, + '-' + ) }-${ attrName }-${ i }`; + + BindingConnectorInstances.push( + { + setAttributes( { + [ attrName ]: newAttrValue, + } ); + }, + [ attrName ] + ) } + propValue={ propValue } + onPropValueChange={ useCallback( + ( newPropValue ) => { + setPropValue?.( newPropValue ); + }, + [ setPropValue ] + ) } + /> + ); + } + } ); return ( <> From 45742caf66db88700052ac95ab5f20d2f39863a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sat, 10 Feb 2024 16:23:16 -0300 Subject: [PATCH 03/79] minor change --- .../block-binding-support/with-block-binding-support.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index aa97f4a6ae5160..174a085b63dc86 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -76,9 +76,9 @@ const withBlockBindingSupport = createHigherOrderComponent( Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { const source = getBlockBindingsSource( settings.source ); - const { useSource } = source; if ( source ) { + const { useSource } = source; const attrValue = attributes[ attrName ]; /* From 6af251711e7e735acfc58a1e285ee5862d9af3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sat, 10 Feb 2024 17:40:54 -0300 Subject: [PATCH 04/79] tweak --- .../with-block-binding-support.js | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index 174a085b63dc86..d493fb06553519 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -17,52 +17,46 @@ import { useSelect } from '@wordpress/data'; * Conponent to bind an attribute to a prop. * * @param {Object} props - The component props. - * @param {any} props.propValue - The prop value. - * @param {Function} props.onAttributeChange - The function to call when the attribute changes. * @param {any} props.attrValue - The attribute value. + * @param {Function} props.onAttributeChange - The function to call when the attribute changes. + * @param {any} props.propValue - The prop value. * @param {Function} props.onPropValueChange - The function to call when the prop value changes. * @return {null} The component. */ const BlockBindingConnector = ( { propValue, - onAttributeChange, + onPropValueChange = () => {}, attrValue, - onPropValueChange = () => {}, + onAttributeChange, } ) => { const lastPropValue = useRef(); const lastAttrValue = useRef(); useEffect( () => { - /* - * When the prop value changes, update the attribute value. - */ if ( propValue === lastPropValue.current ) { return; } lastPropValue.current = propValue; - onAttributeChange( propValue ); - }, [ onAttributeChange, propValue ] ); + onPropValueChange( propValue ); + }, [ onPropValueChange, propValue ] ); useEffect( () => { - /* - * When the attribute value changes, update the prop value. - */ if ( attrValue === lastAttrValue.current ) { return; } lastAttrValue.current = attrValue; - onPropValueChange( attrValue ); - }, [ onPropValueChange, attrValue ] ); + onAttributeChange( attrValue ); + }, [ onAttributeChange, attrValue ] ); return null; }; const withBlockBindingSupport = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const { attributes, setAttributes } = props; + const { attributes, name } = props; const { getBlockBindingsSource } = useSelect( blockEditorStore ); @@ -89,25 +83,22 @@ const withBlockBindingSupport = createHigherOrderComponent( useSource( props, settings.args ); // Create a unique key for the connector instance - const key = `${ settings.source.replace( - /\//gi, - '-' - ) }-${ attrName }-${ i }`; + const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; BindingConnectorInstances.push( { - setAttributes( { + props.setAttributes( { [ attrName ]: newAttrValue, } ); }, [ attrName ] ) } - propValue={ propValue } - onPropValueChange={ useCallback( + attrValue={ attrValue } + onAttributeChange={ useCallback( ( newPropValue ) => { setPropValue?.( newPropValue ); }, From f02c4ef1cbaa8f10f926049de50363af308be61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sat, 10 Feb 2024 17:41:56 -0300 Subject: [PATCH 05/79] do not import use-binding-attributes --- packages/block-editor/src/hooks/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 36efe3dcf409b5..237ada83602222 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -28,7 +28,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; -import './use-bindings-attributes'; +// import './use-bindings-attributes'; createBlockEditFilter( [ From 6412b7d70ea9cbad2f5c491ac52d5e6cb9967cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sat, 10 Feb 2024 17:43:45 -0300 Subject: [PATCH 06/79] use isItPossibleToBindBlock() helper --- packages/block-editor/src/components/rich-text/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 458f5a96609b65..77b0ed46550b82 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -47,7 +47,7 @@ import { getAllowedFormats } from './utils'; import { Content } from './content'; import { withDeprecations } from './with-deprecations'; import { unlock } from '../../lock-unlock'; -import { BLOCK_BINDINGS_ALLOWED_BLOCKS } from '../../hooks/use-bindings-attributes'; +import { isItPossibleToBindBlock } from '../block-binding-support'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -161,7 +161,7 @@ export function RichTextWrapper( ( select ) => { // Disable Rich Text editing if block bindings specify that. let _disableBoundBlocks = false; - if ( blockBindings && blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS ) { + if ( blockBindings && isItPossibleToBindBlock( blockName ) ) { const blockTypeAttributes = getBlockType( blockName ).attributes; const { getBlockBindingsSource } = unlock( From 5749a34536363c8292aed8d4ebe5672f13545687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sat, 10 Feb 2024 17:47:27 -0300 Subject: [PATCH 07/79] introduce core/entity source handler --- packages/editor/src/bindings/entity/index.js | 85 ++++++++++++++++++++ packages/editor/src/bindings/index.js | 2 + 2 files changed, 87 insertions(+) create mode 100644 packages/editor/src/bindings/entity/index.js diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js new file mode 100644 index 00000000000000..5bcb4ea8e4cd7e --- /dev/null +++ b/packages/editor/src/bindings/entity/index.js @@ -0,0 +1,85 @@ +/** + * External dependencies + */ +/** + * WordPress dependencies + */ +import { useEntityProp } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { store as editorStore } from '../../store'; +/** + * React custom hook to bind a source to a block. + * + * @param {Object} blockProps - The block props. + * @param {Object} sourceArgs - The source args. + * @return {Object} The source value and setter. + */ +const useSource = ( blockProps, sourceArgs ) => { + const { context } = blockProps; + + const { postType: contextPostType } = context; + + const { prop: entityPropName, entity: entityType = 'postType' } = + sourceArgs; + + const postType = useSelect( + ( select ) => { + return contextPostType + ? contextPostType + : select( editorStore ).getCurrentPostType(); + }, + [ contextPostType ] + ); + + const [ entityPropValue, setEntityPropValue ] = useEntityProp( + entityType, + postType, + entityPropName + ); + + return { + placeholder: null, + useValue: [ + entityPropValue, + ( nextEntityPropValue ) => { + if ( typeof nextEntityPropValue !== 'string' ) { + return; + } + setEntityPropValue( nextEntityPropValue ); + }, + ], + }; +}; + +/* + * Create the product-entity + * block binding source handler. + * + * source ID: + * `woo/product-entity` + * args: + * - prop: The name of the entity property to bind. + * + * example: + * ``` + * metadata: { + * bindings: { + * content: { + * source: 'woo/product-entity', + * args: { + * prop: 'short_description', + * }, + * }, + * }, + * ``` + */ +export default { + name: 'core/entity', + label: __( 'Core Entity' ), + useSource, + lockAttributesEditing: false, +}; diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js index 1977f9980b067c..415b5f4fa7ece7 100644 --- a/packages/editor/src/bindings/index.js +++ b/packages/editor/src/bindings/index.js @@ -9,7 +9,9 @@ import { dispatch } from '@wordpress/data'; import { unlock } from '../lock-unlock'; import patternOverrides from './pattern-overrides'; import postMeta from './post-meta'; +import entity from './entity'; const { registerBlockBindingsSource } = unlock( dispatch( blocksStore ) ); registerBlockBindingsSource( patternOverrides ); registerBlockBindingsSource( postMeta ); +registerBlockBindingsSource( entity ); From 25b0bb89d4dc0f1c26bd3ef9d2f514157b453eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Sun, 11 Feb 2024 11:40:01 -0300 Subject: [PATCH 08/79] rename folder --- packages/editor/src/bindings/index.js | 2 +- packages/editor/src/bindings/{entity => post-entity}/index.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/editor/src/bindings/{entity => post-entity}/index.js (100%) diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js index 415b5f4fa7ece7..65715173b79c73 100644 --- a/packages/editor/src/bindings/index.js +++ b/packages/editor/src/bindings/index.js @@ -9,7 +9,7 @@ import { dispatch } from '@wordpress/data'; import { unlock } from '../lock-unlock'; import patternOverrides from './pattern-overrides'; import postMeta from './post-meta'; -import entity from './entity'; +import entity from './post-entity'; const { registerBlockBindingsSource } = unlock( dispatch( blocksStore ) ); registerBlockBindingsSource( patternOverrides ); diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/post-entity/index.js similarity index 100% rename from packages/editor/src/bindings/entity/index.js rename to packages/editor/src/bindings/post-entity/index.js From 76e0cb5d09afb4dc78f99f8e4d2075bb701dddc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 08:11:41 -0300 Subject: [PATCH 09/79] rename source name --- packages/editor/src/bindings/post-entity/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/post-entity/index.js b/packages/editor/src/bindings/post-entity/index.js index 5bcb4ea8e4cd7e..62efe3d8bdf0ed 100644 --- a/packages/editor/src/bindings/post-entity/index.js +++ b/packages/editor/src/bindings/post-entity/index.js @@ -78,7 +78,7 @@ const useSource = ( blockProps, sourceArgs ) => { * ``` */ export default { - name: 'core/entity', + name: 'core/post-entity', label: __( 'Core Entity' ), useSource, lockAttributesEditing: false, From dc48e09061388d9e336f04376f6e55f47e10dbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 08:48:17 -0300 Subject: [PATCH 10/79] polish post-entity source handler --- .../editor/src/bindings/post-entity/index.js | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/bindings/post-entity/index.js b/packages/editor/src/bindings/post-entity/index.js index 62efe3d8bdf0ed..fbc927d84f6bfd 100644 --- a/packages/editor/src/bindings/post-entity/index.js +++ b/packages/editor/src/bindings/post-entity/index.js @@ -1,6 +1,3 @@ -/** - * External dependencies - */ /** * WordPress dependencies */ @@ -19,8 +16,15 @@ import { store as editorStore } from '../../store'; * @return {Object} The source value and setter. */ const useSource = ( blockProps, sourceArgs ) => { - const { context } = blockProps; + if ( typeof sourceArgs === 'undefined' ) { + throw new Error( 'The "args" argument is required.' ); + } + if ( ! sourceArgs?.prop ) { + throw new Error( 'The "prop" argument is required.' ); + } + + const { context } = blockProps; const { postType: contextPostType } = context; const { prop: entityPropName, entity: entityType = 'postType' } = @@ -59,19 +63,21 @@ const useSource = ( blockProps, sourceArgs ) => { * Create the product-entity * block binding source handler. * - * source ID: - * `woo/product-entity` + * source ID: `core/post-entity` * args: - * - prop: The name of the entity property to bind. + * - prop: The name of the post entity property to bind. * * example: + * The following metadata will bind the post title + * to the `content` attribute of the block. + * * ``` * metadata: { * bindings: { * content: { - * source: 'woo/product-entity', + * source: 'core/post-entity', * args: { - * prop: 'short_description', + * prop: 'title', * }, * }, * }, @@ -79,7 +85,7 @@ const useSource = ( blockProps, sourceArgs ) => { */ export default { name: 'core/post-entity', - label: __( 'Core Entity' ), + label: __( 'Core Post Entity block binding source' ), useSource, lockAttributesEditing: false, }; From 44421f14d1d6516d05dad005d22d62bbeda9d8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 09:28:28 -0300 Subject: [PATCH 11/79] make core/post-entity more consistent with core-data --- .../editor/src/bindings/post-entity/index.js | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/editor/src/bindings/post-entity/index.js b/packages/editor/src/bindings/post-entity/index.js index fbc927d84f6bfd..4eeec2b23168f0 100644 --- a/packages/editor/src/bindings/post-entity/index.js +++ b/packages/editor/src/bindings/post-entity/index.js @@ -20,15 +20,14 @@ const useSource = ( blockProps, sourceArgs ) => { throw new Error( 'The "args" argument is required.' ); } - if ( ! sourceArgs?.prop ) { - throw new Error( 'The "prop" argument is required.' ); + if ( ! sourceArgs?.name ) { + throw new Error( 'The "name" argument is required.' ); } const { context } = blockProps; const { postType: contextPostType } = context; - const { prop: entityPropName, entity: entityType = 'postType' } = - sourceArgs; + const { name: entityName, entity: entityType = 'postType' } = sourceArgs; const postType = useSelect( ( select ) => { @@ -39,21 +38,21 @@ const useSource = ( blockProps, sourceArgs ) => { [ contextPostType ] ); - const [ entityPropValue, setEntityPropValue ] = useEntityProp( + const [ entityValue, setEntityValue ] = useEntityProp( entityType, postType, - entityPropName + entityName ); return { placeholder: null, useValue: [ - entityPropValue, + entityValue, ( nextEntityPropValue ) => { if ( typeof nextEntityPropValue !== 'string' ) { return; } - setEntityPropValue( nextEntityPropValue ); + setEntityValue( nextEntityPropValue ); }, ], }; @@ -65,7 +64,7 @@ const useSource = ( blockProps, sourceArgs ) => { * * source ID: `core/post-entity` * args: - * - prop: The name of the post entity property to bind. + * - name: The name of the entity to bind. * * example: * The following metadata will bind the post title @@ -77,7 +76,7 @@ const useSource = ( blockProps, sourceArgs ) => { * content: { * source: 'core/post-entity', * args: { - * prop: 'title', + * name: 'title', * }, * }, * }, @@ -85,7 +84,7 @@ const useSource = ( blockProps, sourceArgs ) => { */ export default { name: 'core/post-entity', - label: __( 'Core Post Entity block binding source' ), + label: __( 'Post Entity block-binding source handler' ), useSource, lockAttributesEditing: false, }; From 8a118127b544113d26a85b5aa9d5fbdc6498e396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 10:38:14 -0300 Subject: [PATCH 12/79] make entity source hand;ler more generic --- packages/editor/src/bindings/entity/index.js | 96 +++++++++++++++++++ packages/editor/src/bindings/index.js | 2 +- .../editor/src/bindings/post-entity/index.js | 90 ----------------- 3 files changed, 97 insertions(+), 91 deletions(-) create mode 100644 packages/editor/src/bindings/entity/index.js delete mode 100644 packages/editor/src/bindings/post-entity/index.js diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js new file mode 100644 index 00000000000000..0e4e6de09e61ff --- /dev/null +++ b/packages/editor/src/bindings/entity/index.js @@ -0,0 +1,96 @@ +/** + * WordPress dependencies + */ +import { useEntityProp } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { store as editorStore } from '../../store'; + +/** + * React custom hook to bind a source to a block. + * + * @param {Object} blockProps - The block props. + * @param {Object} sourceArgs - The source args. + * @param {string} sourceArgs.kind - Kind of the entity. Default is `postType`. + * @param {string} sourceArgs.name - Name of the entity. + * @param {string} sourceArgs.prop - The prop to bind. + * @return {Object} The source value and setter. + */ +const useSource = ( blockProps, sourceArgs ) => { + if ( typeof sourceArgs === 'undefined' ) { + throw new Error( 'The "args" argument is required.' ); + } + + if ( ! sourceArgs?.prop ) { + throw new Error( 'The "prop" argument is required.' ); + } + + const { context } = blockProps; + const { prop, kind = 'postType', id } = sourceArgs; + + // Let's define `postType` as the default kind. + const { postType: nameFromContext } = context; + + /* + * Since the `postType` is the default kind, + * Let's try to pick the name from the context + * and from the editor store (post, page, etc). + */ + const name = useSelect( + ( select ) => { + return nameFromContext + ? nameFromContext + : select( editorStore ).getCurrentPostType(); + }, + [ nameFromContext ] + ); + + const [ value, setValue ] = useEntityProp( kind, name, prop, id ); + + return { + placeholder: null, + useValue: [ + value, + ( nextEntityPropValue ) => { + if ( typeof nextEntityPropValue !== 'string' ) { + return; + } + setValue( nextEntityPropValue ); + }, + ], + }; +}; + +/* + * Create the product-entity + * block binding source handler. + * + * source ID: `core/entity` + * args: + * - prop: The prop of the entity to bind. + * + * example: + * The following metadata will bind the title + * to the `content` attribute of the block. + * + * ``` + * metadata: { + * bindings: { + * content: { + * source: 'core/entity', + * args: { + * prop: 'title', + * }, + * }, + * }, + * ``` + */ +export default { + name: 'core/entity', + label: __( 'Entity block-binding source handler' ), + useSource, + lockAttributesEditing: false, +}; diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js index 65715173b79c73..415b5f4fa7ece7 100644 --- a/packages/editor/src/bindings/index.js +++ b/packages/editor/src/bindings/index.js @@ -9,7 +9,7 @@ import { dispatch } from '@wordpress/data'; import { unlock } from '../lock-unlock'; import patternOverrides from './pattern-overrides'; import postMeta from './post-meta'; -import entity from './post-entity'; +import entity from './entity'; const { registerBlockBindingsSource } = unlock( dispatch( blocksStore ) ); registerBlockBindingsSource( patternOverrides ); diff --git a/packages/editor/src/bindings/post-entity/index.js b/packages/editor/src/bindings/post-entity/index.js deleted file mode 100644 index 4eeec2b23168f0..00000000000000 --- a/packages/editor/src/bindings/post-entity/index.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * WordPress dependencies - */ -import { useEntityProp } from '@wordpress/core-data'; -import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -/** - * Internal dependencies - */ -import { store as editorStore } from '../../store'; -/** - * React custom hook to bind a source to a block. - * - * @param {Object} blockProps - The block props. - * @param {Object} sourceArgs - The source args. - * @return {Object} The source value and setter. - */ -const useSource = ( blockProps, sourceArgs ) => { - if ( typeof sourceArgs === 'undefined' ) { - throw new Error( 'The "args" argument is required.' ); - } - - if ( ! sourceArgs?.name ) { - throw new Error( 'The "name" argument is required.' ); - } - - const { context } = blockProps; - const { postType: contextPostType } = context; - - const { name: entityName, entity: entityType = 'postType' } = sourceArgs; - - const postType = useSelect( - ( select ) => { - return contextPostType - ? contextPostType - : select( editorStore ).getCurrentPostType(); - }, - [ contextPostType ] - ); - - const [ entityValue, setEntityValue ] = useEntityProp( - entityType, - postType, - entityName - ); - - return { - placeholder: null, - useValue: [ - entityValue, - ( nextEntityPropValue ) => { - if ( typeof nextEntityPropValue !== 'string' ) { - return; - } - setEntityValue( nextEntityPropValue ); - }, - ], - }; -}; - -/* - * Create the product-entity - * block binding source handler. - * - * source ID: `core/post-entity` - * args: - * - name: The name of the entity to bind. - * - * example: - * The following metadata will bind the post title - * to the `content` attribute of the block. - * - * ``` - * metadata: { - * bindings: { - * content: { - * source: 'core/post-entity', - * args: { - * name: 'title', - * }, - * }, - * }, - * ``` - */ -export default { - name: 'core/post-entity', - label: __( 'Post Entity block-binding source handler' ), - useSource, - lockAttributesEditing: false, -}; From 1b40befe409299452ef4b69ff62ce22230c7cddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 11:03:19 -0300 Subject: [PATCH 13/79] fix entity sour handl;er issues --- packages/editor/src/bindings/entity/index.js | 28 ++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js index 0e4e6de09e61ff..333789b51aa43b 100644 --- a/packages/editor/src/bindings/entity/index.js +++ b/packages/editor/src/bindings/entity/index.js @@ -29,23 +29,35 @@ const useSource = ( blockProps, sourceArgs ) => { } const { context } = blockProps; - const { prop, kind = 'postType', id } = sourceArgs; + const { + kind = 'postType', + name: nameFromArgs = 'post', + prop, + id, + } = sourceArgs; // Let's define `postType` as the default kind. const { postType: nameFromContext } = context; /* - * Since the `postType` is the default kind, - * Let's try to pick the name from the context - * and from the editor store (post, page, etc). + * Entity prop name: + * - If `name` is provided in the source args, use it. + * - If `name` is not provided in the source args, use the `postType` from the context. + * - Otherwise, try to get the current post type from the editor store. */ const name = useSelect( ( select ) => { - return nameFromContext - ? nameFromContext - : select( editorStore ).getCurrentPostType(); + if ( nameFromArgs ) { + return nameFromArgs; + } + + if ( nameFromContext ) { + return nameFromContext; + } + + return select( editorStore ).getCurrentPostType(); }, - [ nameFromContext ] + [ nameFromContext, nameFromArgs ] ); const [ value, setValue ] = useEntityProp( kind, name, prop, id ); From 59b26ba73c08f3cbb20573233d833c8a1399b0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 11:50:30 -0300 Subject: [PATCH 14/79] remove uneeded useValue () hook (crossfingers) --- .../with-block-binding-support.js | 14 +++++++------ packages/editor/src/bindings/entity/index.js | 20 ++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index d493fb06553519..a3a082d89a0c43 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -12,6 +12,7 @@ import { useEffect, useCallback, useRef } from '@wordpress/element'; */ import { store as blockEditorStore } from '../../store'; import { useSelect } from '@wordpress/data'; +import { unlock } from '../../../../editor/src/lock-unlock'; /** * Conponent to bind an attribute to a prop. @@ -58,7 +59,9 @@ const withBlockBindingSupport = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { const { attributes, name } = props; - const { getBlockBindingsSource } = useSelect( blockEditorStore ); + const { getBlockBindingsSource } = unlock( + useSelect( blockEditorStore ) + ); // Bail early if there are no bindings. const bindings = attributes?.metadata?.bindings; @@ -79,8 +82,7 @@ const withBlockBindingSupport = createHigherOrderComponent( * Pick the prop value and setter * from the source custom hook. */ - const { useValue: [ propValue, setPropValue ] = [] } = - useSource( props, settings.args ); + const { value, setValue } = useSource( props, settings.args ); // Create a unique key for the connector instance const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; @@ -88,7 +90,7 @@ const withBlockBindingSupport = createHigherOrderComponent( BindingConnectorInstances.push( { props.setAttributes( { @@ -100,9 +102,9 @@ const withBlockBindingSupport = createHigherOrderComponent( attrValue={ attrValue } onAttributeChange={ useCallback( ( newPropValue ) => { - setPropValue?.( newPropValue ); + setValue?.( newPropValue ); }, - [ setPropValue ] + [ setValue ] ) } /> ); diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js index 333789b51aa43b..c485bdd77ee831 100644 --- a/packages/editor/src/bindings/entity/index.js +++ b/packages/editor/src/bindings/entity/index.js @@ -62,17 +62,19 @@ const useSource = ( blockProps, sourceArgs ) => { const [ value, setValue ] = useEntityProp( kind, name, prop, id ); + function setValueHandler( nextEntityPropValue ) { + // Ensure the value is a string. + if ( typeof nextEntityPropValue !== 'string' ) { + return; + } + + setValue( nextEntityPropValue ); + } + return { placeholder: null, - useValue: [ - value, - ( nextEntityPropValue ) => { - if ( typeof nextEntityPropValue !== 'string' ) { - return; - } - setValue( nextEntityPropValue ); - }, - ], + value, + setValue: setValueHandler, }; }; From 72338e347f05c5de111bc16a6ca3256b40ab84b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 11:57:38 -0300 Subject: [PATCH 15/79] minor jsdoc improvement --- packages/editor/src/bindings/entity/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js index c485bdd77ee831..6f0719bbfb0c22 100644 --- a/packages/editor/src/bindings/entity/index.js +++ b/packages/editor/src/bindings/entity/index.js @@ -17,6 +17,7 @@ import { store as editorStore } from '../../store'; * @param {string} sourceArgs.kind - Kind of the entity. Default is `postType`. * @param {string} sourceArgs.name - Name of the entity. * @param {string} sourceArgs.prop - The prop to bind. + * @param {string} sourceArgs.id - An entity ID to use instead of the context-provided one. Optional. * @return {Object} The source value and setter. */ const useSource = ( blockProps, sourceArgs ) => { From f7d87a8c60687d0eb54d853cf98d72f303e4dcd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 12:56:48 -0300 Subject: [PATCH 16/79] clean --- packages/editor/src/bindings/entity/index.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js index 6f0719bbfb0c22..6fe2666788f37d 100644 --- a/packages/editor/src/bindings/entity/index.js +++ b/packages/editor/src/bindings/entity/index.js @@ -30,14 +30,8 @@ const useSource = ( blockProps, sourceArgs ) => { } const { context } = blockProps; - const { - kind = 'postType', - name: nameFromArgs = 'post', - prop, - id, - } = sourceArgs; + const { kind = 'postType', name: nameFromArgs, prop, id } = sourceArgs; - // Let's define `postType` as the default kind. const { postType: nameFromContext } = context; /* From b60c7ea6f8fa4bd2496fb075cffae7bdf131fa72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 13:24:10 -0300 Subject: [PATCH 17/79] rename with updateValue() --- .../block-binding-support/with-block-binding-support.js | 9 ++++++--- packages/editor/src/bindings/entity/index.js | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index a3a082d89a0c43..caeab7a49b81b9 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -82,7 +82,10 @@ const withBlockBindingSupport = createHigherOrderComponent( * Pick the prop value and setter * from the source custom hook. */ - const { value, setValue } = useSource( props, settings.args ); + const { value, updateValue } = useSource( + props, + settings.args + ); // Create a unique key for the connector instance const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; @@ -102,9 +105,9 @@ const withBlockBindingSupport = createHigherOrderComponent( attrValue={ attrValue } onAttributeChange={ useCallback( ( newPropValue ) => { - setValue?.( newPropValue ); + updateValue?.( newPropValue ); }, - [ setValue ] + [ updateValue ] ) } /> ); diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js index 6fe2666788f37d..919d1c0bc9ed65 100644 --- a/packages/editor/src/bindings/entity/index.js +++ b/packages/editor/src/bindings/entity/index.js @@ -55,21 +55,21 @@ const useSource = ( blockProps, sourceArgs ) => { [ nameFromContext, nameFromArgs ] ); - const [ value, setValue ] = useEntityProp( kind, name, prop, id ); + const [ value, updateValue ] = useEntityProp( kind, name, prop, id ); - function setValueHandler( nextEntityPropValue ) { + function updateValueHandler( nextEntityPropValue ) { // Ensure the value is a string. if ( typeof nextEntityPropValue !== 'string' ) { return; } - setValue( nextEntityPropValue ); + updateValue( nextEntityPropValue ); } return { placeholder: null, value, - setValue: setValueHandler, + updateValue: updateValueHandler, }; }; From adfd711bb92f83b8975fc7ae26865df2b7aa5581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 17:03:38 -0300 Subject: [PATCH 18/79] remove core/entity binding source handler --- packages/editor/src/bindings/entity/index.js | 105 ------------------- packages/editor/src/bindings/index.js | 2 - 2 files changed, 107 deletions(-) delete mode 100644 packages/editor/src/bindings/entity/index.js diff --git a/packages/editor/src/bindings/entity/index.js b/packages/editor/src/bindings/entity/index.js deleted file mode 100644 index 919d1c0bc9ed65..00000000000000 --- a/packages/editor/src/bindings/entity/index.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * WordPress dependencies - */ -import { useEntityProp } from '@wordpress/core-data'; -import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -/** - * Internal dependencies - */ -import { store as editorStore } from '../../store'; - -/** - * React custom hook to bind a source to a block. - * - * @param {Object} blockProps - The block props. - * @param {Object} sourceArgs - The source args. - * @param {string} sourceArgs.kind - Kind of the entity. Default is `postType`. - * @param {string} sourceArgs.name - Name of the entity. - * @param {string} sourceArgs.prop - The prop to bind. - * @param {string} sourceArgs.id - An entity ID to use instead of the context-provided one. Optional. - * @return {Object} The source value and setter. - */ -const useSource = ( blockProps, sourceArgs ) => { - if ( typeof sourceArgs === 'undefined' ) { - throw new Error( 'The "args" argument is required.' ); - } - - if ( ! sourceArgs?.prop ) { - throw new Error( 'The "prop" argument is required.' ); - } - - const { context } = blockProps; - const { kind = 'postType', name: nameFromArgs, prop, id } = sourceArgs; - - const { postType: nameFromContext } = context; - - /* - * Entity prop name: - * - If `name` is provided in the source args, use it. - * - If `name` is not provided in the source args, use the `postType` from the context. - * - Otherwise, try to get the current post type from the editor store. - */ - const name = useSelect( - ( select ) => { - if ( nameFromArgs ) { - return nameFromArgs; - } - - if ( nameFromContext ) { - return nameFromContext; - } - - return select( editorStore ).getCurrentPostType(); - }, - [ nameFromContext, nameFromArgs ] - ); - - const [ value, updateValue ] = useEntityProp( kind, name, prop, id ); - - function updateValueHandler( nextEntityPropValue ) { - // Ensure the value is a string. - if ( typeof nextEntityPropValue !== 'string' ) { - return; - } - - updateValue( nextEntityPropValue ); - } - - return { - placeholder: null, - value, - updateValue: updateValueHandler, - }; -}; - -/* - * Create the product-entity - * block binding source handler. - * - * source ID: `core/entity` - * args: - * - prop: The prop of the entity to bind. - * - * example: - * The following metadata will bind the title - * to the `content` attribute of the block. - * - * ``` - * metadata: { - * bindings: { - * content: { - * source: 'core/entity', - * args: { - * prop: 'title', - * }, - * }, - * }, - * ``` - */ -export default { - name: 'core/entity', - label: __( 'Entity block-binding source handler' ), - useSource, - lockAttributesEditing: false, -}; diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js index 415b5f4fa7ece7..1977f9980b067c 100644 --- a/packages/editor/src/bindings/index.js +++ b/packages/editor/src/bindings/index.js @@ -9,9 +9,7 @@ import { dispatch } from '@wordpress/data'; import { unlock } from '../lock-unlock'; import patternOverrides from './pattern-overrides'; import postMeta from './post-meta'; -import entity from './entity'; const { registerBlockBindingsSource } = unlock( dispatch( blocksStore ) ); registerBlockBindingsSource( patternOverrides ); registerBlockBindingsSource( postMeta ); -registerBlockBindingsSource( entity ); From 832f17009698159de4f3f2108985323e2358ad1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 17:22:38 -0300 Subject: [PATCH 19/79] move useSource to Connector cmp --- .../with-block-binding-support.js | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index caeab7a49b81b9..f26579ec49d10b 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -19,29 +19,27 @@ import { unlock } from '../../../../editor/src/lock-unlock'; * * @param {Object} props - The component props. * @param {any} props.attrValue - The attribute value. - * @param {Function} props.onAttributeChange - The function to call when the attribute changes. - * @param {any} props.propValue - The prop value. * @param {Function} props.onPropValueChange - The function to call when the prop value changes. - * @return {null} The component. + * @param {Function} props.useSource - The custom hook to use the source. + * @return {null} This is a data-handling component. */ const BlockBindingConnector = ( { - propValue, - onPropValueChange = () => {}, - attrValue, - onAttributeChange, + onPropValueChange = () => {}, + useSource, } ) => { const lastPropValue = useRef(); const lastAttrValue = useRef(); + const { value, updateValue } = useSource(); useEffect( () => { - if ( propValue === lastPropValue.current ) { + if ( value === lastPropValue.current ) { return; } - lastPropValue.current = propValue; - onPropValueChange( propValue ); - }, [ onPropValueChange, propValue ] ); + lastPropValue.current = value; + onPropValueChange( value ); + }, [ onPropValueChange, value ] ); useEffect( () => { if ( attrValue === lastAttrValue.current ) { @@ -49,8 +47,8 @@ const BlockBindingConnector = ( { } lastAttrValue.current = attrValue; - onAttributeChange( attrValue ); - }, [ onAttributeChange, attrValue ] ); + updateValue( attrValue ); + }, [ updateValue, attrValue ] ); return null; }; @@ -78,22 +76,13 @@ const withBlockBindingSupport = createHigherOrderComponent( const { useSource } = source; const attrValue = attributes[ attrName ]; - /* - * Pick the prop value and setter - * from the source custom hook. - */ - const { value, updateValue } = useSource( - props, - settings.args - ); - // Create a unique key for the connector instance const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; BindingConnectorInstances.push( { props.setAttributes( { @@ -102,13 +91,7 @@ const withBlockBindingSupport = createHigherOrderComponent( }, [ attrName ] ) } - attrValue={ attrValue } - onAttributeChange={ useCallback( - ( newPropValue ) => { - updateValue?.( newPropValue ); - }, - [ updateValue ] - ) } + useSource={ useSource } /> ); } From 42fc47c170badd5f6a7d58db069120cfef384fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 17:47:46 -0300 Subject: [PATCH 20/79] move the whole dryining logic to the Connect component --- .../with-block-binding-support.js | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index f26579ec49d10b..04accda1834562 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -17,21 +17,42 @@ import { unlock } from '../../../../editor/src/lock-unlock'; /** * Conponent to bind an attribute to a prop. * - * @param {Object} props - The component props. - * @param {any} props.attrValue - The attribute value. - * @param {Function} props.onPropValueChange - The function to call when the prop value changes. - * @param {Function} props.useSource - The custom hook to use the source. - * @return {null} This is a data-handling component. + * @param {Object} props - The component props. + * @param {string} props.attrName - The attribute name. + * @param {any} props.attrValue - The attribute value. + * @param {Function} props.useSource - The custom hook to use the source. + * @param {Object} props.props - The block props with bound attributes. + * @param {Object} props.args - The arguments to pass to the source. + * @return {null} This is a data-handling component. Render nothing. */ const BlockBindingConnector = ( { + attrName, attrValue, - onPropValueChange = () => {}, useSource, + props: blockProps, + args, } ) => { const lastPropValue = useRef(); const lastAttrValue = useRef(); - const { value, updateValue } = useSource(); - + const { value, updateValue } = useSource( blockProps, args ); + + const setAttributes = blockProps.setAttributes; + + const onPropValueChange = useCallback( + ( newAttrValue ) => { + setAttributes( { + [ attrName ]: newAttrValue, + } ); + }, + [ attrName, setAttributes ] + ); + + /* + * From Source Prop => Block Attribute + * + * Detect changes in source prop value, + * and update the attribute value accordingly. + */ useEffect( () => { if ( value === lastPropValue.current ) { return; @@ -41,6 +62,12 @@ const BlockBindingConnector = ( { onPropValueChange( value ); }, [ onPropValueChange, value ] ); + /* + * From Block Attribute => Source Prop + * + * Detect changes in block attribute value, + * and update the source prop value accordingly. + */ useEffect( () => { if ( attrValue === lastAttrValue.current ) { return; @@ -82,16 +109,11 @@ const withBlockBindingSupport = createHigherOrderComponent( BindingConnectorInstances.push( { - props.setAttributes( { - [ attrName ]: newAttrValue, - } ); - }, - [ attrName ] - ) } useSource={ useSource } + props={ props } + args={ settings.args } /> ); } From 09c8cd3ea27f7d94229df3dae94fc3c963651402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 17:51:32 -0300 Subject: [PATCH 21/79] improve jsdoc --- .../with-block-binding-support.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index 04accda1834562..a652e13273c771 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -1,6 +1,3 @@ -/** - * External dependencies - */ /** * WordPress dependencies */ @@ -15,7 +12,12 @@ import { useSelect } from '@wordpress/data'; import { unlock } from '../../../../editor/src/lock-unlock'; /** - * Conponent to bind an attribute to a prop. + * This component is responsible detecting and + * propagating data changes between block attribute and + * the block-binding source property. + * + * The app creates an instance of this component for each + * pair of block-attribute/source-property. * * @param {Object} props - The component props. * @param {string} props.attrName - The attribute name. @@ -48,7 +50,7 @@ const BlockBindingConnector = ( { ); /* - * From Source Prop => Block Attribute + * Source Prop => Block Attribute * * Detect changes in source prop value, * and update the attribute value accordingly. @@ -63,7 +65,7 @@ const BlockBindingConnector = ( { }, [ onPropValueChange, value ] ); /* - * From Block Attribute => Source Prop + * Block Attribute => Source Prop * * Detect changes in block attribute value, * and update the source prop value accordingly. From b0a031001e50d8ea8049dcf5826e26d541e5b825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 12 Feb 2024 17:59:21 -0300 Subject: [PATCH 22/79] rename to blockProps --- .../with-block-binding-support.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index a652e13273c771..f81627d499decd 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -19,19 +19,19 @@ import { unlock } from '../../../../editor/src/lock-unlock'; * The app creates an instance of this component for each * pair of block-attribute/source-property. * - * @param {Object} props - The component props. - * @param {string} props.attrName - The attribute name. - * @param {any} props.attrValue - The attribute value. - * @param {Function} props.useSource - The custom hook to use the source. - * @param {Object} props.props - The block props with bound attributes. - * @param {Object} props.args - The arguments to pass to the source. + * @param {Object} props - The component props. + * @param {string} props.attrName - The attribute name. + * @param {any} props.attrValue - The attribute value. + * @param {Function} props.useSource - The custom hook to use the source. + * @param {Object} props.blockProps - The block props with bound attributes. + * @param {Object} props.args - The arguments to pass to the source. * @return {null} This is a data-handling component. Render nothing. */ const BlockBindingConnector = ( { attrName, attrValue, useSource, - props: blockProps, + blockProps, args, } ) => { const lastPropValue = useRef(); @@ -114,7 +114,7 @@ const withBlockBindingSupport = createHigherOrderComponent( attrName={ attrName } attrValue={ attrValue } useSource={ useSource } - props={ props } + blockProps={ props } args={ settings.args } /> ); From c46c14032f0265141d9721b7e62ccd933155ded0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 13 Feb 2024 17:31:58 -0300 Subject: [PATCH 23/79] minor jsdoc improvements --- .../block-binding-support/with-block-binding-support.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index f81627d499decd..c332eee0288e5d 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -23,9 +23,9 @@ import { unlock } from '../../../../editor/src/lock-unlock'; * @param {string} props.attrName - The attribute name. * @param {any} props.attrValue - The attribute value. * @param {Function} props.useSource - The custom hook to use the source. - * @param {Object} props.blockProps - The block props with bound attributes. + * @param {Object} props.blockProps - The block props with bound attribute. * @param {Object} props.args - The arguments to pass to the source. - * @return {null} This is a data-handling component. Render nothing. + * @return {null} This is a data-handling component. Render nothing. */ const BlockBindingConnector = ( { attrName, From 4f4e3b5929b1c6bd1bcd980b7905db612def7e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 13 Feb 2024 17:32:59 -0300 Subject: [PATCH 24/79] use a single effect to update attr and value --- .../with-block-binding-support.js | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index c332eee0288e5d..7b30f55b366ac2 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -28,19 +28,19 @@ import { unlock } from '../../../../editor/src/lock-unlock'; * @return {null} This is a data-handling component. Render nothing. */ const BlockBindingConnector = ( { + args, attrName, attrValue, - useSource, blockProps, - args, + useSource, } ) => { - const lastPropValue = useRef(); - const lastAttrValue = useRef(); - const { value, updateValue } = useSource( blockProps, args ); - + const { value: propValue, updateValue: updatePropValue } = useSource( + blockProps, + args + ); const setAttributes = blockProps.setAttributes; - const onPropValueChange = useCallback( + const updateBoundAttibute = useCallback( ( newAttrValue ) => { setAttributes( { [ attrName ]: newAttrValue, @@ -49,35 +49,40 @@ const BlockBindingConnector = ( { [ attrName, setAttributes ] ); - /* - * Source Prop => Block Attribute - * - * Detect changes in source prop value, - * and update the attribute value accordingly. - */ - useEffect( () => { - if ( value === lastPropValue.current ) { - return; - } - - lastPropValue.current = value; - onPropValueChange( value ); - }, [ onPropValueChange, value ] ); + // Store a reference to the last value and attribute value. + const lastPropValue = useRef(); + const lastAttrValue = useRef(); /* - * Block Attribute => Source Prop - * - * Detect changes in block attribute value, - * and update the source prop value accordingly. + * Sync data. + * This effect will run every time + * the attribute value or the prop value changes. + * It will sync them in both directions. */ useEffect( () => { - if ( attrValue === lastAttrValue.current ) { + /* + * Source Prop => Block Attribute + * + * Detect changes in source prop value, + * and update the attribute value accordingly. + */ + if ( propValue !== lastPropValue.current ) { + lastPropValue.current = propValue; + updateBoundAttibute( propValue ); return; } - lastAttrValue.current = attrValue; - updateValue( attrValue ); - }, [ updateValue, attrValue ] ); + /* + * Block Attribute => Source Prop + * + * Detect changes in block attribute value, + * and update the source prop value accordingly. + */ + if ( attrValue !== lastAttrValue.current ) { + lastAttrValue.current = attrValue; + updatePropValue( attrValue ); + } + }, [ updateBoundAttibute, propValue, attrValue, updatePropValue ] ); return null; }; From 5ae157e2bed772339fc841eb206e402f62419d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 13 Feb 2024 18:08:38 -0300 Subject: [PATCH 25/79] discard useValue. Return value and setValue instead --- packages/editor/src/bindings/post-meta.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index a9a00599b68037..0d0c737d0eaf77 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -19,6 +19,7 @@ export default { const postType = context.postType ? context.postType : getCurrentPostType(); + const [ meta, setMeta ] = useEntityProp( 'postType', context.postType, @@ -33,9 +34,11 @@ export default { const updateMetaValue = ( newValue ) => { setMeta( { ...meta, [ metaKey ]: newValue } ); }; + return { placeholder: metaKey, - useValue: [ metaValue, updateMetaValue ], + value: metaValue, + updateValue: updateMetaValue, }; }, }; From 6ebb24001f6862bf790b612f08e699ea21d6f3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 13 Feb 2024 18:40:33 -0300 Subject: [PATCH 26/79] check wheter updateValue function is defined --- .../block-binding-support/with-block-binding-support.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index 7b30f55b366ac2..17c8a524d5f3ca 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -34,10 +34,9 @@ const BlockBindingConnector = ( { blockProps, useSource, } ) => { - const { value: propValue, updateValue: updatePropValue } = useSource( - blockProps, - args - ); + const { value: propValue, updateValue: updatePropValue } = + useSource( blockProps, args ) || {}; + const setAttributes = blockProps.setAttributes; const updateBoundAttibute = useCallback( @@ -78,7 +77,7 @@ const BlockBindingConnector = ( { * Detect changes in block attribute value, * and update the source prop value accordingly. */ - if ( attrValue !== lastAttrValue.current ) { + if ( attrValue !== lastAttrValue.current && updatePropValue ) { lastAttrValue.current = attrValue; updatePropValue( attrValue ); } From 8c8c90c5d709079de04719d33d4605fb8de802a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 13 Feb 2024 18:44:29 -0300 Subject: [PATCH 27/79] check prop value is defined when updating attr --- .../block-binding-support/with-block-binding-support.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index 17c8a524d5f3ca..5b7befe909631f 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -65,7 +65,7 @@ const BlockBindingConnector = ( { * Detect changes in source prop value, * and update the attribute value accordingly. */ - if ( propValue !== lastPropValue.current ) { + if ( propValue && propValue !== lastPropValue.current ) { lastPropValue.current = propValue; updateBoundAttibute( propValue ); return; @@ -77,7 +77,11 @@ const BlockBindingConnector = ( { * Detect changes in block attribute value, * and update the source prop value accordingly. */ - if ( attrValue !== lastAttrValue.current && updatePropValue ) { + if ( + attrValue && + attrValue !== lastAttrValue.current && // only update if the value has changed + updatePropValue // check whether update value handler is available + ) { lastAttrValue.current = attrValue; updatePropValue( attrValue ); } From 536bc905489f1155875dbec94af7cd13ba2ce12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 14 Feb 2024 14:51:59 -0300 Subject: [PATCH 28/79] handle `placerholder` --- .../with-block-binding-support.js | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index 5b7befe909631f..c5a8df7c6ddc2f 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -10,6 +10,7 @@ import { useEffect, useCallback, useRef } from '@wordpress/element'; import { store as blockEditorStore } from '../../store'; import { useSelect } from '@wordpress/data'; import { unlock } from '../../../../editor/src/lock-unlock'; +import { getBlockType } from '@wordpress/blocks'; /** * This component is responsible detecting and @@ -34,8 +35,13 @@ const BlockBindingConnector = ( { blockProps, useSource, } ) => { - const { value: propValue, updateValue: updatePropValue } = - useSource( blockProps, args ) || {}; + const { + placeholder, + value: propValue, + updateValue: updatePropValue, + } = useSource( blockProps, args ); + + const blockName = blockProps.name; const setAttributes = blockProps.setAttributes; @@ -65,10 +71,28 @@ const BlockBindingConnector = ( { * Detect changes in source prop value, * and update the attribute value accordingly. */ - if ( propValue && propValue !== lastPropValue.current ) { - lastPropValue.current = propValue; - updateBoundAttibute( propValue ); - return; + if ( typeof propValue !== 'undefined' ) { + if ( propValue !== lastPropValue.current ) { + lastPropValue.current = propValue; + updateBoundAttibute( propValue ); + return; + } + } else if ( placeholder ) { + /* + * If the attribute is `src` or `href`, + * a placeholder can't be used because it is not a valid url. + * Adding this workaround until + * attributes and metadata fields types are improved and include `url`. + */ + const htmlAttribute = + getBlockType( blockName ).attributes[ attrName ].attribute; + + if ( htmlAttribute === 'src' || htmlAttribute === 'href' ) { + updateBoundAttibute( null ); + return; + } + + updateBoundAttibute( placeholder ); } /* @@ -77,15 +101,19 @@ const BlockBindingConnector = ( { * Detect changes in block attribute value, * and update the source prop value accordingly. */ - if ( - attrValue && - attrValue !== lastAttrValue.current && // only update if the value has changed - updatePropValue // check whether update value handler is available - ) { + if ( attrValue !== lastAttrValue.current && updatePropValue ) { lastAttrValue.current = attrValue; updatePropValue( attrValue ); } - }, [ updateBoundAttibute, propValue, attrValue, updatePropValue ] ); + }, [ + updateBoundAttibute, + propValue, + attrValue, + updatePropValue, + placeholder, + blockName, + attrName, + ] ); return null; }; From 4063d7712c107736eeb7c1c03959e3219d5f2135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 14 Feb 2024 14:56:40 -0300 Subject: [PATCH 29/79] ensure to put attribute in sync when onmount --- .../with-block-binding-support.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index c5a8df7c6ddc2f..a3f3649783d25a 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -55,8 +55,16 @@ const BlockBindingConnector = ( { ); // Store a reference to the last value and attribute value. - const lastPropValue = useRef(); - const lastAttrValue = useRef(); + const lastPropValue = useRef( propValue ); + const lastAttrValue = useRef( attrValue ); + + /* + * Initially sync (first render / onMount ) attribute + * value with the source prop value. + */ + useEffect( () => { + updateBoundAttibute( propValue ); + }, [ propValue, updateBoundAttibute ] ); // eslint-disable-line react-hooks/exhaustive-deps /* * Sync data. From 6a28f73e142d3e32ed32afeb2801f5598dacd08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 14 Feb 2024 15:50:03 -0300 Subject: [PATCH 30/79] remove // eslint comment --- .../block-binding-support/with-block-binding-support.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js index a3f3649783d25a..776feb36d723fe 100644 --- a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js @@ -64,7 +64,7 @@ const BlockBindingConnector = ( { */ useEffect( () => { updateBoundAttibute( propValue ); - }, [ propValue, updateBoundAttibute ] ); // eslint-disable-line react-hooks/exhaustive-deps + }, [ propValue, updateBoundAttibute ] ); /* * Sync data. From 55c8cdac369c2a22f8f34a3a608b75ab5fa37ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 14 Feb 2024 17:05:03 -0300 Subject: [PATCH 31/79] enable editing for bound with post-meta --- 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 a255fd5044e363d508de5480bcf01e84e7ea659f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 14 Feb 2024 17:06:58 -0300 Subject: [PATCH 32/79] move block bindiung processor to hooks/ --- packages/block-editor/src/components/index.js | 2 -- packages/block-editor/src/components/rich-text/index.js | 2 +- .../src/{components => hooks}/block-binding-support/index.js | 0 .../block-binding-support/with-block-binding-support.js | 0 packages/block-editor/src/hooks/index.js | 1 + 5 files changed, 2 insertions(+), 3 deletions(-) rename packages/block-editor/src/{components => hooks}/block-binding-support/index.js (100%) rename packages/block-editor/src/{components => hooks}/block-binding-support/with-block-binding-support.js (100%) diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 79bce267ed8931..5263ca3332b250 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -174,5 +174,3 @@ export { useBlockCommands } from './use-block-commands'; * The following rename hint component can be removed in 6.4. */ export { default as ReusableBlocksRenameHint } from './inserter/reusable-block-rename-hint'; - -export { default as __experimentalExtendBlockWithBoundAttributes } from './block-binding-support/'; diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 77b0ed46550b82..8d9074a24efd9a 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -47,7 +47,7 @@ import { getAllowedFormats } from './utils'; import { Content } from './content'; import { withDeprecations } from './with-deprecations'; import { unlock } from '../../lock-unlock'; -import { isItPossibleToBindBlock } from '../block-binding-support'; +import { isItPossibleToBindBlock } from '../../hooks/block-binding-support'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); diff --git a/packages/block-editor/src/components/block-binding-support/index.js b/packages/block-editor/src/hooks/block-binding-support/index.js similarity index 100% rename from packages/block-editor/src/components/block-binding-support/index.js rename to packages/block-editor/src/hooks/block-binding-support/index.js diff --git a/packages/block-editor/src/components/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js similarity index 100% rename from packages/block-editor/src/components/block-binding-support/with-block-binding-support.js rename to packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 237ada83602222..44c524e1dc86ab 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -28,6 +28,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; +import './block-binding-support'; // import './use-bindings-attributes'; createBlockEditFilter( From a2b5326750e5561ab5e3540c4c90c279edea619a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 08:53:24 -0300 Subject: [PATCH 33/79] ensure update bound attr once when mounting --- .../hooks/block-binding-support/with-block-binding-support.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js index 776feb36d723fe..573a20840e38a0 100644 --- a/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js +++ b/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js @@ -64,7 +64,8 @@ const BlockBindingConnector = ( { */ useEffect( () => { updateBoundAttibute( propValue ); - }, [ propValue, updateBoundAttibute ] ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ updateBoundAttibute ] ); /* * Sync data. From ad5b1e194a07e36fa31f0dc0c4ff8dbd1cf3733f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 08:30:39 -0300 Subject: [PATCH 34/79] Update packages/block-editor/src/hooks/block-binding-support/index.js Co-authored-by: Michal --- packages/block-editor/src/hooks/block-binding-support/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/block-binding-support/index.js b/packages/block-editor/src/hooks/block-binding-support/index.js index 39cfcd2cf7a088..f9d2f72906e87d 100644 --- a/packages/block-editor/src/hooks/block-binding-support/index.js +++ b/packages/block-editor/src/hooks/block-binding-support/index.js @@ -44,6 +44,6 @@ export default function extendBlockWithBoundAttributes( settings, name ) { addFilter( 'blocks.registerBlockType', - 'block-edit-with-binding-attributes', + 'core/editor/block-edit-with-binding-attributes', extendBlockWithBoundAttributes ); From 4e8f2c8da6000d0fd9e36c16ff04b8ad7999f326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 11:06:40 -0300 Subject: [PATCH 35/79] disable editing block attribute --- packages/editor/src/bindings/post-meta.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index c2ecdbbd0b3103..0d0c737d0eaf77 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -41,5 +41,4 @@ export default { updateValue: updateMetaValue, }; }, - lockAttributesEditing: false, }; From f06ce6d6a7c0734a4189dc0fa59bdf18efc798c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 11:12:56 -0300 Subject: [PATCH 36/79] move changes to the use-binding-attributes file --- .../src/hooks/block-binding-support/index.js | 49 ---- .../with-block-binding-support.js | 179 ------------ packages/block-editor/src/hooks/index.js | 3 +- .../src/hooks/use-bindings-attributes.js | 260 ++++++++++++------ 4 files changed, 178 insertions(+), 313 deletions(-) delete mode 100644 packages/block-editor/src/hooks/block-binding-support/index.js delete mode 100644 packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js diff --git a/packages/block-editor/src/hooks/block-binding-support/index.js b/packages/block-editor/src/hooks/block-binding-support/index.js deleted file mode 100644 index f9d2f72906e87d..00000000000000 --- a/packages/block-editor/src/hooks/block-binding-support/index.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import withBlockBindingSupport from './with-block-binding-support'; - -export const BLOCK_BINDINGS_ALLOWED_BLOCKS = { - 'core/paragraph': [ 'content' ], - 'core/heading': [ 'content' ], - 'core/image': [ 'url', 'title', 'alt' ], - 'core/button': [ 'url', 'text', 'linkTarget' ], -}; - -export function isItPossibleToBindBlock( blockName ) { - return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; -} - -export default function extendBlockWithBoundAttributes( settings, name ) { - if ( ! isItPossibleToBindBlock( name ) ) { - return settings; - } - - return { - ...settings, - /* - * Expose relevant data through - * the block context. - */ - usesContext: [ - ...new Set( [ - ...( settings.usesContext || [] ), - 'postId', - 'postType', - 'queryId', - ] ), - ], - edit: withBlockBindingSupport( settings.edit ), - }; -} - -addFilter( - 'blocks.registerBlockType', - 'core/editor/block-edit-with-binding-attributes', - extendBlockWithBoundAttributes -); diff --git a/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js b/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js deleted file mode 100644 index 573a20840e38a0..00000000000000 --- a/packages/block-editor/src/hooks/block-binding-support/with-block-binding-support.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * WordPress dependencies - */ -import { createHigherOrderComponent } from '@wordpress/compose'; -import { useEffect, useCallback, useRef } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; -import { useSelect } from '@wordpress/data'; -import { unlock } from '../../../../editor/src/lock-unlock'; -import { getBlockType } from '@wordpress/blocks'; - -/** - * This component is responsible detecting and - * propagating data changes between block attribute and - * the block-binding source property. - * - * The app creates an instance of this component for each - * pair of block-attribute/source-property. - * - * @param {Object} props - The component props. - * @param {string} props.attrName - The attribute name. - * @param {any} props.attrValue - The attribute value. - * @param {Function} props.useSource - The custom hook to use the source. - * @param {Object} props.blockProps - The block props with bound attribute. - * @param {Object} props.args - The arguments to pass to the source. - * @return {null} This is a data-handling component. Render nothing. - */ -const BlockBindingConnector = ( { - args, - attrName, - attrValue, - blockProps, - useSource, -} ) => { - const { - placeholder, - value: propValue, - updateValue: updatePropValue, - } = useSource( blockProps, args ); - - const blockName = blockProps.name; - - const setAttributes = blockProps.setAttributes; - - const updateBoundAttibute = useCallback( - ( newAttrValue ) => { - setAttributes( { - [ attrName ]: newAttrValue, - } ); - }, - [ attrName, setAttributes ] - ); - - // Store a reference to the last value and attribute value. - const lastPropValue = useRef( propValue ); - const lastAttrValue = useRef( attrValue ); - - /* - * Initially sync (first render / onMount ) attribute - * value with the source prop value. - */ - useEffect( () => { - updateBoundAttibute( propValue ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ updateBoundAttibute ] ); - - /* - * Sync data. - * This effect will run every time - * the attribute value or the prop value changes. - * It will sync them in both directions. - */ - useEffect( () => { - /* - * Source Prop => Block Attribute - * - * Detect changes in source prop value, - * and update the attribute value accordingly. - */ - if ( typeof propValue !== 'undefined' ) { - if ( propValue !== lastPropValue.current ) { - lastPropValue.current = propValue; - updateBoundAttibute( propValue ); - return; - } - } else if ( placeholder ) { - /* - * If the attribute is `src` or `href`, - * a placeholder can't be used because it is not a valid url. - * Adding this workaround until - * attributes and metadata fields types are improved and include `url`. - */ - const htmlAttribute = - getBlockType( blockName ).attributes[ attrName ].attribute; - - if ( htmlAttribute === 'src' || htmlAttribute === 'href' ) { - updateBoundAttibute( null ); - return; - } - - updateBoundAttibute( placeholder ); - } - - /* - * Block Attribute => Source Prop - * - * Detect changes in block attribute value, - * and update the source prop value accordingly. - */ - if ( attrValue !== lastAttrValue.current && updatePropValue ) { - lastAttrValue.current = attrValue; - updatePropValue( attrValue ); - } - }, [ - updateBoundAttibute, - propValue, - attrValue, - updatePropValue, - placeholder, - blockName, - attrName, - ] ); - - return null; -}; - -const withBlockBindingSupport = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const { attributes, name } = props; - - const { getBlockBindingsSource } = unlock( - useSelect( blockEditorStore ) - ); - - // Bail early if there are no bindings. - const bindings = attributes?.metadata?.bindings; - if ( ! bindings ) { - return ; - } - - const BindingConnectorInstances = []; - - Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { - const source = getBlockBindingsSource( settings.source ); - - if ( source ) { - const { useSource } = source; - const attrValue = attributes[ attrName ]; - - // Create a unique key for the connector instance - const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; - - BindingConnectorInstances.push( - - ); - } - } ); - - return ( - <> - { BindingConnectorInstances } - - - ); - }, - 'withBlockBindingSupport' -); - -export default withBlockBindingSupport; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 44c524e1dc86ab..36efe3dcf409b5 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -28,8 +28,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; -import './block-binding-support'; -// import './use-bindings-attributes'; +import './use-bindings-attributes'; createBlockEditFilter( [ diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 0e5b6614f07cbf..1e145796f91f46 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -1,112 +1,206 @@ /** * WordPress dependencies */ -import { getBlockType, store as blocksStore } from '@wordpress/blocks'; +import { getBlockType } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; +import { useEffect, useCallback, useRef } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; +import { useSelect } from '@wordpress/data'; +import { unlock } from '@wordpress/icons'; + /** * Internal dependencies */ import { store as blockEditorStore } from '../store'; -import { useBlockEditContext } from '../components/block-edit/context'; -import { unlock } from '../lock-unlock'; -/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ -/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ - -/** - * Given a binding of block attributes, returns a higher order component that - * overrides its `attributes` and `setAttributes` props to sync any changes needed. - * - * @return {WPHigherOrderComponent} Higher-order component. - */ - -export const BLOCK_BINDINGS_ALLOWED_BLOCKS = { +const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], 'core/heading': [ 'content' ], 'core/image': [ 'url', 'title', 'alt' ], 'core/button': [ 'url', 'text', 'linkTarget' ], }; -const createEditFunctionWithBindingsAttribute = () => - createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const { clientId, name: blockName } = useBlockEditContext(); - const blockBindingsSources = unlock( - useSelect( blocksStore ) - ).getAllBlockBindingsSources(); - const { getBlockAttributes } = useSelect( blockEditorStore ); - - const updatedAttributes = getBlockAttributes( clientId ); - if ( updatedAttributes?.metadata?.bindings ) { - Object.entries( updatedAttributes.metadata.bindings ).forEach( - ( [ attributeName, settings ] ) => { - const source = blockBindingsSources[ settings.source ]; - - if ( source && source.useSource ) { - // Second argument (`updateMetaValue`) will be used to update the value in the future. - const { - placeholder, - useValue: [ metaValue = null ] = [], - } = source.useSource( props, settings.args ); - - if ( placeholder && ! metaValue ) { - // If the attribute is `src` or `href`, a placeholder can't be used because it is not a valid url. - // Adding this workaround until attributes and metadata fields types are improved and include `url`. - const htmlAttribute = - getBlockType( blockName ).attributes[ - attributeName - ].attribute; - if ( - htmlAttribute === 'src' || - htmlAttribute === 'href' - ) { - updatedAttributes[ attributeName ] = null; - } else { - updatedAttributes[ attributeName ] = - placeholder; - } - } - - if ( metaValue ) { - updatedAttributes[ attributeName ] = metaValue; - } - } - } - ); - } - - return ( - - ); - }, - 'useBoundAttributes' - ); +export function isItPossibleToBindBlock( blockName ) { + return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; +} /** - * Filters a registered block's settings to enhance a block's `edit` component - * to upgrade bound attributes. + * This component is responsible detecting and + * propagating data changes between block attribute and + * the block-binding source property. * - * @param {WPBlockSettings} settings Registered block settings. + * The app creates an instance of this component for each + * pair of block-attribute/source-property. * - * @return {WPBlockSettings} Filtered block settings. + * @param {Object} props - The component props. + * @param {string} props.attrName - The attribute name. + * @param {any} props.attrValue - The attribute value. + * @param {Function} props.useSource - The custom hook to use the source. + * @param {Object} props.blockProps - The block props with bound attribute. + * @param {Object} props.args - The arguments to pass to the source. + * @return {null} This is a data-handling component. Render nothing. */ -function shimAttributeSource( settings ) { - if ( ! ( settings.name in BLOCK_BINDINGS_ALLOWED_BLOCKS ) ) { +const BlockBindingConnector = ( { + args, + attrName, + attrValue, + blockProps, + useSource, +} ) => { + const { + placeholder, + value: propValue, + updateValue: updatePropValue, + } = useSource( blockProps, args ); + + const blockName = blockProps.name; + + const setAttributes = blockProps.setAttributes; + + const updateBoundAttibute = useCallback( + ( newAttrValue ) => { + setAttributes( { + [ attrName ]: newAttrValue, + } ); + }, + [ attrName, setAttributes ] + ); + + // Store a reference to the last value and attribute value. + const lastPropValue = useRef( propValue ); + const lastAttrValue = useRef( attrValue ); + + /* + * Initially sync (first render / onMount ) attribute + * value with the source prop value. + */ + useEffect( () => { + updateBoundAttibute( propValue ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ updateBoundAttibute ] ); + + /* + * Sync data. + * This effect will run every time + * the attribute value or the prop value changes. + * It will sync them in both directions. + */ + useEffect( () => { + /* + * Source Prop => Block Attribute + * + * Detect changes in source prop value, + * and update the attribute value accordingly. + */ + if ( typeof propValue !== 'undefined' ) { + if ( propValue !== lastPropValue.current ) { + lastPropValue.current = propValue; + updateBoundAttibute( propValue ); + return; + } + } else if ( placeholder ) { + /* + * If the attribute is `src` or `href`, + * a placeholder can't be used because it is not a valid url. + * Adding this workaround until + * attributes and metadata fields types are improved and include `url`. + */ + const htmlAttribute = + getBlockType( blockName ).attributes[ attrName ].attribute; + + if ( htmlAttribute === 'src' || htmlAttribute === 'href' ) { + updateBoundAttibute( null ); + return; + } + + updateBoundAttibute( placeholder ); + } + + /* + * Block Attribute => Source Prop + * + * Detect changes in block attribute value, + * and update the source prop value accordingly. + */ + if ( attrValue !== lastAttrValue.current && updatePropValue ) { + lastAttrValue.current = attrValue; + updatePropValue( attrValue ); + } + }, [ + updateBoundAttibute, + propValue, + attrValue, + updatePropValue, + placeholder, + blockName, + attrName, + ] ); + + return null; +}; + +const withBlockBindingSupport = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes, name } = props; + + const { getBlockBindingsSource } = unlock( + useSelect( blockEditorStore ) + ); + + // Bail early if there are no bindings. + const bindings = attributes?.metadata?.bindings; + if ( ! bindings ) { + return ; + } + + const BindingConnectorInstances = []; + + Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { + const source = getBlockBindingsSource( settings.source ); + + if ( source ) { + const { useSource } = source; + const attrValue = attributes[ attrName ]; + + // Create a unique key for the connector instance + const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; + + BindingConnectorInstances.push( + + ); + } + } ); + + return ( + <> + { BindingConnectorInstances } + + + ); + }, + 'withBlockBindingSupport' +); + +function extendBlockWithBoundAttributes( settings, name ) { + if ( ! isItPossibleToBindBlock( name ) ) { return settings; } - settings.edit = createEditFunctionWithBindingsAttribute()( settings.edit ); - return settings; + return { + ...settings, + edit: withBlockBindingSupport( settings.edit ), + }; } addFilter( 'blocks.registerBlockType', - 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', - shimAttributeSource + 'core/editor/block-edit-with-binding-attributes', + extendBlockWithBoundAttributes ); From de41aa0b56702ff218d117528b8ffcea8ba383be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 11:50:23 -0300 Subject: [PATCH 37/79] introduce BlockBindingBridge component --- .../src/hooks/use-bindings-attributes.js | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 1e145796f91f46..03ef8ea023f622 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -5,13 +5,13 @@ import { getBlockType } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useEffect, useCallback, useRef } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; -import { useSelect } from '@wordpress/data'; -import { unlock } from '@wordpress/icons'; +import { select } from '@wordpress/data'; /** * Internal dependencies */ import { store as blockEditorStore } from '../store'; +import { unlock } from '../../../editor/src/lock-unlock'; const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], @@ -139,48 +139,58 @@ const BlockBindingConnector = ( { return null; }; -const withBlockBindingSupport = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const { attributes, name } = props; +function BlockBindingBridge( { bindings, props } ) { + if ( ! bindings || Object.keys( bindings ).length === 0 ) { + return null; + } - const { getBlockBindingsSource } = unlock( - useSelect( blockEditorStore ) - ); + const { attributes, name } = props; + const BindingConnectorInstances = []; - // Bail early if there are no bindings. - const bindings = attributes?.metadata?.bindings; - if ( ! bindings ) { - return ; - } + Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { + const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); + const source = getBlockBindingsSource( settings.source ); - const BindingConnectorInstances = []; + if ( ! source ) { + return; + } - Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { - const source = getBlockBindingsSource( settings.source ); + if ( source ) { + const { useSource } = source; + const attrValue = attributes[ attrName ]; + + // Create a unique key for the connector instance + const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; + + BindingConnectorInstances.push( + + ); + } + } ); - if ( source ) { - const { useSource } = source; - const attrValue = attributes[ attrName ]; + return BindingConnectorInstances; +} - // Create a unique key for the connector instance - const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; +const withBlockBindingSupport = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes } = props; - BindingConnectorInstances.push( - - ); - } - } ); + // Bail early if the block doesn't have bindings. + const bindings = attributes?.metadata?.bindings; + if ( ! bindings || Object.keys( bindings ).length === 0 ) { + return null; + } return ( <> - { BindingConnectorInstances } + ); From c1119b5f3f5a47b66517ca32275edd50207aec32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 11:51:07 -0300 Subject: [PATCH 38/79] update isItPossibleToBindBlock() import path --- packages/block-editor/src/components/rich-text/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 8d9074a24efd9a..0f9e934c0301b2 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -47,7 +47,7 @@ import { getAllowedFormats } from './utils'; import { Content } from './content'; import { withDeprecations } from './with-deprecations'; import { unlock } from '../../lock-unlock'; -import { isItPossibleToBindBlock } from '../../hooks/block-binding-support'; +import { isItPossibleToBindBlock } from '../../hooks/use-bindings-attributes'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); From 52c45e1501f443f322a7827133f3cc3db97806ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 12:56:17 -0300 Subject: [PATCH 39/79] introduce hasPossibleBlockBinding() helper --- .../src/hooks/use-bindings-attributes.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 03ef8ea023f622..90d96c86556f2f 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -24,6 +24,13 @@ export function isItPossibleToBindBlock( blockName ) { return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; } +function hasPossibleBlockBinding( blockName, attribute ) { + return ( + isItPossibleToBindBlock( blockName ) && + BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attribute ) + ); +} + /** * This component is responsible detecting and * propagating data changes between block attribute and @@ -148,6 +155,11 @@ function BlockBindingBridge( { bindings, props } ) { const BindingConnectorInstances = []; Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { + // Check if the block attribute can be bound. + if ( ! hasPossibleBlockBinding( name, attrName ) ) { + return; + } + const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); const source = getBlockBindingsSource( settings.source ); From bdc0b82f99afa73d9e37f7d8f14239f9753e505e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 16:27:06 -0300 Subject: [PATCH 40/79] use hooks API to extened blocks with bound attts --- packages/block-editor/src/hooks/index.js | 6 ++- .../src/hooks/use-bindings-attributes.js | 51 ++++--------------- packages/block-editor/src/hooks/utils.js | 20 ++++++++ 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 36efe3dcf409b5..aaae049de5cb33 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -28,7 +28,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; -import './use-bindings-attributes'; +import blockBinding from './use-bindings-attributes'; createBlockEditFilter( [ @@ -42,7 +42,11 @@ createBlockEditFilter( contentLockUI, blockHooks, blockRenaming, +<<<<<<< HEAD childLayout, +======= + blockBinding, +>>>>>>> 730c3930af (use hooks API to extened blocks with bound attts) ].filter( Boolean ) ); createBlockListBlockFilter( [ diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 90d96c86556f2f..4fefbbc3bb7e80 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -2,9 +2,7 @@ * WordPress dependencies */ import { getBlockType } from '@wordpress/blocks'; -import { createHigherOrderComponent } from '@wordpress/compose'; import { useEffect, useCallback, useRef } from '@wordpress/element'; -import { addFilter } from '@wordpress/hooks'; import { select } from '@wordpress/data'; /** @@ -146,12 +144,13 @@ const BlockBindingConnector = ( { return null; }; -function BlockBindingBridge( { bindings, props } ) { +function BlockBindingBridge( props ) { + const bindings = props?.metadata?.bindings; if ( ! bindings || Object.keys( bindings ).length === 0 ) { return null; } - const { attributes, name } = props; + const { name } = props; const BindingConnectorInstances = []; Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { @@ -162,14 +161,13 @@ function BlockBindingBridge( { bindings, props } ) { const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); const source = getBlockBindingsSource( settings.source ); - if ( ! source ) { return; } if ( source ) { const { useSource } = source; - const attrValue = attributes[ attrName ]; + const attrValue = settings.source; // Create a unique key for the connector instance const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; @@ -190,39 +188,8 @@ function BlockBindingBridge( { bindings, props } ) { return BindingConnectorInstances; } -const withBlockBindingSupport = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const { attributes } = props; - - // Bail early if the block doesn't have bindings. - const bindings = attributes?.metadata?.bindings; - if ( ! bindings || Object.keys( bindings ).length === 0 ) { - return null; - } - - return ( - <> - - - - ); - }, - 'withBlockBindingSupport' -); - -function extendBlockWithBoundAttributes( settings, name ) { - if ( ! isItPossibleToBindBlock( name ) ) { - return settings; - } - - return { - ...settings, - edit: withBlockBindingSupport( settings.edit ), - }; -} - -addFilter( - 'blocks.registerBlockType', - 'core/editor/block-edit-with-binding-attributes', - extendBlockWithBoundAttributes -); +export default { + edit: BlockBindingBridge, + attributeKeys: [ 'metadata' ], + hasSupport: isItPossibleToBindBlock, +}; diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index fbe84514c3e53c..38bbc463573319 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -453,6 +453,26 @@ export function createBlockEditFilter( features ) { } } + /* + * "metadata" attribute is a special case. + */ + if ( props.attributes.metadata ) { + const bindings = { + ...props.attributes.metadata.bindings, + }; + + Object.entries( bindings ).forEach( + ( [ attrName ] ) => { + bindings[ attrName ].value = + props.attributes[ attrName ]; + } + ); + + neededProps.metadata = { bindings }; + // @todo: grab it from the React context. + neededProps.context = props.context; + } + return ( Date: Thu, 15 Feb 2024 17:19:47 -0300 Subject: [PATCH 41/79] fix propagating attr value. jsdoc --- .../src/hooks/use-bindings-attributes.js | 2 +- packages/block-editor/src/hooks/utils.js | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 4fefbbc3bb7e80..56796c558c0a9b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -167,7 +167,7 @@ function BlockBindingBridge( props ) { if ( source ) { const { useSource } = source; - const attrValue = settings.source; + const attrValue = settings.value; // Create a unique key for the connector instance const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 38bbc463573319..5294b85aa48900 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -455,6 +455,32 @@ export function createBlockEditFilter( features ) { /* * "metadata" attribute is a special case. + * It has the following structure: + * + * metadata: { + * bindings: { + * : { + * source: , + * key: , + * } + * } + * } + * + * When the feature has a "metadata" attribute, we need to + * pass the "metadata" attribute to the Edit component, + * but also the bound attributes (). + * Additionally, we populate the bound attribute object + * with the attribute value. Thus: + * + * metadata: { + * bindings: { + * : { + * source: , + * key: , + * value: + * } + * } + * } */ if ( props.attributes.metadata ) { const bindings = { @@ -469,6 +495,7 @@ export function createBlockEditFilter( features ) { ); neededProps.metadata = { bindings }; + // @todo: grab it from the React context. neededProps.context = props.context; } From b944f21edbd194e5fe48ea3f9eca3fb9c790a181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 15 Feb 2024 17:25:49 -0300 Subject: [PATCH 42/79] minor changes --- packages/block-editor/src/hooks/use-bindings-attributes.js | 5 +---- 1 file changed, 1 insertion(+), 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 56796c558c0a9b..a68ea48673e96b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -169,12 +169,9 @@ function BlockBindingBridge( props ) { const { useSource } = source; const attrValue = settings.value; - // Create a unique key for the connector instance - const key = `${ settings.source }-${ name }-${ attrName }-${ i }`; - BindingConnectorInstances.push( Date: Mon, 19 Feb 2024 17:22:23 -0300 Subject: [PATCH 43/79] minor code enhancement --- .../src/hooks/use-bindings-attributes.js | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index a68ea48673e96b..1074771be6f8e8 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -1,9 +1,9 @@ /** * WordPress dependencies */ -import { getBlockType } from '@wordpress/blocks'; import { useEffect, useCallback, useRef } from '@wordpress/element'; import { select } from '@wordpress/data'; +import { getBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -165,21 +165,19 @@ function BlockBindingBridge( props ) { return; } - if ( source ) { - const { useSource } = source; - const attrValue = settings.value; - - BindingConnectorInstances.push( - - ); - } + const { useSource } = source; + const attrValue = settings.value; + + BindingConnectorInstances.push( + + ); } ); return BindingConnectorInstances; From e6cf0e3dbd910c3a6bb90c5dae37cda2085c854b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 19 Feb 2024 19:16:08 -0300 Subject: [PATCH 44/79] not edit bound prop for now --- .../src/hooks/use-bindings-attributes.js | 163 ++++++++++-------- 1 file changed, 94 insertions(+), 69 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 1074771be6f8e8..0c2f1ae3a1a450 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -1,9 +1,11 @@ /** * WordPress dependencies */ -import { useEffect, useCallback, useRef } from '@wordpress/element'; -import { select } from '@wordpress/data'; import { getBlockType } from '@wordpress/blocks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { select } from '@wordpress/data'; +import { useEffect, useCallback } from '@wordpress/element'; +import { addFilter } from '@wordpress/hooks'; /** * Internal dependencies @@ -11,6 +13,16 @@ import { getBlockType } from '@wordpress/blocks'; import { store as blockEditorStore } from '../store'; import { unlock } from '../../../editor/src/lock-unlock'; +/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ +/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ + +/** + * Given a binding of block attributes, returns a higher order component that + * overrides its `attributes` and `setAttributes` props to sync any changes needed. + * + * @return {WPHigherOrderComponent} Higher-order component. + */ + const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/paragraph': [ 'content' ], 'core/heading': [ 'content' ], @@ -18,14 +30,29 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/button': [ 'url', 'text', 'linkTarget' ], }; +/** + * Based on the given block name, + * check if it is possible to bind the block. + * + * @param {string} blockName - The block name. + * @return {boolean} Whether it is possible to bind the block attribute. + */ export function isItPossibleToBindBlock( blockName ) { return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; } -function hasPossibleBlockBinding( blockName, attribute ) { +/** + * Based on the given block name and attribute name, + * check if it is possible to bind the block. + * + * @param {string} blockName - The block name. + * @param {string} attributeName - The attribute name. + * @return {boolean} Whether it is possible to bind the block attribute. + */ +export function hasPossibleBlockBinding( blockName, attributeName ) { return ( isItPossibleToBindBlock( blockName ) && - BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attribute ) + BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) ); } @@ -52,11 +79,7 @@ const BlockBindingConnector = ( { blockProps, useSource, } ) => { - const { - placeholder, - value: propValue, - updateValue: updatePropValue, - } = useSource( blockProps, args ); + const { placeholder, value: propValue } = useSource( blockProps, args ); const blockName = blockProps.name; @@ -71,10 +94,6 @@ const BlockBindingConnector = ( { [ attrName, setAttributes ] ); - // Store a reference to the last value and attribute value. - const lastPropValue = useRef( propValue ); - const lastAttrValue = useRef( attrValue ); - /* * Initially sync (first render / onMount ) attribute * value with the source prop value. @@ -84,32 +103,10 @@ const BlockBindingConnector = ( { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ updateBoundAttibute ] ); - /* - * Sync data. - * This effect will run every time - * the attribute value or the prop value changes. - * It will sync them in both directions. - */ useEffect( () => { - /* - * Source Prop => Block Attribute - * - * Detect changes in source prop value, - * and update the attribute value accordingly. - */ if ( typeof propValue !== 'undefined' ) { - if ( propValue !== lastPropValue.current ) { - lastPropValue.current = propValue; - updateBoundAttibute( propValue ); - return; - } + updateBoundAttibute( propValue ); } else if ( placeholder ) { - /* - * If the attribute is `src` or `href`, - * a placeholder can't be used because it is not a valid url. - * Adding this workaround until - * attributes and metadata fields types are improved and include `url`. - */ const htmlAttribute = getBlockType( blockName ).attributes[ attrName ].attribute; @@ -120,22 +117,10 @@ const BlockBindingConnector = ( { updateBoundAttibute( placeholder ); } - - /* - * Block Attribute => Source Prop - * - * Detect changes in block attribute value, - * and update the source prop value accordingly. - */ - if ( attrValue !== lastAttrValue.current && updatePropValue ) { - lastAttrValue.current = attrValue; - updatePropValue( attrValue ); - } }, [ updateBoundAttibute, propValue, attrValue, - updatePropValue, placeholder, blockName, attrName, @@ -144,9 +129,8 @@ const BlockBindingConnector = ( { return null; }; -function BlockBindingBridge( props ) { - const bindings = props?.metadata?.bindings; - if ( ! bindings || Object.keys( bindings ).length === 0 ) { +function BlockBindingBridge( { bindings, props } ) { + if ( ! bindings ) { return null; } @@ -165,26 +149,67 @@ function BlockBindingBridge( props ) { return; } - const { useSource } = source; - const attrValue = settings.value; - - BindingConnectorInstances.push( - - ); + if ( source ) { + const { useSource } = source; + const attrValue = settings.value; + + BindingConnectorInstances.push( + + ); + } } ); return BindingConnectorInstances; } -export default { - edit: BlockBindingBridge, - attributeKeys: [ 'metadata' ], - hasSupport: isItPossibleToBindBlock, -}; +const withBlockBindingSupport = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes } = props; + + // Bail early if the block doesn't have bindings. + const bindings = attributes?.metadata?.bindings; + if ( ! bindings ) { + return null; + } + + return ( + <> + + + + ); + }, + 'withBlockBindingSupport' +); + +/** + * Filters a registered block's settings to enhance a block's `edit` component + * to upgrade bound attributes. + * + * @param {WPBlockSettings} settings - Registered block settings. + * @param {string} name - Block name. + * @return {WPBlockSettings} Filtered block settings. + */ +function shimAttributeSource( settings, name ) { + if ( ! isItPossibleToBindBlock( name ) ) { + return settings; + } + + return { + ...settings, + edit: withBlockBindingSupport( settings.edit ), + }; +} + +addFilter( + 'blocks.registerBlockType', + 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', + shimAttributeSource +); From 8f6906282439469653bcd8f8e5bec04cdf66aa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 19 Feb 2024 19:24:19 -0300 Subject: [PATCH 45/79] jsdoc --- .../src/hooks/use-bindings-attributes.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 0c2f1ae3a1a450..0fb0d66f19425e 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -94,18 +94,16 @@ const BlockBindingConnector = ( { [ attrName, setAttributes ] ); - /* - * Initially sync (first render / onMount ) attribute - * value with the source prop value. - */ - useEffect( () => { - updateBoundAttibute( propValue ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ updateBoundAttibute ] ); - useEffect( () => { if ( typeof propValue !== 'undefined' ) { updateBoundAttibute( propValue ); + + /* + * If the attribute is `src` or `href`, + * a placeholder can't be used because it is not a valid url. + * Adding this workaround until + * attributes and metadata fields types are improved and include `url`. + */ } else if ( placeholder ) { const htmlAttribute = getBlockType( blockName ).attributes[ attrName ].attribute; From ff76966f1b19ac06718dba4c6cff4e66191912c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 19 Feb 2024 19:27:11 -0300 Subject: [PATCH 46/79] revert using hooks API to extrend block --- packages/block-editor/src/hooks/index.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index aaae049de5cb33..36efe3dcf409b5 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -28,7 +28,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; -import blockBinding from './use-bindings-attributes'; +import './use-bindings-attributes'; createBlockEditFilter( [ @@ -42,11 +42,7 @@ createBlockEditFilter( contentLockUI, blockHooks, blockRenaming, -<<<<<<< HEAD childLayout, -======= - blockBinding, ->>>>>>> 730c3930af (use hooks API to extened blocks with bound attts) ].filter( Boolean ) ); createBlockListBlockFilter( [ From 8b135baa8d09e28acb2e1cb89e4c5a71d9d20cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 19 Feb 2024 19:28:30 -0300 Subject: [PATCH 47/79] jsdoc --- packages/block-editor/src/hooks/use-bindings-attributes.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 0fb0d66f19425e..09d65d39f44309 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -58,11 +58,7 @@ export function hasPossibleBlockBinding( blockName, attributeName ) { /** * This component is responsible detecting and - * propagating data changes between block attribute and - * the block-binding source property. - * - * The app creates an instance of this component for each - * pair of block-attribute/source-property. + * propagating data changes from the source to the block. * * @param {Object} props - The component props. * @param {string} props.attrName - The attribute name. From df35039e76fb2499d3d883ffd60a001e18ee6a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 19 Feb 2024 19:30:41 -0300 Subject: [PATCH 48/79] update internal path --- packages/block-editor/src/hooks/use-bindings-attributes.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 09d65d39f44309..b85365e01c6b7b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -11,7 +11,7 @@ import { addFilter } from '@wordpress/hooks'; * Internal dependencies */ import { store as blockEditorStore } from '../store'; -import { unlock } from '../../../editor/src/lock-unlock'; +import { unlock } from '../lock-unlock'; /** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ /** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ @@ -80,7 +80,6 @@ const BlockBindingConnector = ( { const blockName = blockProps.name; const setAttributes = blockProps.setAttributes; - const updateBoundAttibute = useCallback( ( newAttrValue ) => { setAttributes( { From d84bc0a82854856d2bbedf64b0f9686fbc570ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 19 Feb 2024 19:31:59 -0300 Subject: [PATCH 49/79] rollback hook utils chnages --- packages/block-editor/src/hooks/utils.js | 47 ------------------------ 1 file changed, 47 deletions(-) diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 5294b85aa48900..fbe84514c3e53c 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -453,53 +453,6 @@ export function createBlockEditFilter( features ) { } } - /* - * "metadata" attribute is a special case. - * It has the following structure: - * - * metadata: { - * bindings: { - * : { - * source: , - * key: , - * } - * } - * } - * - * When the feature has a "metadata" attribute, we need to - * pass the "metadata" attribute to the Edit component, - * but also the bound attributes (). - * Additionally, we populate the bound attribute object - * with the attribute value. Thus: - * - * metadata: { - * bindings: { - * : { - * source: , - * key: , - * value: - * } - * } - * } - */ - if ( props.attributes.metadata ) { - const bindings = { - ...props.attributes.metadata.bindings, - }; - - Object.entries( bindings ).forEach( - ( [ attrName ] ) => { - bindings[ attrName ].value = - props.attributes[ attrName ]; - } - ); - - neededProps.metadata = { bindings }; - - // @todo: grab it from the React context. - neededProps.context = props.context; - } - return ( Date: Tue, 20 Feb 2024 09:01:53 -0300 Subject: [PATCH 50/79] tidy --- .../src/hooks/use-bindings-attributes.js | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index b85365e01c6b7b..627bdf36c7a7f3 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -60,46 +60,48 @@ export function hasPossibleBlockBinding( blockName, attributeName ) { * This component is responsible detecting and * propagating data changes from the source to the block. * - * @param {Object} props - The component props. - * @param {string} props.attrName - The attribute name. - * @param {any} props.attrValue - The attribute value. - * @param {Function} props.useSource - The custom hook to use the source. - * @param {Object} props.blockProps - The block props with bound attribute. - * @param {Object} props.args - The arguments to pass to the source. - * @return {null} This is a data-handling component. Render nothing. + * @param {Object} props - The component props. + * @param {string} props.attrName - The attribute name. + * @param {any} props.attrValue - The attribute value. + * @param {Object} props.blockProps - The block props with bound attribute. + * @param {Object} props.source - Source handler. + * @param {Object} props.args - The arguments to pass to the source. + * @return {null} This is a data-handling component. Render nothing. */ const BlockBindingConnector = ( { args, attrName, attrValue, blockProps, - useSource, + source, } ) => { - const { placeholder, value: propValue } = useSource( blockProps, args ); + const { placeholder, value: propValue } = source.useSource( + blockProps, + args + ); const blockName = blockProps.name; const setAttributes = blockProps.setAttributes; const updateBoundAttibute = useCallback( - ( newAttrValue ) => { + ( newAttrValue ) => setAttributes( { [ attrName ]: newAttrValue, - } ); - }, + } ), [ attrName, setAttributes ] ); useEffect( () => { if ( typeof propValue !== 'undefined' ) { updateBoundAttibute( propValue ); - + } else if ( placeholder ) { /* + * Placeholder fallback. * If the attribute is `src` or `href`, * a placeholder can't be used because it is not a valid url. * Adding this workaround until * attributes and metadata fields types are improved and include `url`. */ - } else if ( placeholder ) { const htmlAttribute = getBlockType( blockName ).attributes[ attrName ].attribute; @@ -127,36 +129,35 @@ function BlockBindingBridge( { bindings, props } ) { return null; } - const { name } = props; + const { name, attributes } = props; + + // Collect all the binding connectors. const BindingConnectorInstances = []; - Object.entries( bindings ).forEach( ( [ attrName, settings ], i ) => { + Object.entries( bindings ).forEach( ( [ attrName, boundAttribute ], i ) => { // Check if the block attribute can be bound. if ( ! hasPossibleBlockBinding( name, attrName ) ) { return; } const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); - const source = getBlockBindingsSource( settings.source ); + + // Bail early if the block doesn't have a valid source handler. + const source = getBlockBindingsSource( boundAttribute.source ); if ( ! source ) { return; } - if ( source ) { - const { useSource } = source; - const attrValue = settings.value; - - BindingConnectorInstances.push( - - ); - } + BindingConnectorInstances.push( + + ); } ); return BindingConnectorInstances; From 2c8edfbd3ce894174925a8e0be63f53d5c74b80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 20 Feb 2024 11:44:17 -0300 Subject: [PATCH 51/79] wrap Connector instances with a Fragment --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 627bdf36c7a7f3..46ea978e9a46e7 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -160,7 +160,7 @@ function BlockBindingBridge( { bindings, props } ) { ); } ); - return BindingConnectorInstances; + return <>{ BindingConnectorInstances }; } const withBlockBindingSupport = createHigherOrderComponent( From 5b390249fd172e0db7493eb7c3c021ae3b7c2528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 20 Feb 2024 11:51:26 -0300 Subject: [PATCH 52/79] return original Edit instance when no bindings --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 46ea978e9a46e7..dfb7b72ca910e6 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -170,7 +170,7 @@ const withBlockBindingSupport = createHigherOrderComponent( // Bail early if the block doesn't have bindings. const bindings = attributes?.metadata?.bindings; if ( ! bindings ) { - return null; + return ; } return ( From c86f3dc5a1945deb0c899b96315060a9cd79dbef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 20 Feb 2024 13:19:56 -0300 Subject: [PATCH 53/79] check whether useSource is defined --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index dfb7b72ca910e6..f1ec4d66c52d68 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -144,7 +144,7 @@ function BlockBindingBridge( { bindings, props } ) { // Bail early if the block doesn't have a valid source handler. const source = getBlockBindingsSource( boundAttribute.source ); - if ( ! source ) { + if ( ! source?.useSource ) { return; } From 967efaa3ee343f2e3cc1b47229627d4f149ec78c Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 20 Feb 2024 17:04:20 +0000 Subject: [PATCH 54/79] Use `useSelect` and move it out of the for loop --- .../src/hooks/use-bindings-attributes.js | 13 +++++++++---- 1 file changed, 9 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 f1ec4d66c52d68..328ede7af35dfa 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -3,7 +3,7 @@ */ import { getBlockType } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { select } from '@wordpress/data'; +import { select, useSelect } from '@wordpress/data'; import { useEffect, useCallback } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; @@ -57,7 +57,7 @@ export function hasPossibleBlockBinding( blockName, attributeName ) { } /** - * This component is responsible detecting and + * This component is responsible for detecting and * propagating data changes from the source to the block. * * @param {Object} props - The component props. @@ -125,6 +125,13 @@ const BlockBindingConnector = ( { }; function BlockBindingBridge( { bindings, props } ) { + const { getBlockBindingsSource } = useSelect( () => { + return { + getBlockBindingsSource: unlock( select( blockEditorStore ) ) + .getBlockBindingsSource, + }; + }, [] ); + if ( ! bindings ) { return null; } @@ -140,8 +147,6 @@ function BlockBindingBridge( { bindings, props } ) { return; } - const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); - // Bail early if the block doesn't have a valid source handler. const source = getBlockBindingsSource( boundAttribute.source ); if ( ! source?.useSource ) { From 314339232fbd0818f2b917cb4d0aac949cb7cfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 20 Feb 2024 17:59:57 -0300 Subject: [PATCH 55/79] check attr value type --- .../src/hooks/use-bindings-attributes.js | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 328ede7af35dfa..ebaab00e2f8bcb 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -6,6 +6,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; import { select, useSelect } from '@wordpress/data'; import { useEffect, useCallback } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; +import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies @@ -83,12 +84,30 @@ const BlockBindingConnector = ( { const blockName = blockProps.name; const setAttributes = blockProps.setAttributes; + const updateBoundAttibute = useCallback( - ( newAttrValue ) => + ( newAttrValue ) => { + /* + * If the attribute is a RichTextData instance, + * (core/paragraph, core/heading, etc.) + * convert it to HTML string and compare with the new value. + * If they are the same, don't update the attribute. + * + * To do: it looks like a workaround. + * Consider improving the attribute and metadata fields types. + */ + if ( + attrValue instanceof RichTextData && + attrValue.toHTMLString() === newAttrValue + ) { + return; + } + setAttributes( { [ attrName ]: newAttrValue, - } ), - [ attrName, setAttributes ] + } ); + }, + [ attrName, attrValue, setAttributes ] ); useEffect( () => { From f0af98577258a180d8770357f7fe59f718b14fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Feb 2024 08:53:08 -0300 Subject: [PATCH 56/79] iterare when creating BindingConnector instances --- .../src/hooks/use-bindings-attributes.js | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index ebaab00e2f8bcb..10809eb6f2e9b3 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -69,7 +69,7 @@ export function hasPossibleBlockBinding( blockName, attributeName ) { * @param {Object} props.args - The arguments to pass to the source. * @return {null} This is a data-handling component. Render nothing. */ -const BlockBindingConnector = ( { +const BindingConnector = ( { args, attrName, attrValue, @@ -157,34 +157,37 @@ function BlockBindingBridge( { bindings, props } ) { const { name, attributes } = props; - // Collect all the binding connectors. - const BindingConnectorInstances = []; - - Object.entries( bindings ).forEach( ( [ attrName, boundAttribute ], i ) => { - // Check if the block attribute can be bound. - if ( ! hasPossibleBlockBinding( name, attrName ) ) { - return; - } - - // Bail early if the block doesn't have a valid source handler. - const source = getBlockBindingsSource( boundAttribute.source ); - if ( ! source?.useSource ) { - return; - } - - BindingConnectorInstances.push( - - ); - } ); - - return <>{ BindingConnectorInstances }; + return ( + <> + { Object.entries( bindings ).map( + ( [ attrName, boundAttribute ], i ) => { + // Check if the block attribute can be bound. + if ( ! hasPossibleBlockBinding( name, attrName ) ) { + return null; + } + + // Bail early if the block doesn't have a valid source handler. + const source = getBlockBindingsSource( + boundAttribute.source + ); + if ( ! source?.useSource ) { + return null; + } + + return ( + + ); + } + ) } + + ); } const withBlockBindingSupport = createHigherOrderComponent( From 67dd86a9ddad969cbe3763ee5e2eeac56477ee8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Feb 2024 09:06:49 -0300 Subject: [PATCH 57/79] rename helper functions --- .../block-editor/src/components/rich-text/index.js | 4 ++-- .../block-editor/src/hooks/use-bindings-attributes.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 0f9e934c0301b2..7236e74b2f6d68 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -47,7 +47,7 @@ import { getAllowedFormats } from './utils'; import { Content } from './content'; import { withDeprecations } from './with-deprecations'; import { unlock } from '../../lock-unlock'; -import { isItPossibleToBindBlock } from '../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../hooks/use-bindings-attributes'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -161,7 +161,7 @@ export function RichTextWrapper( ( select ) => { // Disable Rich Text editing if block bindings specify that. let _disableBoundBlocks = false; - if ( blockBindings && isItPossibleToBindBlock( blockName ) ) { + if ( blockBindings && canBindBlock( blockName ) ) { const blockTypeAttributes = getBlockType( blockName ).attributes; const { getBlockBindingsSource } = unlock( diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 10809eb6f2e9b3..37e69060610f0d 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -38,7 +38,7 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { * @param {string} blockName - The block name. * @return {boolean} Whether it is possible to bind the block attribute. */ -export function isItPossibleToBindBlock( blockName ) { +export function canBindBlock( blockName ) { return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; } @@ -50,9 +50,9 @@ export function isItPossibleToBindBlock( blockName ) { * @param {string} attributeName - The attribute name. * @return {boolean} Whether it is possible to bind the block attribute. */ -export function hasPossibleBlockBinding( blockName, attributeName ) { +export function canBindAttribute( blockName, attributeName ) { return ( - isItPossibleToBindBlock( blockName ) && + canBindBlock( blockName ) && BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) ); } @@ -162,7 +162,7 @@ function BlockBindingBridge( { bindings, props } ) { { Object.entries( bindings ).map( ( [ attrName, boundAttribute ], i ) => { // Check if the block attribute can be bound. - if ( ! hasPossibleBlockBinding( name, attrName ) ) { + if ( ! canBindAttribute( name, attrName ) ) { return null; } @@ -219,7 +219,7 @@ const withBlockBindingSupport = createHigherOrderComponent( * @return {WPBlockSettings} Filtered block settings. */ function shimAttributeSource( settings, name ) { - if ( ! isItPossibleToBindBlock( name ) ) { + if ( ! canBindBlock( name ) ) { return settings; } From fed0e872fab0322dcebe609b3053cea6dc63ae78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Feb 2024 09:11:07 -0300 Subject: [PATCH 58/79] use useSelect to get binding sources --- packages/block-editor/src/hooks/use-bindings-attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 37e69060610f0d..913e287b5afed9 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -3,7 +3,7 @@ */ import { getBlockType } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { select, useSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { useEffect, useCallback } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { RichTextData } from '@wordpress/rich-text'; @@ -144,7 +144,7 @@ const BindingConnector = ( { }; function BlockBindingBridge( { bindings, props } ) { - const { getBlockBindingsSource } = useSelect( () => { + const { getBlockBindingsSource } = useSelect( ( select ) => { return { getBlockBindingsSource: unlock( select( blockEditorStore ) ) .getBlockBindingsSource, From 91265585290b71a4baaffe8e969259094166cdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Feb 2024 10:14:00 -0300 Subject: [PATCH 59/79] Update packages/block-editor/src/hooks/use-bindings-attributes.js Co-authored-by: Michal --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 913e287b5afed9..b52b15529e82df 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -36,7 +36,7 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { * check if it is possible to bind the block. * * @param {string} blockName - The block name. - * @return {boolean} Whether it is possible to bind the block attribute. + * @return {boolean} Whether it is possible to bind the block to sources. */ export function canBindBlock( blockName ) { return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; From 5179ad0a9c80a77468bdcdb5d8ab62385985974d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Feb 2024 10:14:16 -0300 Subject: [PATCH 60/79] Update packages/block-editor/src/hooks/use-bindings-attributes.js Co-authored-by: Michal --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index b52b15529e82df..2e397d1a81c210 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -44,7 +44,7 @@ export function canBindBlock( blockName ) { /** * Based on the given block name and attribute name, - * check if it is possible to bind the block. + * check if it is possible to bind the block attribute. * * @param {string} blockName - The block name. * @param {string} attributeName - The attribute name. From 9837379a3aab1b52137707053d04610237d5d22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Feb 2024 13:52:50 -0300 Subject: [PATCH 61/79] pass prev attr value to compare --- .../src/hooks/use-bindings-attributes.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 2e397d1a81c210..9b8fb4dfff055b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -86,33 +86,29 @@ const BindingConnector = ( { const setAttributes = blockProps.setAttributes; const updateBoundAttibute = useCallback( - ( newAttrValue ) => { + ( newAttrValue, prevAttrValue ) => { /* * If the attribute is a RichTextData instance, * (core/paragraph, core/heading, etc.) - * convert it to HTML string and compare with the new value. - * If they are the same, don't update the attribute. + * convert it to HTML string. * * To do: it looks like a workaround. * Consider improving the attribute and metadata fields types. */ - if ( - attrValue instanceof RichTextData && - attrValue.toHTMLString() === newAttrValue - ) { - return; + if ( prevAttrValue instanceof RichTextData ) { + prevAttrValue = prevAttrValue.toHTMLString(); } setAttributes( { [ attrName ]: newAttrValue, } ); }, - [ attrName, attrValue, setAttributes ] + [ attrName, setAttributes ] ); useEffect( () => { if ( typeof propValue !== 'undefined' ) { - updateBoundAttibute( propValue ); + updateBoundAttibute( propValue, attrValue ); } else if ( placeholder ) { /* * Placeholder fallback. From cdbbda708460569c1df72dc8c7a198f6eb1244f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Feb 2024 14:25:12 -0300 Subject: [PATCH 62/79] improve binding allowed block attributes --- .../src/hooks/use-bindings-attributes.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 9b8fb4dfff055b..ea3b9a09e771b8 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -147,21 +147,12 @@ function BlockBindingBridge( { bindings, props } ) { }; }, [] ); - if ( ! bindings ) { - return null; - } - const { name, attributes } = props; return ( <> { Object.entries( bindings ).map( ( [ attrName, boundAttribute ], i ) => { - // Check if the block attribute can be bound. - if ( ! canBindAttribute( name, attrName ) ) { - return null; - } - // Bail early if the block doesn't have a valid source handler. const source = getBlockBindingsSource( boundAttribute.source @@ -190,8 +181,17 @@ const withBlockBindingSupport = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { const { attributes } = props; - // Bail early if the block doesn't have bindings. - const bindings = attributes?.metadata?.bindings; + /* + * Create binding object filtering + * only the attributes that can be bound. + */ + const bindings = Object.fromEntries( + Object.entries( attributes.metadata?.bindings || {} ).filter( + ( [ attrName ] ) => canBindAttribute( props.name, attrName ) + ) + ); + + // If the block doesn't have any bindings, render the original block edit. if ( ! bindings ) { return ; } From f4906b64a4fead1716f154e2d6762bce5862c29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Feb 2024 16:52:45 -0300 Subject: [PATCH 63/79] sync derevied updates when updating bound attr --- .../src/hooks/use-bindings-attributes.js | 12 ++++++++---- 1 file changed, 8 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 ea3b9a09e771b8..389c93bedeb7d1 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -3,7 +3,7 @@ */ import { getBlockType } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useCallback } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { RichTextData } from '@wordpress/rich-text'; @@ -85,6 +85,8 @@ const BindingConnector = ( { const setAttributes = blockProps.setAttributes; + const { syncDerivedUpdates } = unlock( useDispatch( blockEditorStore ) ); + const updateBoundAttibute = useCallback( ( newAttrValue, prevAttrValue ) => { /* @@ -99,11 +101,13 @@ const BindingConnector = ( { prevAttrValue = prevAttrValue.toHTMLString(); } - setAttributes( { - [ attrName ]: newAttrValue, + syncDerivedUpdates( () => { + setAttributes( { + [ attrName ]: newAttrValue, + } ); } ); }, - [ attrName, setAttributes ] + [ attrName, setAttributes, syncDerivedUpdates ] ); useEffect( () => { From c554dc5bd1746525e60fa34031b0545bfc2a95d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Feb 2024 17:22:11 -0300 Subject: [PATCH 64/79] improve getting attr source --- .../src/hooks/use-bindings-attributes.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 389c93bedeb7d1..c21f5af03bacb7 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -144,12 +144,9 @@ const BindingConnector = ( { }; function BlockBindingBridge( { bindings, props } ) { - const { getBlockBindingsSource } = useSelect( ( select ) => { - return { - getBlockBindingsSource: unlock( select( blockEditorStore ) ) - .getBlockBindingsSource, - }; - }, [] ); + const blockBindingsSources = unlock( + useSelect( blockEditorStore ) + ).getAllBlockBindingsSources(); const { name, attributes } = props; @@ -158,9 +155,8 @@ function BlockBindingBridge( { bindings, props } ) { { Object.entries( bindings ).map( ( [ attrName, boundAttribute ], i ) => { // Bail early if the block doesn't have a valid source handler. - const source = getBlockBindingsSource( - boundAttribute.source - ); + const source = + blockBindingsSources[ boundAttribute.source ]; if ( ! source?.useSource ) { return null; } From 9621b024316ca25dbb0cc2ffa6243202419d370b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Feb 2024 18:01:16 -0300 Subject: [PATCH 65/79] check properly bindings data --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index c21f5af03bacb7..c35fe81159447f 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -192,7 +192,7 @@ const withBlockBindingSupport = createHigherOrderComponent( ); // If the block doesn't have any bindings, render the original block edit. - if ( ! bindings ) { + if ( ! Object.keys( bindings ).length ) { return ; } From eb27c872a18e5561f0174e920cef28c139364e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Fri, 23 Feb 2024 11:03:37 -0300 Subject: [PATCH 66/79] preserve the RichTextData for block attr --- .../src/hooks/use-bindings-attributes.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index c35fe81159447f..32dea023f561b0 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -91,14 +91,27 @@ const BindingConnector = ( { ( newAttrValue, prevAttrValue ) => { /* * If the attribute is a RichTextData instance, - * (core/paragraph, core/heading, etc.) - * convert it to HTML string. + * (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 ) { - prevAttrValue = prevAttrValue.toHTMLString(); + // 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; } syncDerivedUpdates( () => { From fbb24f20d213b907c39c6775fdcb2d9827ea9d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Fri, 23 Feb 2024 11:05:13 -0300 Subject: [PATCH 67/79] comment line just for tesrting purposes --- packages/block-editor/src/store/reducer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 751a19a1c2a8c2..7db1333932b0c0 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -475,7 +475,7 @@ function withPersistentBlockChange( reducer ) { } const isExplicitPersistentChange = - action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || + // action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || @todo: just for testing purposes markNextChangeAsNotPersistent; // Defer to previous state value (or default) unless changing or From 90c828a002ff4ba4b5e6ccdaff1c8384b07c889a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Fri, 23 Feb 2024 11:27:29 -0300 Subject: [PATCH 68/79] rebasing changes --- .../src/hooks/use-bindings-attributes.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 32dea023f561b0..ad4a1e704837be 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { getBlockType } from '@wordpress/blocks'; +import { getBlockType, store as blocksStore } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useCallback } from '@wordpress/element'; @@ -13,6 +13,7 @@ import { RichTextData } from '@wordpress/rich-text'; */ import { store as blockEditorStore } from '../store'; import { unlock } from '../lock-unlock'; +import { useBlockEditContext } from '../components/block-edit/context'; /** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ /** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ @@ -64,6 +65,7 @@ export function canBindAttribute( blockName, attributeName ) { * @param {Object} props - The component props. * @param {string} props.attrName - The attribute name. * @param {any} props.attrValue - The attribute value. + * @param {string} props.blockName - The block name. * @param {Object} props.blockProps - The block props with bound attribute. * @param {Object} props.source - Source handler. * @param {Object} props.args - The arguments to pass to the source. @@ -73,6 +75,7 @@ const BindingConnector = ( { args, attrName, attrValue, + blockName, blockProps, source, } ) => { @@ -81,8 +84,6 @@ const BindingConnector = ( { args ); - const blockName = blockProps.name; - const setAttributes = blockProps.setAttributes; const { syncDerivedUpdates } = unlock( useDispatch( blockEditorStore ) ); @@ -156,13 +157,11 @@ const BindingConnector = ( { return null; }; -function BlockBindingBridge( { bindings, props } ) { +function BlockBindingBridge( { bindings, props, blockName, attributes } ) { const blockBindingsSources = unlock( - useSelect( blockEditorStore ) + useSelect( blocksStore ) ).getAllBlockBindingsSources(); - const { name, attributes } = props; - return ( <> { Object.entries( bindings ).map( @@ -176,7 +175,8 @@ function BlockBindingBridge( { bindings, props } ) { return ( ( props ) => { - const { attributes } = props; + const { clientId, name: blockName } = useBlockEditContext(); + const { getBlockAttributes } = useSelect( blockEditorStore ); /* * Create binding object filtering * only the attributes that can be bound. */ + const attributes = getBlockAttributes( clientId ); const bindings = Object.fromEntries( Object.entries( attributes.metadata?.bindings || {} ).filter( ( [ attrName ] ) => canBindAttribute( props.name, attrName ) @@ -211,7 +213,12 @@ const withBlockBindingSupport = createHigherOrderComponent( return ( <> - + ); From 01ce1807c28b8b83759c5e26bcd5e4b33ea7689a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Fri, 23 Feb 2024 14:30:21 -0300 Subject: [PATCH 69/79] rollback change foir testing purposes --- packages/block-editor/src/store/reducer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 7db1333932b0c0..751a19a1c2a8c2 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -475,7 +475,7 @@ function withPersistentBlockChange( reducer ) { } const isExplicitPersistentChange = - // action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || @todo: just for testing purposes + action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || markNextChangeAsNotPersistent; // Defer to previous state value (or default) unless changing or From f790ca72c900c11333c2719e8be41a0a07f38705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 27 Feb 2024 08:20:06 -0400 Subject: [PATCH 70/79] change cmp prop name. improve jsdoc --- .../src/hooks/use-bindings-attributes.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index ad4a1e704837be..d8742571542027 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -157,7 +157,20 @@ const BindingConnector = ( { return null; }; -function BlockBindingBridge( { bindings, props, blockName, attributes } ) { +/** + * BlockBindingBridge acts like a component wrapper + * that connects the bound attributes of a block + * to the source handlers. + * For this, it creates a BindingConnector for each bound attribute. + * + * @param {Object} props - The component props. + * @param {string} props.blockName - The block name. + * @param {Object} props.blockProps - The BlockEdit props object. + * @param {Object} props.bindings - The block bindings settings. + * @param {Object} props.attributes - The block attributes. + * @return {null} This is a data-handling component. Render nothing. + */ +function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { const blockBindingsSources = unlock( useSelect( blocksStore ) ).getAllBlockBindingsSources(); @@ -180,7 +193,7 @@ function BlockBindingBridge( { bindings, props, blockName, attributes } ) { attrName={ attrName } attrValue={ attributes[ attrName ] } source={ source } - blockProps={ props } + blockProps={ blockProps } args={ boundAttribute.args } /> ); @@ -214,7 +227,7 @@ const withBlockBindingSupport = createHigherOrderComponent( return ( <> Date: Tue, 27 Feb 2024 08:24:48 -0400 Subject: [PATCH 71/79] simplify checking bindins value --- .../src/hooks/use-bindings-attributes.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index d8742571542027..c042153d8d2669 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -219,19 +219,16 @@ const withBlockBindingSupport = createHigherOrderComponent( ) ); - // If the block doesn't have any bindings, render the original block edit. - if ( ! Object.keys( bindings ).length ) { - return ; - } - return ( <> - + { Object.keys( bindings ).length > 0 && ( + + ) } ); From 1762e9c31041fc41987dcdd47b3602700231d5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 27 Feb 2024 08:28:47 -0400 Subject: [PATCH 72/79] use attr name as key instance --- packages/block-editor/src/hooks/use-bindings-attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index c042153d8d2669..5c41714b778759 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -178,7 +178,7 @@ function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { return ( <> { Object.entries( bindings ).map( - ( [ attrName, boundAttribute ], i ) => { + ( [ attrName, boundAttribute ] ) => { // Bail early if the block doesn't have a valid source handler. const source = blockBindingsSources[ boundAttribute.source ]; @@ -188,7 +188,7 @@ function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { return ( Date: Tue, 27 Feb 2024 09:09:00 -0400 Subject: [PATCH 73/79] store bound attrs values in a local state --- .../src/hooks/use-bindings-attributes.js | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 5c41714b778759..3fc7548877e641 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -3,8 +3,8 @@ */ import { getBlockType, store as blocksStore } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect, useCallback } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { useEffect, useCallback, useState } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { RichTextData } from '@wordpress/rich-text'; @@ -62,14 +62,15 @@ export function canBindAttribute( blockName, attributeName ) { * This component is responsible for detecting and * propagating data changes from the source to the block. * - * @param {Object} props - The component props. - * @param {string} props.attrName - The attribute name. - * @param {any} props.attrValue - The attribute value. - * @param {string} props.blockName - The block name. - * @param {Object} props.blockProps - The block props with bound attribute. - * @param {Object} props.source - Source handler. - * @param {Object} props.args - The arguments to pass to the source. - * @return {null} This is a data-handling component. Render nothing. + * @param {Object} props - The component props. + * @param {string} props.attrName - The attribute name. + * @param {any} props.attrValue - The attribute value. + * @param {string} props.blockName - The block name. + * @param {Object} props.blockProps - The block props with bound attribute. + * @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. + * @return {null} Data-handling component. Render nothing. */ const BindingConnector = ( { args, @@ -78,16 +79,13 @@ const BindingConnector = ( { blockName, blockProps, source, + onPropValueChange, } ) => { const { placeholder, value: propValue } = source.useSource( blockProps, args ); - const setAttributes = blockProps.setAttributes; - - const { syncDerivedUpdates } = unlock( useDispatch( blockEditorStore ) ); - const updateBoundAttibute = useCallback( ( newAttrValue, prevAttrValue ) => { /* @@ -115,13 +113,9 @@ const BindingConnector = ( { return; } - syncDerivedUpdates( () => { - setAttributes( { - [ attrName ]: newAttrValue, - } ); - } ); + onPropValueChange?.( { [ attrName ]: newAttrValue } ); }, - [ attrName, setAttributes, syncDerivedUpdates ] + [ attrName, onPropValueChange ] ); useEffect( () => { @@ -163,14 +157,21 @@ const BindingConnector = ( { * to the source handlers. * For this, it creates a BindingConnector for each bound attribute. * - * @param {Object} props - The component props. - * @param {string} props.blockName - The block name. - * @param {Object} props.blockProps - The BlockEdit props object. - * @param {Object} props.bindings - The block bindings settings. - * @param {Object} props.attributes - The block attributes. - * @return {null} This is a data-handling component. Render nothing. + * @param {Object} props - The component props. + * @param {string} props.blockName - The block name. + * @param {Object} props.blockProps - The BlockEdit props object. + * @param {Object} props.bindings - The block bindings settings. + * @param {Object} props.attributes - The block attributes. + * @param {Function} props.onPropValueChange - The function to call when the attribute value changes. + * @return {null} Data-handling component. Render nothing. */ -function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { +function BlockBindingBridge( { + blockName, + blockProps, + bindings, + attributes, + onPropValueChange, +} ) { const blockBindingsSources = unlock( useSelect( blocksStore ) ).getAllBlockBindingsSources(); @@ -195,6 +196,7 @@ function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { source={ source } blockProps={ blockProps } args={ boundAttribute.args } + onPropValueChange={ onPropValueChange } /> ); } @@ -208,6 +210,8 @@ const withBlockBindingSupport = createHigherOrderComponent( const { clientId, name: blockName } = useBlockEditContext(); const { getBlockAttributes } = useSelect( blockEditorStore ); + const [ , setBoundAttributes ] = useState( {} ); + /* * Create binding object filtering * only the attributes that can be bound. @@ -219,6 +223,13 @@ const withBlockBindingSupport = createHigherOrderComponent( ) ); + const updateBoundAttributes = useCallback( ( newAttributes ) => { + setBoundAttributes( ( prev ) => ( { + ...prev, + ...newAttributes, + } ) ); + }, [] ); + return ( <> { Object.keys( bindings ).length > 0 && ( @@ -227,8 +238,10 @@ const withBlockBindingSupport = createHigherOrderComponent( blockName={ blockName } bindings={ bindings } attributes={ attributes } + onPropValueChange={ updateBoundAttributes } /> ) } + ); From 8e2eeaa45205942d7da228f48b2eda418bea6be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 27 Feb 2024 09:13:55 -0400 Subject: [PATCH 74/79] collect and update bound attr in a local state --- .../src/hooks/use-bindings-attributes.js | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 3fc7548877e641..80b0da24435f47 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -210,7 +210,19 @@ const withBlockBindingSupport = createHigherOrderComponent( const { clientId, name: blockName } = useBlockEditContext(); const { getBlockAttributes } = useSelect( blockEditorStore ); - const [ , setBoundAttributes ] = useState( {} ); + /* + * Collect and update the bound attributes + * in a separate state. + */ + const [ boundAttributes, setBoundAttributes ] = useState( {} ); + const updateBoundAttributes = useCallback( + ( newAttributes ) => + setBoundAttributes( ( prev ) => ( { + ...prev, + ...newAttributes, + } ) ), + [] + ); /* * Create binding object filtering @@ -223,13 +235,6 @@ const withBlockBindingSupport = createHigherOrderComponent( ) ); - const updateBoundAttributes = useCallback( ( newAttributes ) => { - setBoundAttributes( ( prev ) => ( { - ...prev, - ...newAttributes, - } ) ); - }, [] ); - return ( <> { Object.keys( bindings ).length > 0 && ( @@ -242,7 +247,10 @@ const withBlockBindingSupport = createHigherOrderComponent( /> ) } - + ); }, From 56a782b4f0b490d5250b6f2ae986ab545759a4d5 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 27 Feb 2024 19:35:46 +0000 Subject: [PATCH 75/79] Refactor block binding functionality from e55f6bc --- .../src/hooks/use-bindings-attributes.js | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 80b0da24435f47..acfa665a750b4a 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -11,9 +11,7 @@ import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies */ -import { store as blockEditorStore } from '../store'; import { unlock } from '../lock-unlock'; -import { useBlockEditContext } from '../components/block-edit/context'; /** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ /** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ @@ -64,8 +62,6 @@ export function canBindAttribute( blockName, attributeName ) { * * @param {Object} props - The component props. * @param {string} props.attrName - The attribute name. - * @param {any} props.attrValue - The attribute value. - * @param {string} props.blockName - The block name. * @param {Object} props.blockProps - The block props with bound attribute. * @param {Object} props.source - Source handler. * @param {Object} props.args - The arguments to pass to the source. @@ -75,8 +71,6 @@ export function canBindAttribute( blockName, attributeName ) { const BindingConnector = ( { args, attrName, - attrValue, - blockName, blockProps, source, onPropValueChange, @@ -86,6 +80,9 @@ const BindingConnector = ( { args ); + const { name: blockName } = blockProps; + const attrValue = blockProps.attributes[ attrName ]; + const updateBoundAttibute = useCallback( ( newAttrValue, prevAttrValue ) => { /* @@ -158,20 +155,12 @@ const BindingConnector = ( { * For this, it creates a BindingConnector for each bound attribute. * * @param {Object} props - The component props. - * @param {string} props.blockName - The block name. * @param {Object} props.blockProps - The BlockEdit props object. * @param {Object} props.bindings - The block bindings settings. - * @param {Object} props.attributes - The block attributes. * @param {Function} props.onPropValueChange - The function to call when the attribute value changes. * @return {null} Data-handling component. Render nothing. */ -function BlockBindingBridge( { - blockName, - blockProps, - bindings, - attributes, - onPropValueChange, -} ) { +function BlockBindingBridge( { blockProps, bindings, onPropValueChange } ) { const blockBindingsSources = unlock( useSelect( blocksStore ) ).getAllBlockBindingsSources(); @@ -190,9 +179,7 @@ function BlockBindingBridge( { return ( ( props ) => { - const { clientId, name: blockName } = useBlockEditContext(); - const { getBlockAttributes } = useSelect( blockEditorStore ); - /* * Collect and update the bound attributes * in a separate state. @@ -228,9 +212,8 @@ const withBlockBindingSupport = createHigherOrderComponent( * Create binding object filtering * only the attributes that can be bound. */ - const attributes = getBlockAttributes( clientId ); const bindings = Object.fromEntries( - Object.entries( attributes.metadata?.bindings || {} ).filter( + Object.entries( props.attributes.metadata?.bindings || {} ).filter( ( [ attrName ] ) => canBindAttribute( props.name, attrName ) ) ); @@ -240,16 +223,14 @@ const withBlockBindingSupport = createHigherOrderComponent( { Object.keys( bindings ).length > 0 && ( ) } ); From 80e81021328b04f9059fa8703a220751370fafdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 27 Feb 2024 21:13:28 -0400 Subject: [PATCH 76/79] pick block data from straight props --- .../src/hooks/use-bindings-attributes.js | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 80b0da24435f47..acfa665a750b4a 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -11,9 +11,7 @@ import { RichTextData } from '@wordpress/rich-text'; /** * Internal dependencies */ -import { store as blockEditorStore } from '../store'; import { unlock } from '../lock-unlock'; -import { useBlockEditContext } from '../components/block-edit/context'; /** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ /** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ @@ -64,8 +62,6 @@ export function canBindAttribute( blockName, attributeName ) { * * @param {Object} props - The component props. * @param {string} props.attrName - The attribute name. - * @param {any} props.attrValue - The attribute value. - * @param {string} props.blockName - The block name. * @param {Object} props.blockProps - The block props with bound attribute. * @param {Object} props.source - Source handler. * @param {Object} props.args - The arguments to pass to the source. @@ -75,8 +71,6 @@ export function canBindAttribute( blockName, attributeName ) { const BindingConnector = ( { args, attrName, - attrValue, - blockName, blockProps, source, onPropValueChange, @@ -86,6 +80,9 @@ const BindingConnector = ( { args ); + const { name: blockName } = blockProps; + const attrValue = blockProps.attributes[ attrName ]; + const updateBoundAttibute = useCallback( ( newAttrValue, prevAttrValue ) => { /* @@ -158,20 +155,12 @@ const BindingConnector = ( { * For this, it creates a BindingConnector for each bound attribute. * * @param {Object} props - The component props. - * @param {string} props.blockName - The block name. * @param {Object} props.blockProps - The BlockEdit props object. * @param {Object} props.bindings - The block bindings settings. - * @param {Object} props.attributes - The block attributes. * @param {Function} props.onPropValueChange - The function to call when the attribute value changes. * @return {null} Data-handling component. Render nothing. */ -function BlockBindingBridge( { - blockName, - blockProps, - bindings, - attributes, - onPropValueChange, -} ) { +function BlockBindingBridge( { blockProps, bindings, onPropValueChange } ) { const blockBindingsSources = unlock( useSelect( blocksStore ) ).getAllBlockBindingsSources(); @@ -190,9 +179,7 @@ function BlockBindingBridge( { return ( ( props ) => { - const { clientId, name: blockName } = useBlockEditContext(); - const { getBlockAttributes } = useSelect( blockEditorStore ); - /* * Collect and update the bound attributes * in a separate state. @@ -228,9 +212,8 @@ const withBlockBindingSupport = createHigherOrderComponent( * Create binding object filtering * only the attributes that can be bound. */ - const attributes = getBlockAttributes( clientId ); const bindings = Object.fromEntries( - Object.entries( attributes.metadata?.bindings || {} ).filter( + Object.entries( props.attributes.metadata?.bindings || {} ).filter( ( [ attrName ] ) => canBindAttribute( props.name, attrName ) ) ); @@ -240,16 +223,14 @@ const withBlockBindingSupport = createHigherOrderComponent( { Object.keys( bindings ).length > 0 && ( ) } ); From fa9ed050deac23cc4138bb17336f980b8eb5d8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 28 Feb 2024 08:53:43 -0400 Subject: [PATCH 77/79] remove conditional onPropValueChange call --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index acfa665a750b4a..2a1fb8f17c11cf 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -110,7 +110,7 @@ const BindingConnector = ( { return; } - onPropValueChange?.( { [ attrName ]: newAttrValue } ); + onPropValueChange( { [ attrName ]: newAttrValue } ); }, [ attrName, onPropValueChange ] ); From b6d14b5797f2f168c97bb5b08f1b4285f415be1a Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 28 Feb 2024 14:22:57 +0100 Subject: [PATCH 78/79] Update e2e tests --- test/e2e/specs/editor/various/block-bindings.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js index 419a70faeaf9be..ca19c06beff01a 100644 --- a/test/e2e/specs/editor/various/block-bindings.spec.js +++ b/test/e2e/specs/editor/various/block-bindings.spec.js @@ -1245,7 +1245,7 @@ test.describe( 'Block bindings', () => { const postId = await editor.publishPost(); await page.goto( `/?p=${ postId }` ); await expect( page.locator( '#paragraph-binding' ) ).toHaveText( - 'non_existing_custom_field' + 'fallback value' ); } ); @@ -1276,7 +1276,7 @@ test.describe( 'Block bindings', () => { const postId = await editor.publishPost(); await page.goto( `/?p=${ postId }` ); await expect( page.locator( '#paragraph-binding' ) ).toHaveText( - '_protected_field' + 'fallback value' ); } ); @@ -1309,7 +1309,7 @@ test.describe( 'Block bindings', () => { const postId = await editor.publishPost(); await page.goto( `/?p=${ postId }` ); await expect( page.locator( '#paragraph-binding' ) ).toHaveText( - 'show_in_rest_false_field' + 'fallback value' ); } ); } ); From 879129cf2143f5a13e03e6aca799d51491fb87be Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 28 Feb 2024 14:34:19 +0100 Subject: [PATCH 79/79] Use `useLayoutEffect` instead of `useEffect` --- packages/block-editor/src/hooks/use-bindings-attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 2a1fb8f17c11cf..5cd8cb46b3b7e7 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -4,7 +4,7 @@ import { getBlockType, store as blocksStore } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; -import { useEffect, useCallback, useState } from '@wordpress/element'; +import { useLayoutEffect, useCallback, useState } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { RichTextData } from '@wordpress/rich-text'; @@ -115,7 +115,7 @@ const BindingConnector = ( { [ attrName, onPropValueChange ] ); - useEffect( () => { + useLayoutEffect( () => { if ( typeof propValue !== 'undefined' ) { updateBoundAttibute( propValue, attrValue ); } else if ( placeholder ) {