From e9a1ddf728071f4b4d1ff8540f1eb9c7d4d9bfb8 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/74] 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 0000000000000..39cfcd2cf7a08 --- /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 0000000000000..040f2a49ab1ef --- /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 5263ca3332b25..79bce267ed893 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 8c20db3ab56a4cb08fabd079fdd277010cd574b8 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/74] 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 040f2a49ab1ef..aa97f4a6ae516 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 31c1becd5b5a110663421e9875f63b21cf7a3bf9 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/74] 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 aa97f4a6ae516..174a085b63dc8 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 8a8a8396aed8d895a6c84ac08a3f22f8df8e1cb8 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/74] 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 174a085b63dc8..d493fb0655351 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 39b7a895e242fa68d069fd37fd523386e740f499 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/74] 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 36efe3dcf409b..237ada8360222 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 9f643e7b122dc22f29b00874ddd7cfa6f907e76f 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/74] 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 458f5a96609b6..77b0ed46550b8 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 f89fc68dc753dad38299c267d2213a58116c1367 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/74] 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 0000000000000..5bcb4ea8e4cd7 --- /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 1977f9980b067..415b5f4fa7ece 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 8ef5a5bb12e886ed7911a6a7ac9e22df2024a904 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/74] 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 415b5f4fa7ece..65715173b79c7 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 b5a86accde025ce03901544c4bd3595d5e51d804 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/74] 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 5bcb4ea8e4cd7..62efe3d8bdf0e 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 71e716c974552946512270e542af9fee795a765b 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/74] 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 62efe3d8bdf0e..fbc927d84f6bf 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 12d5cd41369cf32d335499bf826771f40ffb9777 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/74] 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 fbc927d84f6bf..4eeec2b23168f 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 ebb9bb28662830009ec8e365e4d7aba6c7e1af0d 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/74] 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 0000000000000..0e4e6de09e61f --- /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 65715173b79c7..415b5f4fa7ece 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 4eeec2b23168f..0000000000000 --- 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 14708188b93121f9113f7d7a66efe254b6f34dc0 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/74] 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 0e4e6de09e61f..333789b51aa43 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 64eedc2f141a59ad6cb25bc459e32d2bb08af51e 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/74] 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 d493fb0655351..a3a082d89a0c4 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 333789b51aa43..c485bdd77ee83 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 a2d5cb2c20d34def37d09ad071defee1f8f6e7ea 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/74] 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 c485bdd77ee83..6f0719bbfb0c2 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 4deda605de3c555c6da796611136f576cde5b29a 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/74] 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 6f0719bbfb0c2..6fe2666788f37 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 2510322d72055f6c358288380fa6d6e27ddae35e 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/74] 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 a3a082d89a0c4..caeab7a49b81b 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 6fe2666788f37..919d1c0bc9ed6 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 fce85391adad179854b6d08801dc32903c68a25b 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/74] 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 919d1c0bc9ed6..0000000000000 --- 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 415b5f4fa7ece..1977f9980b067 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 8d71311e215f67a68263d482b219901037469293 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/74] 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 caeab7a49b81b..f26579ec49d10 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 411ede898700c8aa166672873eec5628801ea919 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/74] 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 f26579ec49d10..04accda183456 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 eb7e0329a9cdc08db3d830b16a72e88dba0b90d0 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/74] 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 04accda183456..a652e13273c77 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 b8a90618b04a27cd625d5f24828043cf3ce49ec7 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/74] 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 a652e13273c77..f81627d499dec 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 2ae62f096ef2c1f60498e95e5d32523a75bf9464 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/74] 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 f81627d499dec..c332eee0288e5 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 cc21a162da68cd62db19e062320ae0dd0d3e1901 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/74] 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 c332eee0288e5..7b30f55b366ac 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 396dfb3729baaa9571d0d5fb2ba7370393a5c874 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/74] 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 a9a00599b6803..0d0c737d0eaf7 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 f25f9b8924d834c09625daf91c5b0d81828b62af 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/74] 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 7b30f55b366ac..17c8a524d5f3c 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 f3b3e0508285638bb2da2499c7777a9db07fe69c 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/74] 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 17c8a524d5f3c..5b7befe909631 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 56ae182fcf625801655fd49ace844d813c39c6b7 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/74] 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 5b7befe909631..c5a8df7c6ddc2 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 7de80c3d9fc39f48bac37813a900c9ef90caa9e2 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/74] 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 c5a8df7c6ddc2..a3f3649783d25 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 8866135bb50e30f38e3ebc0f9c1f718de5e65e20 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/74] 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 a3f3649783d25..776feb36d723f 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 42ed184dba6a96a40433aa5abcd5ba9f3d7aba0c 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/74] 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 0d0c737d0eaf7..c2ecdbbd0b310 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 60e73d4706c878c8c39d509fe66caca5585c4f0c 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/74] 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 79bce267ed893..5263ca3332b25 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 77b0ed46550b8..8d9074a24efd9 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 237ada8360222..44c524e1dc86a 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 3c9a341d24cdaef56dbd3cdbbc78b0ea1a2f58e6 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/74] 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 776feb36d723f..573a20840e38a 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 7263d22279c1a86e6b9cca72096b1f1f61f98e26 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/74] 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 39cfcd2cf7a08..f9d2f72906e87 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 d5b5e785561a9a1029a43da0b29390ac19474fb8 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/74] 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 c2ecdbbd0b310..0d0c737d0eaf7 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 7268a558150ada40d4359c1db8cc2b4e66bb479d 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/74] 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 f9d2f72906e87..0000000000000 --- 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 573a20840e38a..0000000000000 --- 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 44c524e1dc86a..36efe3dcf409b 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 0e5b6614f07cb..1e145796f91f4 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 10b7765640960784011bd5b305cff54a1681a754 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/74] 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 1e145796f91f4..03ef8ea023f62 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 472d01eae079fbac7080c435f72d701e62ea37a2 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/74] 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 8d9074a24efd9..0f9e934c0301b 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 37c6ab912ffee613f7b203dc9c74e08206b3126d 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/74] 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 03ef8ea023f62..90d96c86556f2 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 eab050b7884e0f08fc27cce57e17ca04e0324ab8 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/74] 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 36efe3dcf409b..aaae049de5cb3 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 90d96c86556f2..4fefbbc3bb7e8 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 fbe84514c3e53..38bbc46357331 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/74] 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 4fefbbc3bb7e8..56796c558c0a9 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 38bbc46357331..5294b85aa4890 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 28eb1681d2773e26d62ef68f6cbb3284e7301c20 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/74] 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 56796c558c0a9..a68ea48673e96 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/74] 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 a68ea48673e96..1074771be6f8e 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 cb9e3809fb29e6b0cf6176df84c3e1da64b8cd92 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/74] 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 1074771be6f8e..0c2f1ae3a1a45 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 bd9cf18d413b441dcc654236d8bfdb52bc94e69a 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/74] 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 0c2f1ae3a1a45..0fb0d66f19425 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 7ecfab9141e3dac96e9e2cbd56b5505c41347bc1 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/74] 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 aaae049de5cb3..36efe3dcf409b 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 ed1626732e0da0df56111e8a2b85c7e24ecfa813 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/74] 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 0fb0d66f19425..09d65d39f4430 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 dbf70e57498a6c09e2176a6a5e2a8f9e2427f44d 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/74] 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 09d65d39f4430..b85365e01c6b7 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 8bb843d3c2b0b87b8de7002835e590f422131e4a 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/74] 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 5294b85aa4890..fbe84514c3e53 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/74] 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 b85365e01c6b7..627bdf36c7a7f 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 ef40bd49327a8f923964b85e755c0ff9393f509a 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/74] 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 627bdf36c7a7f..46ea978e9a46e 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 ca54c3f7ca0f6bfa8298aa2352431cddfac78c13 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/74] 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 46ea978e9a46e..dfb7b72ca910e 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 bfde100a185962cc4ab4c7aee383158750395e75 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/74] 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 dfb7b72ca910e..f1ec4d66c52d6 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 9aedf872338c00cbc30ce4940513b6704e9d8de6 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 20 Feb 2024 17:04:20 +0000 Subject: [PATCH 54/74] 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 f1ec4d66c52d6..328ede7af35df 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 dd77b7c4c1c55d76188696a4c904835d35d548b1 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/74] 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 328ede7af35df..ebaab00e2f8bc 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 b17ce147458a4274ca55501d6dbc94b18392f673 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/74] 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 ebaab00e2f8bc..10809eb6f2e9b 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 74b32adb1010edbbf48d275db3d8989a2814eeaa 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/74] 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 0f9e934c0301b..7236e74b2f6d6 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 10809eb6f2e9b..37e69060610f0 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 93430dab23ae75fb34fe195d6387e98d74279b93 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/74] 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 37e69060610f0..913e287b5afed 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 ecaf10a6aaa68a8aa9edf0cfa97b29a45ae6f88b 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/74] 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 913e287b5afed..b52b15529e82d 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 2fd99116e08032f2c7678324cf62a25eb6ad9df0 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/74] 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 b52b15529e82d..2e397d1a81c21 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 5bd3a79e48354b9c9d4bc64e32226100af9100ce 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/74] 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 2e397d1a81c21..9b8fb4dfff055 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 174149700439096fa20d8c8a959e272ebd1108a9 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/74] 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 9b8fb4dfff055..ea3b9a09e771b 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 e435a3d5eb4adbec2a196062f32bcef4ccc11086 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/74] 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 ea3b9a09e771b..389c93bedeb7d 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 8f27c48e39ee6cee317d2abb82c09a1834d78926 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/74] 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 389c93bedeb7d..c21f5af03bacb 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 3d953f816a13a7fc40dac87f15cce5056f5c7c0d 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/74] 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 c21f5af03bacb..c35fe81159447 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 3d106ce3c37d2d40dfb50d591eb4c60d452c26dd 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/74] 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 c35fe81159447..32dea023f561b 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 994bed11a09a9b072dbcf623e8a72f1ac8e3c4d7 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/74] 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 751a19a1c2a8c..7db1333932b0c 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 4998315ac344ebdcb20f573fb6f347fbe070eb15 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/74] 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 32dea023f561b..ad4a1e704837b 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 9072e6a5f4fca84d65a42c45887a0dacc8e30eea 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/74] 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 7db1333932b0c..751a19a1c2a8c 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 21333930e74a0d0ed47cda8c06a164cd78bfb196 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/74] 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 ad4a1e704837b..d874257154202 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/74] 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 d874257154202..c042153d8d266 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 987fb580d0d7cb9aa4e2654880c220e96fe066be 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/74] 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 c042153d8d266..5c41714b77875 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: Mon, 26 Feb 2024 17:11:15 +0000 Subject: [PATCH 73/74] Refactor useMarkPersistent function --- packages/block-editor/src/components/rich-text/index.js | 2 +- .../src/components/rich-text/use-mark-persistent.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 7236e74b2f6d6..a0316ee54337f 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -329,7 +329,7 @@ export function RichTextWrapper( onChange, } ); - useMarkPersistent( { html: adjustedValue, value } ); + useMarkPersistent( value ); const keyboardShortcuts = useRef( new Set() ); const inputEvents = useRef( new Set() ); diff --git a/packages/block-editor/src/components/rich-text/use-mark-persistent.js b/packages/block-editor/src/components/rich-text/use-mark-persistent.js index 10e157452fbe2..e52df535a8416 100644 --- a/packages/block-editor/src/components/rich-text/use-mark-persistent.js +++ b/packages/block-editor/src/components/rich-text/use-mark-persistent.js @@ -9,7 +9,7 @@ import { useDispatch } from '@wordpress/data'; */ import { store as blockEditorStore } from '../../store'; -export function useMarkPersistent( { html, value } ) { +export function useMarkPersistent( value ) { const previousText = useRef(); const hasActiveFormats = !! value.activeFormats?.length; const { __unstableMarkLastChangeAsPersistent } = @@ -36,5 +36,5 @@ export function useMarkPersistent( { html, value } ) { } __unstableMarkLastChangeAsPersistent(); - }, [ html, hasActiveFormats ] ); + }, [ value.text, hasActiveFormats ] ); } From e55f6bc0770700125a2dbabb0b27f5788edb8ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Tue, 27 Feb 2024 12:39:40 -0400 Subject: [PATCH 74/74] pick block data from straight props --- .../src/hooks/use-bindings-attributes.js | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 5c41714b77875..49a39e53c912d 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -13,7 +13,6 @@ 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,27 +63,19 @@ 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. * @return {null} This is a data-handling component. Render nothing. */ -const BindingConnector = ( { - args, - attrName, - attrValue, - blockName, - blockProps, - source, -} ) => { +const BindingConnector = ( { args, attrName, blockProps, source } ) => { const { placeholder, value: propValue } = source.useSource( blockProps, args ); - const setAttributes = blockProps.setAttributes; + const { setAttributes, name } = blockProps; + const attrValue = blockProps.attributes[ attrName ]; const { syncDerivedUpdates } = unlock( useDispatch( blockEditorStore ) ); @@ -136,7 +127,7 @@ const BindingConnector = ( { * attributes and metadata fields types are improved and include `url`. */ const htmlAttribute = - getBlockType( blockName ).attributes[ attrName ].attribute; + getBlockType( name ).attributes[ attrName ].attribute; if ( htmlAttribute === 'src' || htmlAttribute === 'href' ) { updateBoundAttibute( null ); @@ -150,7 +141,7 @@ const BindingConnector = ( { propValue, attrValue, placeholder, - blockName, + name, attrName, ] ); @@ -164,13 +155,11 @@ 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. * @return {null} This is a data-handling component. Render nothing. */ -function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { +function BlockBindingBridge( { blockProps, bindings } ) { const blockBindingsSources = unlock( useSelect( blocksStore ) ).getAllBlockBindingsSources(); @@ -189,9 +178,7 @@ function BlockBindingBridge( { blockName, blockProps, bindings, attributes } ) { return ( ( 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( + Object.entries( props.attributes.metadata?.bindings || {} ).filter( ( [ attrName ] ) => canBindAttribute( props.name, attrName ) ) ); @@ -224,9 +207,7 @@ const withBlockBindingSupport = createHigherOrderComponent( { Object.keys( bindings ).length > 0 && ( ) }