diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js
index 83d0e3f406f829..97d69503a6c4de 100644
--- a/packages/block-editor/src/components/block-edit/edit.js
+++ b/packages/block-editor/src/components/block-edit/edit.js
@@ -47,6 +47,8 @@ const Edit = ( props ) => {
const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit );
+// const EditWithFiltersAndBindings = withBlockBindingsSupport( EditWithFilters );
+
const EditWithGeneratedProps = ( props ) => {
const { attributes = {}, name } = props;
const blockType = getBlockType( name );
diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/components/block-edit/with-block-bindings-support.js
similarity index 73%
rename from packages/block-editor/src/hooks/use-bindings-attributes.js
rename to packages/block-editor/src/components/block-edit/with-block-bindings-support.js
index fdc617fda20c05..0529c019b8deec 100644
--- a/packages/block-editor/src/hooks/use-bindings-attributes.js
+++ b/packages/block-editor/src/components/block-edit/with-block-bindings-support.js
@@ -5,31 +5,19 @@ import { store as blocksStore } from '@wordpress/blocks';
import { createHigherOrderComponent } from '@wordpress/compose';
import { useRegistry, useSelect } from '@wordpress/data';
import { useCallback, useMemo, useContext } from '@wordpress/element';
-import { addFilter } from '@wordpress/hooks';
/**
* Internal dependencies
*/
-import isURLLike from '../components/link-control/is-url-like';
-import { unlock } from '../lock-unlock';
-import BlockContext from '../components/block-context';
+import isURLLike from '../link-control/is-url-like';
+import { unlock } from '../../lock-unlock';
+import BlockContext from '../block-context';
+import {
+ BLOCK_BINDINGS_ALLOWED_BLOCKS,
+ canBindAttribute,
+} from '../../utils/block-bindings';
/** @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' ],
- 'core/image': [ 'id', 'url', 'title', 'alt' ],
- 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ],
-};
const DEFAULT_ATTRIBUTE = '__default';
@@ -67,36 +55,12 @@ function replacePatternOverrideDefaultBindings( blockName, bindings ) {
}
/**
- * 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 to sources.
- */
-export function canBindBlock( blockName ) {
- return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS;
-}
-
-/**
- * Based on the given block name and attribute name,
- * check if it is possible to bind the block attribute.
+ * Given a binding of block attributes, returns a higher order component that
+ * overrides its `attributes` and `setAttributes` props to sync any changes needed.
*
- * @param {string} blockName - The block name.
- * @param {string} attributeName - The attribute name.
- * @return {boolean} Whether it is possible to bind the block attribute.
+ * @return {WPHigherOrderComponent} Higher-order component.
*/
-export function canBindAttribute( blockName, attributeName ) {
- return (
- canBindBlock( blockName ) &&
- BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName )
- );
-}
-
-export function getBindableAttributes( blockName ) {
- return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ];
-}
-
-export const withBlockBindingSupport = createHigherOrderComponent(
+export const withBlockBindingsSupport = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const registry = useRegistry();
const blockContext = useContext( BlockContext );
@@ -108,9 +72,9 @@ export const withBlockBindingSupport = createHigherOrderComponent(
() =>
replacePatternOverrideDefaultBindings(
name,
- props.attributes.metadata?.bindings
+ props.attributes?.metadata?.bindings
),
- [ props.attributes.metadata?.bindings, name ]
+ [ props.attributes?.metadata?.bindings, name ]
);
// While this hook doesn't directly call any selectors, `useSelect` is
@@ -196,7 +160,7 @@ export const withBlockBindingSupport = createHigherOrderComponent(
const hasParentPattern = !! updatedContext[ 'pattern/overrides' ];
const hasPatternOverridesDefaultBinding =
- props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
+ props.attributes?.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
?.source === 'core/pattern-overrides';
const _setAttributes = useCallback(
@@ -283,40 +247,13 @@ export const withBlockBindingSupport = createHigherOrderComponent(
);
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 ( ! canBindBlock( name ) ) {
- return settings;
- }
-
- return {
- ...settings,
- edit: withBlockBindingSupport( settings.edit ),
- };
-}
-
-addFilter(
- 'blocks.registerBlockType',
- 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source',
- shimAttributeSource
-);
diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js
index 4696149dc38751..7e50b75e1b9564 100644
--- a/packages/block-editor/src/components/block-list/use-block-props/index.js
+++ b/packages/block-editor/src/components/block-list/use-block-props/index.js
@@ -29,7 +29,7 @@ import { useBlockRefProvider } from './use-block-refs';
import { useIntersectionObserver } from './use-intersection-observer';
import { useScrollIntoView } from './use-scroll-into-view';
import { useFlashEditableBlocks } from '../../use-flash-editable-blocks';
-import { canBindBlock } from '../../../hooks/use-bindings-attributes';
+import { canBindBlock } from '../../../utils/block-bindings';
import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility';
/**
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index bc8eca6ea94d05..768ffbb0cdd2dc 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -39,7 +39,7 @@ import FormatEdit from './format-edit';
import { getAllowedFormats } from './utils';
import { Content, valueToHTMLString } from './content';
import { withDeprecations } from './with-deprecations';
-import { canBindBlock } from '../../hooks/use-bindings-attributes';
+import { canBindBlock } from '../../utils/block-bindings';
import BlockContext from '../block-context';
export const keyboardShortcutContext = createContext();
diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js
index e10696cc1257d7..cec80dffaeaa16 100644
--- a/packages/block-editor/src/hooks/block-bindings.js
+++ b/packages/block-editor/src/hooks/block-bindings.js
@@ -23,15 +23,15 @@ import { useViewportMatch } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import {
- canBindAttribute,
- getBindableAttributes,
-} from '../hooks/use-bindings-attributes';
import { unlock } from '../lock-unlock';
import InspectorControls from '../components/inspector-controls';
import BlockContext from '../components/block-context';
import { useBlockEditContext } from '../components/block-edit';
-import { useBlockBindingsUtils } from '../utils/block-bindings';
+import {
+ canBindAttribute,
+ getBindableAttributes,
+ useBlockBindingsUtils,
+} from '../utils/block-bindings';
import { store as blockEditorStore } from '../store';
const { Menu } = unlock( componentsPrivateApis );
diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js
index 66ff60b691b66f..7f9b29376ad1fb 100644
--- a/packages/block-editor/src/hooks/index.js
+++ b/packages/block-editor/src/hooks/index.js
@@ -32,7 +32,6 @@ import './metadata';
import blockHooks from './block-hooks';
import blockBindingsPanel from './block-bindings';
import './block-renaming';
-import './use-bindings-attributes';
import './grid-visualizer';
createBlockEditFilter(
diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js
index dcf80d985473b2..82f0dff53531a4 100644
--- a/packages/block-editor/src/utils/block-bindings.js
+++ b/packages/block-editor/src/utils/block-bindings.js
@@ -13,6 +13,43 @@ function isObjectEmpty( object ) {
return ! object || Object.keys( object ).length === 0;
}
+export const BLOCK_BINDINGS_ALLOWED_BLOCKS = {
+ 'core/paragraph': [ 'content' ],
+ 'core/heading': [ 'content' ],
+ 'core/image': [ 'id', 'url', 'title', 'alt' ],
+ 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ],
+};
+
+/**
+ * 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 to sources.
+ */
+export function canBindBlock( blockName ) {
+ return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS;
+}
+
+/**
+ * Based on the given block name and attribute name,
+ * check if it is possible to bind the block attribute.
+ *
+ * @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 canBindAttribute( blockName, attributeName ) {
+ return (
+ canBindBlock( blockName ) &&
+ BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName )
+ );
+}
+
+export function getBindableAttributes( blockName ) {
+ return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ];
+}
+
/**
* Contains utils to update the block `bindings` metadata.
*
diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php
index b86673c2c523d0..1fd6d8468c77db 100644
--- a/packages/e2e-tests/plugins/block-bindings.php
+++ b/packages/e2e-tests/plugins/block-bindings.php
@@ -41,7 +41,11 @@ function gutenberg_test_block_bindings_registration() {
plugins_url( 'block-bindings/index.js', __FILE__ ),
array(
'wp-blocks',
- 'wp-private-apis',
+ 'wp-block-editor',
+ 'wp-components',
+ 'wp-compose',
+ 'wp-element',
+ 'wp-hooks',
),
filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ),
true
diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js
index 5c364257caed19..63c463e197fa8a 100644
--- a/packages/e2e-tests/plugins/block-bindings/index.js
+++ b/packages/e2e-tests/plugins/block-bindings/index.js
@@ -1,4 +1,9 @@
const { registerBlockBindingsSource } = wp.blocks;
+const { InspectorControls } = wp.blockEditor;
+const { PanelBody, TextControl } = wp.components;
+const { createHigherOrderComponent } = wp.compose;
+const { createElement: el, Fragment } = wp.element;
+const { addFilter } = wp.hooks;
const { fieldsList } = window.testingBindings || {};
const getValues = ( { bindings } ) => {
@@ -46,3 +51,43 @@ registerBlockBindingsSource( {
getValues,
canUserEditValue: () => true,
} );
+
+const withBlockBindingsInspectorControl = createHigherOrderComponent(
+ ( BlockEdit ) => {
+ return ( props ) => {
+ if ( ! props.attributes?.metadata?.bindings?.content ) {
+ return el( BlockEdit, props );
+ }
+
+ return el(
+ Fragment,
+ {},
+ el( BlockEdit, props ),
+ el(
+ InspectorControls,
+ {},
+ el(
+ PanelBody,
+ { title: 'Bindings' },
+ el( TextControl, {
+ __next40pxDefaultSize: true,
+ __nextHasNoMarginBottom: true,
+ label: 'Content',
+ value: props.attributes.content,
+ onChange: ( content ) =>
+ props.setAttributes( {
+ content,
+ } ),
+ } )
+ )
+ )
+ );
+ };
+ }
+);
+
+addFilter(
+ 'editor.BlockEdit',
+ 'testing/bindings-inspector-control',
+ withBlockBindingsInspectorControl
+);
diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
index 32334bfc777f2a..318707e22f098d 100644
--- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
+++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
@@ -524,6 +524,47 @@ test.describe( 'Post Meta source', () => {
previewPage.locator( '#connected-paragraph' )
).toHaveText( 'new value' );
} );
+
+ test( 'should be possible to edit the value of the connected custom fields in the inspector control registered by the plugin', async ( {
+ editor,
+ page,
+ } ) => {
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: {
+ anchor: 'connected-paragraph',
+ content: 'fallback content',
+ metadata: {
+ bindings: {
+ content: {
+ source: 'core/post-meta',
+ args: {
+ key: 'movie_field',
+ },
+ },
+ },
+ },
+ },
+ } );
+ const contentInput = page.getByRole( 'textbox', {
+ name: 'Content',
+ } );
+ await expect( contentInput ).toHaveValue(
+ 'Movie field default value'
+ );
+ await contentInput.fill( 'new value' );
+ // Check that the paragraph content attribute didn't change.
+ const [ paragraphBlockObject ] = await editor.getBlocks();
+ expect( paragraphBlockObject.attributes.content ).toBe(
+ 'fallback content'
+ );
+ // Check the value of the custom field is being updated by visiting the frontend.
+ const previewPage = await editor.openPreviewPage();
+ await expect(
+ previewPage.locator( '#connected-paragraph' )
+ ).toHaveText( 'new value' );
+ } );
+
test( 'should be possible to connect movie fields through the attributes panel', async ( {
editor,
page,