diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js
index 5cd8cb46b3b7e7..6ae1c606577f8f 100644
--- a/packages/block-editor/src/hooks/use-bindings-attributes.js
+++ b/packages/block-editor/src/hooks/use-bindings-attributes.js
@@ -1,12 +1,11 @@
/**
* WordPress dependencies
*/
-import { getBlockType, store as blocksStore } from '@wordpress/blocks';
+import { store as blocksStore } from '@wordpress/blocks';
import { createHigherOrderComponent } from '@wordpress/compose';
-import { useSelect } from '@wordpress/data';
-import { useLayoutEffect, useCallback, useState } from '@wordpress/element';
+import { useRegistry, useSelect } from '@wordpress/data';
+import { useCallback } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
-import { RichTextData } from '@wordpress/rich-text';
/**
* Internal dependencies
@@ -56,181 +55,134 @@ export function canBindAttribute( blockName, attributeName ) {
);
}
-/**
- * This component is responsible for detecting and
- * propagating data changes from the source to the block.
- *
- * @param {Object} props - The component props.
- * @param {string} props.attrName - The attribute name.
- * @param {Object} props.blockProps - The block props with bound attribute.
- * @param {Object} props.source - Source handler.
- * @param {Object} props.args - The arguments to pass to the source.
- * @param {Function} props.onPropValueChange - The function to call when the attribute value changes.
- * @return {null} Data-handling component. Render nothing.
- */
-const BindingConnector = ( {
- args,
- attrName,
- blockProps,
- source,
- onPropValueChange,
-} ) => {
- const { placeholder, value: propValue } = source.useSource(
- blockProps,
- args
- );
-
- const { name: blockName } = blockProps;
- const attrValue = blockProps.attributes[ attrName ];
-
- const updateBoundAttibute = useCallback(
- ( newAttrValue, prevAttrValue ) => {
- /*
- * If the attribute is a RichTextData instance,
- * (core/paragraph, core/heading, core/button, etc.)
- * compare its HTML representation with the new value.
- *
- * To do: it looks like a workaround.
- * Consider improving the attribute and metadata fields types.
- */
- if ( prevAttrValue instanceof RichTextData ) {
- // Bail early if the Rich Text value is the same.
- if ( prevAttrValue.toHTMLString() === newAttrValue ) {
+export const withBlockBindingSupport = createHigherOrderComponent(
+ ( BlockEdit ) => ( props ) => {
+ const registry = useRegistry();
+
+ const boundAttributes = useSelect(
+ ( select ) => {
+ const bindings = Object.fromEntries(
+ Object.entries(
+ props.attributes.metadata?.bindings || {}
+ ).filter( ( [ attrName ] ) =>
+ canBindAttribute( props.name, attrName )
+ )
+ );
+
+ if ( ! Object.keys( bindings ).length > 0 ) {
return;
}
- /*
- * To preserve the value type,
- * convert the new value to a RichTextData instance.
- */
- newAttrValue = RichTextData.fromHTMLString( newAttrValue );
- }
-
- if ( prevAttrValue === newAttrValue ) {
- return;
- }
-
- onPropValueChange( { [ attrName ]: newAttrValue } );
- },
- [ attrName, onPropValueChange ]
- );
-
- useLayoutEffect( () => {
- if ( typeof propValue !== 'undefined' ) {
- updateBoundAttibute( propValue, attrValue );
- } 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`.
- */
- const htmlAttribute =
- getBlockType( blockName ).attributes[ attrName ].attribute;
-
- if ( htmlAttribute === 'src' || htmlAttribute === 'href' ) {
- updateBoundAttibute( null );
- return;
- }
-
- updateBoundAttibute( placeholder );
- }
- }, [
- updateBoundAttibute,
- propValue,
- attrValue,
- placeholder,
- blockName,
- attrName,
- ] );
-
- return null;
-};
+ const blockBindingsSources = unlock(
+ select( blocksStore )
+ ).getAllBlockBindingsSources();
+
+ return Object.entries( bindings ).reduce(
+ ( accu, [ attrName, boundAttribute ] ) => {
+ // Bail early if the block doesn't have a valid source handler.
+ const source =
+ blockBindingsSources[ boundAttribute.source ];
+
+ if ( ! source?.getValue ) {
+ return accu;
+ }
+
+ const args = {
+ registry,
+ context: props.context,
+ clientId: props.clientId,
+ attributeName: attrName,
+ args: boundAttribute.args,
+ };
+
+ accu[ attrName ] = source.getValue( args );
+
+ if ( accu[ attrName ] === undefined ) {
+ if ( attrName === 'url' ) {
+ accu[ attrName ] = null;
+ } else {
+ accu[ attrName ] =
+ source.getPlaceholder?.( args );
+ }
+ }
+
+ return accu;
+ },
+ {}
+ );
+ },
+ [
+ props.attributes.metadata?.bindings,
+ props.name,
+ props.context,
+ props.clientId,
+ registry,
+ ]
+ );
-/**
- * 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 {Object} props.blockProps - The BlockEdit props object.
- * @param {Object} props.bindings - The block bindings settings.
- * @param {Function} props.onPropValueChange - The function to call when the attribute value changes.
- * @return {null} Data-handling component. Render nothing.
- */
-function BlockBindingBridge( { blockProps, bindings, onPropValueChange } ) {
- const blockBindingsSources = unlock(
- useSelect( blocksStore )
- ).getAllBlockBindingsSources();
+ const { setAttributes } = props;
+
+ const _setAttributes = useCallback(
+ ( nextAttributes ) => {
+ const keptAttributes = { ...nextAttributes };
+ registry.batch( () => {
+ const bindings = Object.fromEntries(
+ Object.entries(
+ props.attributes.metadata?.bindings || {}
+ ).filter( ( [ attrName ] ) =>
+ canBindAttribute( props.name, attrName )
+ )
+ );
- return (
- <>
- { Object.entries( bindings ).map(
- ( [ attrName, boundAttribute ] ) => {
- // Bail early if the block doesn't have a valid source handler.
- const source =
- blockBindingsSources[ boundAttribute.source ];
- if ( ! source?.useSource ) {
- return null;
+ if ( ! Object.keys( bindings ).length > 0 ) {
+ return setAttributes( nextAttributes );
}
- return (
-
- );
- }
- ) }
- >
- );
-}
-
-const withBlockBindingSupport = createHigherOrderComponent(
- ( BlockEdit ) => ( props ) => {
- /*
- * Collect and update the bound attributes
- * in a separate state.
- */
- const [ boundAttributes, setBoundAttributes ] = useState( {} );
- const updateBoundAttributes = useCallback(
- ( newAttributes ) =>
- setBoundAttributes( ( prev ) => ( {
- ...prev,
- ...newAttributes,
- } ) ),
- []
- );
+ const blockBindingsSources = unlock(
+ registry.select( blocksStore )
+ ).getAllBlockBindingsSources();
+
+ for ( const [ attributeKey, value ] of Object.entries(
+ nextAttributes
+ ) ) {
+ if ( bindings[ attributeKey ] ) {
+ const source =
+ blockBindingsSources[
+ bindings[ attributeKey ].source
+ ];
+ if ( source?.setValue ) {
+ source.setValue( {
+ registry,
+ context: props.context,
+ clientId: props.clientId,
+ attributeName: attributeKey,
+ value,
+ args: bindings[ attributeKey ].args,
+ } );
+ delete keptAttributes[ attributeKey ];
+ }
+ }
+ }
- /*
- * Create binding object filtering
- * only the attributes that can be bound.
- */
- const bindings = Object.fromEntries(
- Object.entries( props.attributes.metadata?.bindings || {} ).filter(
- ( [ attrName ] ) => canBindAttribute( props.name, attrName )
- )
+ setAttributes( keptAttributes );
+ } );
+ },
+ [
+ registry,
+ props.attributes.metadata?.bindings,
+ props.name,
+ props.context,
+ props.clientId,
+ setAttributes,
+ ]
);
return (
<>
- { Object.keys( bindings ).length > 0 && (
-
- ) }
-
>
);
diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js
index d609f70b91b55d..1ef9c3614922e0 100644
--- a/packages/blocks/src/store/private-actions.js
+++ b/packages/blocks/src/store/private-actions.js
@@ -51,7 +51,9 @@ export function registerBlockBindingsSource( source ) {
type: 'REGISTER_BLOCK_BINDINGS_SOURCE',
sourceName: source.name,
sourceLabel: source.label,
- useSource: source.useSource,
+ getValue: source.getValue,
+ setValue: source.setValue,
+ getPlaceholder: source.getPlaceholder,
lockAttributesEditing: source.lockAttributesEditing,
};
}
diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js
index f92fb376b530a7..7a7dac93b3fb77 100644
--- a/packages/blocks/src/store/reducer.js
+++ b/packages/blocks/src/store/reducer.js
@@ -390,6 +390,9 @@ export function blockBindingsSources( state = {}, action ) {
[ action.sourceName ]: {
label: action.sourceLabel,
useSource: action.useSource,
+ getValue: action.getValue,
+ setValue: action.setValue,
+ getPlaceholder: action.getPlaceholder,
lockAttributesEditing: action.lockAttributesEditing ?? true,
},
};
diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js
index 0d0c737d0eaf77..f5b3b526dbfd4a 100644
--- a/packages/editor/src/bindings/post-meta.js
+++ b/packages/editor/src/bindings/post-meta.js
@@ -1,9 +1,9 @@
/**
* WordPress dependencies
*/
-import { useEntityProp } from '@wordpress/core-data';
-import { useSelect } from '@wordpress/data';
+import { store as coreDataStore } from '@wordpress/core-data';
import { _x } from '@wordpress/i18n';
+
/**
* Internal dependencies
*/
@@ -12,33 +12,17 @@ import { store as editorStore } from '../store';
export default {
name: 'core/post-meta',
label: _x( 'Post Meta', 'block bindings source' ),
- useSource( props, sourceAttributes ) {
- const { getCurrentPostType } = useSelect( editorStore );
- const { context } = props;
- const { key: metaKey } = sourceAttributes;
+ getPlaceholder( { args } ) {
+ return args.key;
+ },
+ getValue( { registry, context, args } ) {
const postType = context.postType
? context.postType
- : getCurrentPostType();
-
- const [ meta, setMeta ] = useEntityProp(
- 'postType',
- context.postType,
- 'meta',
- context.postId
- );
-
- if ( postType === 'wp_template' ) {
- return { placeholder: metaKey };
- }
- const metaValue = meta[ metaKey ];
- const updateMetaValue = ( newValue ) => {
- setMeta( { ...meta, [ metaKey ]: newValue } );
- };
+ : registry.select( editorStore ).getCurrentPostType();
- return {
- placeholder: metaKey,
- value: metaValue,
- updateValue: updateMetaValue,
- };
+ return registry
+ .select( coreDataStore )
+ .getEditedEntityRecord( 'postType', postType, context.postId )
+ .meta?.[ args.key ];
},
};