From 33c6f32cd89c1a6243ccb9a2511726f246756683 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:20:15 +0000 Subject: [PATCH 1/9] Initial plan From 3803aa9182a937d8641d1fbcbc423446b3666ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:24:26 +0000 Subject: [PATCH 2/9] Expand block bindings support to include more WordPress core blocks - Add support for core/details block - Add support for dynamic blocks: post-title, post-date, post-excerpt, post-featured-image, post-author - Add support for additional button attributes: linkTarget and rel - Add support for image title attribute - Add filter to allow dynamic registration of bindable blocks - Update BlockBindingControls to handle new blocks and attributes - Update documentation with supported blocks and extension guide Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- docs/concepts/block-bindings.md | 41 +++++++++++++++++ src/block-editor/filters/addUsesContext.ts | 44 ++++++++++++++++++- .../components/BlockBindingControls.tsx | 38 ++++++++++++++++ .../remote-data-container/config/constants.ts | 10 +++++ 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/docs/concepts/block-bindings.md b/docs/concepts/block-bindings.md index 61d18b437..f9132330c 100644 --- a/docs/concepts/block-bindings.md +++ b/docs/concepts/block-bindings.md @@ -3,3 +3,44 @@ Remote Data Blocks takes advantage of the [block bindings API](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/). This core WordPress API allows you to “bind” dynamic data to the attributes of core blocks, which are then reflected in the final HTML markup. Generally, this avoids the need to write and maintain custom blocks. For a quick overview of block bindings, the [announcement post](https://make.wordpress.org/core/2024/03/06/new-feature-the-block-bindings-api/) is very helpful; for a deeper dive, consult the [public documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/). That said, an in-depth understanding of block bindings isn't necessary to use Remote Data Blocks: just know that the plugin is built on core, stable WordPress APIs. + +## Supported Core Blocks + +Remote Data Blocks supports block bindings for the following WordPress core blocks: + +### Static Blocks +- **Paragraph** (`core/paragraph`) - `content` attribute +- **Heading** (`core/heading`) - `content` attribute +- **Image** (`core/image`) - `url`, `alt`, `title` attributes +- **Button** (`core/button`) - `url`, `text`, `linkTarget`, `rel` attributes +- **Details** (`core/details`) - `content` attribute (experimental) + +### Dynamic Blocks +- **Post Title** (`core/post-title`) - `content` attribute +- **Post Date** (`core/post-date`) - `content` attribute +- **Post Excerpt** (`core/post-excerpt`) - `content` attribute +- **Post Featured Image** (`core/post-featured-image`) - `url`, `alt` attributes +- **Post Author** (`core/post-author`) - `content` attribute + +## Extending Supported Blocks + +You can extend the list of supported blocks using WordPress filters: + +### JavaScript Filter + +```javascript +import { addFilter } from '@wordpress/hooks'; + +addFilter( +'remote_data_blocks_supported_blocks', +'my-plugin/add-custom-blocks', +( supportedBlocks, blockName, settings ) => { +// Add your custom block to the list +return [ ...supportedBlocks, 'my-plugin/custom-block' ]; +} +); +``` + +### Automatic Detection + +Blocks that define bindable attributes using WordPress core's `__experimentalLabel` property or `supports.bindings` configuration will be automatically supported without needing to add them to the filter. diff --git a/src/block-editor/filters/addUsesContext.ts b/src/block-editor/filters/addUsesContext.ts index 622e26dfe..cfa16698f 100644 --- a/src/block-editor/filters/addUsesContext.ts +++ b/src/block-editor/filters/addUsesContext.ts @@ -1,15 +1,57 @@ import { BlockConfiguration } from '@wordpress/blocks'; +import { applyFilters } from '@wordpress/hooks'; import { REMOTE_DATA_CONTEXT_KEY, SUPPORTED_CORE_BLOCKS, } from '@/blocks/remote-data-container/config/constants'; +/** + * Check if a block supports bindings based on its metadata. + * + * @param settings The block configuration settings + * @returns true if the block supports bindings + */ +function blockSupportsBindings( settings: BlockConfiguration< RemoteDataInnerBlockAttributes > ): boolean { + // Check if the block has __experimentalLabel defined on any attribute + if ( settings.attributes ) { + for ( const attr of Object.values( settings.attributes ) ) { + // @ts-ignore - __experimentalLabel is not in the types but is supported by WordPress + if ( attr.__experimentalLabel ) { + return true; + } + } + } + + // Check if the block has explicit supports.bindings configuration + // @ts-ignore - bindings is not in the types but is supported by WordPress + if ( settings.supports?.bindings ) { + return true; + } + + return false; +} + export function addUsesContext( settings: BlockConfiguration< RemoteDataInnerBlockAttributes >, name: string ) { - if ( ! SUPPORTED_CORE_BLOCKS.includes( name ) ) { + /** + * Filter the list of supported core blocks for remote data bindings. + * + * @param supportedBlocks Array of block names that support remote data bindings + * @param blockName The name of the block being registered + * @param settings The block configuration settings + */ + const supportedBlocks = applyFilters( + 'remote_data_blocks_supported_blocks', + SUPPORTED_CORE_BLOCKS, + name, + settings + ) as string[]; + + // Check if the block is in the supported list or if it explicitly supports bindings + if ( ! supportedBlocks.includes( name ) && ! blockSupportsBindings( settings ) ) { return settings; } diff --git a/src/blocks/remote-data-container/components/BlockBindingControls.tsx b/src/blocks/remote-data-container/components/BlockBindingControls.tsx index 26821781c..d9fa6f05d 100644 --- a/src/blocks/remote-data-container/components/BlockBindingControls.tsx +++ b/src/blocks/remote-data-container/components/BlockBindingControls.tsx @@ -1,10 +1,13 @@ import { CheckboxControl, SelectControl } from '@wordpress/components'; import { + BUTTON_LINK_TARGET_FIELD_TYPES, + BUTTON_REL_FIELD_TYPES, BUTTON_TEXT_FIELD_TYPES, BUTTON_URL_FIELD_TYPES, HTML_FIELD_TYPES, IMAGE_ALT_FIELD_TYPES, + IMAGE_TITLE_FIELD_TYPES, IMAGE_URL_FIELD_TYPES, TEXT_FIELD_TYPES, } from '@/blocks/remote-data-container/config/constants'; @@ -57,9 +60,12 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { const contentArgs = attributes.metadata?.bindings?.content?.args; const contentField = contentArgs?.field ?? ''; const imageAltField = attributes.metadata?.bindings?.alt?.args?.field ?? ''; + const imageTitleField = attributes.metadata?.bindings?.title?.args?.field ?? ''; const imageUrlField = attributes.metadata?.bindings?.url?.args?.field ?? ''; const buttonUrlField = attributes.metadata?.bindings?.url?.args?.field ?? ''; const buttonTextField = attributes.metadata?.bindings?.text?.args?.field ?? ''; + const buttonLinkTargetField = attributes.metadata?.bindings?.linkTarget?.args?.field ?? ''; + const buttonRelField = attributes.metadata?.bindings?.rel?.args?.field ?? ''; function updateFieldBinding( target: string, field: string ): void { if ( ! field ) { @@ -102,6 +108,11 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { switch ( blockName ) { case 'core/heading': case 'core/paragraph': + case 'core/details': + case 'core/post-title': + case 'core/post-date': + case 'core/post-excerpt': + case 'core/post-author': return ( <> + { blockName === 'core/image' && ( + + ) } ); case 'core/button': @@ -162,6 +184,22 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { updateFieldBinding={ updateFieldBinding } value={ buttonTextField } /> + + ); diff --git a/src/blocks/remote-data-container/config/constants.ts b/src/blocks/remote-data-container/config/constants.ts index 8a50a21a9..95c1e9472 100644 --- a/src/blocks/remote-data-container/config/constants.ts +++ b/src/blocks/remote-data-container/config/constants.ts @@ -3,9 +3,16 @@ import { getClassName } from '@/utils/string'; export const SUPPORTED_CORE_BLOCKS = [ 'core/button', + 'core/details', 'core/heading', 'core/image', 'core/paragraph', + // Dynamic blocks + 'core/post-author', + 'core/post-date', + 'core/post-excerpt', + 'core/post-featured-image', + 'core/post-title', ]; export const DISPLAY_QUERY_KEY = 'display'; @@ -24,9 +31,12 @@ export const SEARCH_INPUT_VARIABLE_TYPE = 'ui:search_input'; export const BUTTON_TEXT_FIELD_TYPES = [ 'button_text' ]; export const BUTTON_URL_FIELD_TYPES = [ 'button_url' ]; +export const BUTTON_LINK_TARGET_FIELD_TYPES = [ 'button_link_target', 'string' ]; +export const BUTTON_REL_FIELD_TYPES = [ 'button_rel', 'string' ]; export const HTML_FIELD_TYPES = [ 'html' ]; export const ID_FIELD_TYPES = [ 'id', 'id:list' ]; export const IMAGE_ALT_FIELD_TYPES = [ 'image_alt' ]; +export const IMAGE_TITLE_FIELD_TYPES = [ 'image_title', 'string' ]; export const IMAGE_URL_FIELD_TYPES = [ 'image_url' ]; export const TEXT_FIELD_TYPES = [ 'currency_in_current_locale', From 3aca7d770a001dc9df67ebdb5128e3550fdefe3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:25:10 +0000 Subject: [PATCH 3/9] Add tests for addUsesContext filter with new blocks support Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- .../filters/addUsesContext.test.ts | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/src/block-editor/filters/addUsesContext.test.ts diff --git a/tests/src/block-editor/filters/addUsesContext.test.ts b/tests/src/block-editor/filters/addUsesContext.test.ts new file mode 100644 index 000000000..5eb22417d --- /dev/null +++ b/tests/src/block-editor/filters/addUsesContext.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it, vi } from 'vitest'; +import { BlockConfiguration } from '@wordpress/blocks'; + +import { addUsesContext } from '@/block-editor/filters/addUsesContext'; +import { REMOTE_DATA_CONTEXT_KEY, SUPPORTED_CORE_BLOCKS } from '@/blocks/remote-data-container/config/constants'; + +// Mock the applyFilters function +vi.mock( '@wordpress/hooks', () => ( { + applyFilters: vi.fn( ( _filterName, supportedBlocks ) => supportedBlocks ), +} ) ); + +describe( 'addUsesContext', () => { + it( 'should add context to supported core blocks', () => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', + }; + + const result = addUsesContext( settings, 'core/paragraph' ); + + expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + } ); + + it( 'should not modify blocks not in the supported list', () => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', + }; + + const result = addUsesContext( settings, 'core/unknown-block' ); + + expect( result ).toEqual( settings ); + expect( result.usesContext ).toBeUndefined(); + } ); + + it( 'should preserve existing usesContext values', () => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', + usesContext: [ 'existing/context' ], + }; + + const result = addUsesContext( settings, 'core/heading' ); + + expect( result.usesContext ).toContain( 'existing/context' ); + expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + } ); + + it( 'should not duplicate context if already present', () => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', + usesContext: [ REMOTE_DATA_CONTEXT_KEY ], + }; + + const result = addUsesContext( settings, 'core/button' ); + + expect( result ).toEqual( settings ); + const contextCount = result.usesContext?.filter( ctx => ctx === REMOTE_DATA_CONTEXT_KEY ).length; + expect( contextCount ).toBe( 1 ); + } ); + + it( 'should add context to all supported blocks', () => { + SUPPORTED_CORE_BLOCKS.forEach( blockName => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', + }; + + const result = addUsesContext( settings, blockName ); + + expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + } ); + } ); + + it( 'should support blocks with __experimentalLabel', () => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: { + content: { + type: 'string', + // @ts-ignore - __experimentalLabel is not in types + __experimentalLabel: 'Content', + }, + }, + category: 'text', + title: 'Test Block', + }; + + const result = addUsesContext( settings, 'custom/block-with-label' ); + + expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + } ); + + it( 'should support blocks with supports.bindings', () => { + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', + // @ts-ignore - bindings is not in types + supports: { + bindings: true, + }, + }; + + const result = addUsesContext( settings, 'custom/block-with-bindings' ); + + expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + } ); +} ); From f72e7753312e1588ca9c720204ec00c9c5915693 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:25:46 +0000 Subject: [PATCH 4/9] Add clarifying comments for field type constants Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- src/blocks/remote-data-container/config/constants.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blocks/remote-data-container/config/constants.ts b/src/blocks/remote-data-container/config/constants.ts index 95c1e9472..cf9e2cb58 100644 --- a/src/blocks/remote-data-container/config/constants.ts +++ b/src/blocks/remote-data-container/config/constants.ts @@ -31,11 +31,13 @@ export const SEARCH_INPUT_VARIABLE_TYPE = 'ui:search_input'; export const BUTTON_TEXT_FIELD_TYPES = [ 'button_text' ]; export const BUTTON_URL_FIELD_TYPES = [ 'button_url' ]; +// Include 'string' as fallback for newly supported attributes that may not have dedicated field types yet export const BUTTON_LINK_TARGET_FIELD_TYPES = [ 'button_link_target', 'string' ]; export const BUTTON_REL_FIELD_TYPES = [ 'button_rel', 'string' ]; export const HTML_FIELD_TYPES = [ 'html' ]; export const ID_FIELD_TYPES = [ 'id', 'id:list' ]; export const IMAGE_ALT_FIELD_TYPES = [ 'image_alt' ]; +// Include 'string' as fallback for newly supported attributes that may not have dedicated field types yet export const IMAGE_TITLE_FIELD_TYPES = [ 'image_title', 'string' ]; export const IMAGE_URL_FIELD_TYPES = [ 'image_url' ]; export const TEXT_FIELD_TYPES = [ From afc52aaa2f939e5d523855a5de30dcc3e505cf36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:46:21 +0000 Subject: [PATCH 5/9] Use WordPress core's __experimentalBlockBindingsSupportedAttributes instead of hardcoded list Based on feedback, replaced the hardcoded SUPPORTED_CORE_BLOCKS list with a dynamic query to WordPress's block editor settings. This ensures automatic compatibility with all blocks that WordPress designates as supporting bindings, including future additions. - Query __experimentalBlockBindingsSupportedAttributes from block editor store - Remove hardcoded list of supported blocks - Keep new field type constants for UI controls - Update tests to mock the block editor store - Update documentation to reflect automatic detection Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- docs/concepts/block-bindings.md | 50 +++--- src/block-editor/filters/addUsesContext.ts | 58 +++---- .../remote-data-container/config/constants.ts | 14 -- .../filters/addUsesContext.test.ts | 153 ++++++++++++++---- 4 files changed, 157 insertions(+), 118 deletions(-) diff --git a/docs/concepts/block-bindings.md b/docs/concepts/block-bindings.md index f9132330c..dcf90418e 100644 --- a/docs/concepts/block-bindings.md +++ b/docs/concepts/block-bindings.md @@ -1,46 +1,32 @@ # Block bindings -Remote Data Blocks takes advantage of the [block bindings API](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/). This core WordPress API allows you to “bind” dynamic data to the attributes of core blocks, which are then reflected in the final HTML markup. Generally, this avoids the need to write and maintain custom blocks. +Remote Data Blocks takes advantage of the [block bindings API](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/). This core WordPress API allows you to "bind" dynamic data to the attributes of core blocks, which are then reflected in the final HTML markup. Generally, this avoids the need to write and maintain custom blocks. For a quick overview of block bindings, the [announcement post](https://make.wordpress.org/core/2024/03/06/new-feature-the-block-bindings-api/) is very helpful; for a deeper dive, consult the [public documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/). That said, an in-depth understanding of block bindings isn't necessary to use Remote Data Blocks: just know that the plugin is built on core, stable WordPress APIs. -## Supported Core Blocks +## Automatic Support for Block Bindings -Remote Data Blocks supports block bindings for the following WordPress core blocks: +Remote Data Blocks automatically supports all blocks that WordPress core designates as supporting block bindings. This includes: -### Static Blocks -- **Paragraph** (`core/paragraph`) - `content` attribute -- **Heading** (`core/heading`) - `content` attribute -- **Image** (`core/image`) - `url`, `alt`, `title` attributes -- **Button** (`core/button`) - `url`, `text`, `linkTarget`, `rel` attributes -- **Details** (`core/details`) - `content` attribute (experimental) +- Core blocks like **Paragraph**, **Heading**, **Image**, **Button**, and **Details** +- Dynamic blocks like **Post Title**, **Post Date**, **Post Excerpt**, **Post Featured Image**, and **Post Author** +- Any custom blocks that register bindable attributes through WordPress core's mechanisms -### Dynamic Blocks -- **Post Title** (`core/post-title`) - `content` attribute -- **Post Date** (`core/post-date`) - `content` attribute -- **Post Excerpt** (`core/post-excerpt`) - `content` attribute -- **Post Featured Image** (`core/post-featured-image`) - `url`, `alt` attributes -- **Post Author** (`core/post-author`) - `content` attribute +The plugin queries WordPress's `__experimentalBlockBindingsSupportedAttributes` setting to determine which blocks and attributes support bindings, ensuring compatibility with current and future WordPress versions without requiring manual updates. -## Extending Supported Blocks +## Extending Block Bindings -You can extend the list of supported blocks using WordPress filters: +To add block binding support to your custom blocks, follow [WordPress's official documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/#extending-supported-attributes) on extending supported attributes. Once registered with WordPress core, your blocks will automatically work with Remote Data Blocks. -### JavaScript Filter +Example of making a custom block attribute bindable: ```javascript -import { addFilter } from '@wordpress/hooks'; - -addFilter( -'remote_data_blocks_supported_blocks', -'my-plugin/add-custom-blocks', -( supportedBlocks, blockName, settings ) => { -// Add your custom block to the list -return [ ...supportedBlocks, 'my-plugin/custom-block' ]; -} -); +registerBlockType( 'my-plugin/custom-block', { +attributes: { +customField: { +type: 'string', +__experimentalLabel: 'Custom Field', // Makes this attribute bindable +}, +}, +} ); ``` - -### Automatic Detection - -Blocks that define bindable attributes using WordPress core's `__experimentalLabel` property or `supports.bindings` configuration will be automatically supported without needing to add them to the filter. diff --git a/src/block-editor/filters/addUsesContext.ts b/src/block-editor/filters/addUsesContext.ts index cfa16698f..5a1c0f66b 100644 --- a/src/block-editor/filters/addUsesContext.ts +++ b/src/block-editor/filters/addUsesContext.ts @@ -1,57 +1,35 @@ +import { store as blockEditorStore } from '@wordpress/block-editor'; import { BlockConfiguration } from '@wordpress/blocks'; -import { applyFilters } from '@wordpress/hooks'; +import { select } from '@wordpress/data'; -import { - REMOTE_DATA_CONTEXT_KEY, - SUPPORTED_CORE_BLOCKS, -} from '@/blocks/remote-data-container/config/constants'; +import { REMOTE_DATA_CONTEXT_KEY } from '@/blocks/remote-data-container/config/constants'; /** - * Check if a block supports bindings based on its metadata. + * Get the list of blocks that support bindings from WordPress core. * - * @param settings The block configuration settings - * @returns true if the block supports bindings + * @returns Record of block names to their supported attributes */ -function blockSupportsBindings( settings: BlockConfiguration< RemoteDataInnerBlockAttributes > ): boolean { - // Check if the block has __experimentalLabel defined on any attribute - if ( settings.attributes ) { - for ( const attr of Object.values( settings.attributes ) ) { - // @ts-ignore - __experimentalLabel is not in the types but is supported by WordPress - if ( attr.__experimentalLabel ) { - return true; - } - } +function getSupportedBlockBindings(): Record< string, string[] > { + try { + // Get the block editor settings which contain the supported bindings + const editorSettings = select( blockEditorStore )?.getSettings?.(); + // @ts-ignore - __experimentalBlockBindingsSupportedAttributes is not in types + return editorSettings?.__experimentalBlockBindingsSupportedAttributes ?? {}; + } catch ( error ) { + // If the store isn't available yet (e.g., during initial load), return empty + return {}; } - - // Check if the block has explicit supports.bindings configuration - // @ts-ignore - bindings is not in the types but is supported by WordPress - if ( settings.supports?.bindings ) { - return true; - } - - return false; } export function addUsesContext( settings: BlockConfiguration< RemoteDataInnerBlockAttributes >, name: string ) { - /** - * Filter the list of supported core blocks for remote data bindings. - * - * @param supportedBlocks Array of block names that support remote data bindings - * @param blockName The name of the block being registered - * @param settings The block configuration settings - */ - const supportedBlocks = applyFilters( - 'remote_data_blocks_supported_blocks', - SUPPORTED_CORE_BLOCKS, - name, - settings - ) as string[]; + // Check if this block supports bindings according to WordPress core + const supportedBindings = getSupportedBlockBindings(); + const blockSupportsBindings = name in supportedBindings && supportedBindings[ name ].length > 0; - // Check if the block is in the supported list or if it explicitly supports bindings - if ( ! supportedBlocks.includes( name ) && ! blockSupportsBindings( settings ) ) { + if ( ! blockSupportsBindings ) { return settings; } diff --git a/src/blocks/remote-data-container/config/constants.ts b/src/blocks/remote-data-container/config/constants.ts index cf9e2cb58..b7c1d90ed 100644 --- a/src/blocks/remote-data-container/config/constants.ts +++ b/src/blocks/remote-data-container/config/constants.ts @@ -1,20 +1,6 @@ import { getRestUrl } from '@/utils/localized-block-data'; import { getClassName } from '@/utils/string'; -export const SUPPORTED_CORE_BLOCKS = [ - 'core/button', - 'core/details', - 'core/heading', - 'core/image', - 'core/paragraph', - // Dynamic blocks - 'core/post-author', - 'core/post-date', - 'core/post-excerpt', - 'core/post-featured-image', - 'core/post-title', -]; - export const DISPLAY_QUERY_KEY = 'display'; export const REMOTE_DATA_CONTEXT_KEY = 'remote-data-blocks/remoteData'; export const REMOTE_DATA_REST_API_URL = getRestUrl(); diff --git a/tests/src/block-editor/filters/addUsesContext.test.ts b/tests/src/block-editor/filters/addUsesContext.test.ts index 5eb22417d..e3c5dc8af 100644 --- a/tests/src/block-editor/filters/addUsesContext.test.ts +++ b/tests/src/block-editor/filters/addUsesContext.test.ts @@ -1,16 +1,40 @@ -import { describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; import { BlockConfiguration } from '@wordpress/blocks'; import { addUsesContext } from '@/block-editor/filters/addUsesContext'; -import { REMOTE_DATA_CONTEXT_KEY, SUPPORTED_CORE_BLOCKS } from '@/blocks/remote-data-container/config/constants'; +import { REMOTE_DATA_CONTEXT_KEY } from '@/blocks/remote-data-container/config/constants'; -// Mock the applyFilters function -vi.mock( '@wordpress/hooks', () => ( { - applyFilters: vi.fn( ( _filterName, supportedBlocks ) => supportedBlocks ), +// Mock the WordPress data store +const mockGetSettings = vi.fn(); +const mockSelect = vi.fn( () => ( { + getSettings: mockGetSettings, +} ) ); + +vi.mock( '@wordpress/data', () => ( { + select: mockSelect, +} ) ); + +vi.mock( '@wordpress/block-editor', () => ( { + store: 'core/block-editor', } ) ); describe( 'addUsesContext', () => { - it( 'should add context to supported core blocks', () => { + beforeEach( () => { + // Reset mocks before each test + mockGetSettings.mockClear(); + mockSelect.mockClear(); + } ); + + it( 'should add context to blocks that support bindings according to WordPress', () => { + // Mock the block editor settings with supported bindings + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'url', 'alt', 'title' ], + }, + } ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { attributes: {}, category: 'text', @@ -22,7 +46,14 @@ describe( 'addUsesContext', () => { expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); } ); - it( 'should not modify blocks not in the supported list', () => { + it( 'should not modify blocks that do not support bindings', () => { + // Mock the block editor settings without the block + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/paragraph': [ 'content' ], + }, + } ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { attributes: {}, category: 'text', @@ -36,6 +67,12 @@ describe( 'addUsesContext', () => { } ); it( 'should preserve existing usesContext values', () => { + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/heading': [ 'content' ], + }, + } ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { attributes: {}, category: 'text', @@ -50,6 +87,12 @@ describe( 'addUsesContext', () => { } ); it( 'should not duplicate context if already present', () => { + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], + }, + } ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { attributes: {}, category: 'text', @@ -64,51 +107,97 @@ describe( 'addUsesContext', () => { expect( contextCount ).toBe( 1 ); } ); - it( 'should add context to all supported blocks', () => { - SUPPORTED_CORE_BLOCKS.forEach( blockName => { - const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { - attributes: {}, - category: 'text', - title: 'Test Block', - }; + it( 'should handle blocks with multiple bindable attributes', () => { + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/image': [ 'url', 'alt', 'title' ], + }, + } ); - const result = addUsesContext( settings, blockName ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'media', + title: 'Image Block', + }; - expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + const result = addUsesContext( settings, 'core/image' ); + + expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + } ); + + it( 'should handle dynamic blocks like post blocks', () => { + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/post-title': [ 'content' ], + 'core/post-featured-image': [ 'url', 'alt' ], + }, } ); + + const postTitleSettings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'theme', + title: 'Post Title', + }; + + const postImageSettings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'theme', + title: 'Post Featured Image', + }; + + const titleResult = addUsesContext( postTitleSettings, 'core/post-title' ); + const imageResult = addUsesContext( postImageSettings, 'core/post-featured-image' ); + + expect( titleResult.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + expect( imageResult.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); } ); - it( 'should support blocks with __experimentalLabel', () => { + it( 'should return settings unchanged if block editor settings are not available', () => { + // Mock select to return undefined (store not ready yet) + mockSelect.mockReturnValue( undefined ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { - attributes: { - content: { - type: 'string', - // @ts-ignore - __experimentalLabel is not in types - __experimentalLabel: 'Content', - }, - }, + attributes: {}, category: 'text', title: 'Test Block', }; - const result = addUsesContext( settings, 'custom/block-with-label' ); + const result = addUsesContext( settings, 'core/paragraph' ); - expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + expect( result ).toEqual( settings ); } ); - it( 'should support blocks with supports.bindings', () => { + it( 'should handle missing __experimentalBlockBindingsSupportedAttributes', () => { + // Mock settings without the experimental property + mockGetSettings.mockReturnValue( {} ); + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { attributes: {}, category: 'text', title: 'Test Block', - // @ts-ignore - bindings is not in types - supports: { - bindings: true, + }; + + const result = addUsesContext( settings, 'core/paragraph' ); + + expect( result ).toEqual( settings ); + } ); + + it( 'should not add context for blocks with empty attribute arrays', () => { + mockGetSettings.mockReturnValue( { + __experimentalBlockBindingsSupportedAttributes: { + 'core/some-block': [], // Block registered but no attributes support bindings }, + } ); + + const settings: BlockConfiguration< RemoteDataInnerBlockAttributes > = { + attributes: {}, + category: 'text', + title: 'Test Block', }; - const result = addUsesContext( settings, 'custom/block-with-bindings' ); + const result = addUsesContext( settings, 'core/some-block' ); - expect( result.usesContext ).toContain( REMOTE_DATA_CONTEXT_KEY ); + expect( result ).toEqual( settings ); + expect( result.usesContext ).toBeUndefined(); } ); } ); From 02211c707a0fd4a6067af9fc0f287794673414a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:59:55 +0000 Subject: [PATCH 6/9] Make BlockBindingControls handle generic block/attribute combinations Refactored BlockBindingControls to dynamically generate UI controls based on WordPress's supported bindings list instead of using hardcoded switch statements. This allows it to work with any block that WordPress designates as supporting bindings. - Query __experimentalBlockBindingsSupportedAttributes to get bindable attributes for each block - Dynamically generate controls for each supported attribute - Map attribute names to appropriate field types with sensible defaults - Auto-generate human-readable labels for attributes - Add fallback support for custom blocks like remote-data-blocks/remote-html - Remove hardcoded switch statement with specific block cases Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- .../components/BlockBindingControls.tsx | 260 +++++++++--------- 1 file changed, 136 insertions(+), 124 deletions(-) diff --git a/src/blocks/remote-data-container/components/BlockBindingControls.tsx b/src/blocks/remote-data-container/components/BlockBindingControls.tsx index d9fa6f05d..24f61fa07 100644 --- a/src/blocks/remote-data-container/components/BlockBindingControls.tsx +++ b/src/blocks/remote-data-container/components/BlockBindingControls.tsx @@ -1,4 +1,6 @@ +import { store as blockEditorStore } from '@wordpress/block-editor'; import { CheckboxControl, SelectControl } from '@wordpress/components'; +import { select } from '@wordpress/data'; import { BUTTON_LINK_TARGET_FIELD_TYPES, @@ -54,18 +56,98 @@ interface BlockBindingControlsProps { updateBinding: ( target: string, args: Omit< RemoteDataBlockBindingArgs, 'block' > ) => void; } +/** + * Get the list of bindable attributes for a specific block from WordPress core. + * + * @param blockName The name of the block + * @returns Array of attribute names that support bindings + */ +function getSupportedAttributesForBlock( blockName: string ): string[] { + try { + const editorSettings = select( blockEditorStore )?.getSettings?.(); + // @ts-ignore - __experimentalBlockBindingsSupportedAttributes is not in types + const supportedBindings = editorSettings?.__experimentalBlockBindingsSupportedAttributes ?? {}; + + // Check if this block has registered bindings in WordPress + if ( supportedBindings[ blockName ] ) { + return supportedBindings[ blockName ]; + } + + // Fallback for custom blocks like remote-data-blocks/remote-html + // that may not register with WordPress but still support bindings + if ( blockName === 'remote-data-blocks/remote-html' ) { + return [ 'content' ]; + } + + return []; + } catch ( error ) { + return []; + } +} + +/** + * Get appropriate field types for a given attribute name on a specific block. + * This provides sensible defaults based on common attribute names and block-specific overrides. + * + * @param blockName The name of the block + * @param attributeName The name of the attribute + * @returns Array of field types that should be available for this attribute + */ +function getFieldTypesForAttribute( blockName: string, attributeName: string ): string[] { + // Block-specific attribute mappings + if ( blockName === 'remote-data-blocks/remote-html' && attributeName === 'content' ) { + return HTML_FIELD_TYPES; + } + + // Map attribute names to their appropriate field types + const attributeFieldTypeMap: Record< string, string[] > = { + // Text content attributes + content: TEXT_FIELD_TYPES, + + // Image attributes + url: IMAGE_URL_FIELD_TYPES, + alt: IMAGE_ALT_FIELD_TYPES, + title: IMAGE_TITLE_FIELD_TYPES, + + // Button attributes + text: BUTTON_TEXT_FIELD_TYPES, + linkTarget: BUTTON_LINK_TARGET_FIELD_TYPES, + rel: BUTTON_REL_FIELD_TYPES, + + // HTML content + html: HTML_FIELD_TYPES, + }; + + // Return mapped types or default to TEXT_FIELD_TYPES for unknown attributes + return attributeFieldTypeMap[ attributeName ] ?? TEXT_FIELD_TYPES; +} + +/** + * Get a human-readable label for an attribute name. + * + * @param attributeName The name of the attribute + * @returns A human-readable label + */ +function getAttributeLabel( attributeName: string ): string { + const labelMap: Record< string, string > = { + content: 'Content', + url: 'URL', + alt: 'Alt Text', + title: 'Title', + text: 'Text', + linkTarget: 'Link Target', + rel: 'Link Relationship', + id: 'ID', + caption: 'Caption', + }; + + // Return mapped label or capitalize the attribute name + return labelMap[ attributeName ] ?? attributeName.charAt( 0 ).toUpperCase() + attributeName.slice( 1 ); +} + export function BlockBindingControls( props: BlockBindingControlsProps ) { const { attributes, availableBindings, blockName, remoteDataName, removeBinding, updateBinding } = props; - const contentArgs = attributes.metadata?.bindings?.content?.args; - const contentField = contentArgs?.field ?? ''; - const imageAltField = attributes.metadata?.bindings?.alt?.args?.field ?? ''; - const imageTitleField = attributes.metadata?.bindings?.title?.args?.field ?? ''; - const imageUrlField = attributes.metadata?.bindings?.url?.args?.field ?? ''; - const buttonUrlField = attributes.metadata?.bindings?.url?.args?.field ?? ''; - const buttonTextField = attributes.metadata?.bindings?.text?.args?.field ?? ''; - const buttonLinkTargetField = attributes.metadata?.bindings?.linkTarget?.args?.field ?? ''; - const buttonRelField = attributes.metadata?.bindings?.rel?.args?.field ?? ''; function updateFieldBinding( target: string, field: string ): void { if ( ! field ) { @@ -89,134 +171,64 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { } ); } - function updateFieldLabel( showLabel: boolean ): void { - if ( ! contentField ) { - // Form input should be disabled in this state, but check anyway. + function updateFieldLabel( target: string, showLabel: boolean ): void { + const currentField = attributes.metadata?.bindings?.[ target ]?.args?.field; + + if ( ! currentField ) { return; } const label = showLabel - ? Object.entries( availableBindings ).find( ( [ key ] ) => key === contentField )?.[ 1 ]?.name + ? Object.entries( availableBindings ).find( ( [ key ] ) => key === currentField )?.[ 1 ]?.name : undefined; - updateBinding( 'content', { ...contentArgs, field: contentField, label } ); + + const currentArgs = attributes.metadata?.bindings?.[ target ]?.args ?? {}; + updateBinding( target, { ...currentArgs, field: currentField, label } ); sendTracksEvent( 'remote_data_container_actions', { action: showLabel ? 'show_label' : 'hide_label', data_source_type: getBlockDataSourceType( remoteDataName ), } ); } - switch ( blockName ) { - case 'core/heading': - case 'core/paragraph': - case 'core/details': - case 'core/post-title': - case 'core/post-date': - case 'core/post-excerpt': - case 'core/post-author': - return ( - <> - - - - ); - - case 'core/image': - case 'core/post-featured-image': - return ( - <> - - - { blockName === 'core/image' && ( + // Get the list of bindable attributes for this block + const supportedAttributes = getSupportedAttributesForBlock( blockName ); + + // If no attributes support bindings, don't render anything + if ( supportedAttributes.length === 0 ) { + return null; + } + + // Dynamically generate controls for each supported attribute + return ( + <> + { supportedAttributes.map( ( attributeName ) => { + const fieldValue = attributes.metadata?.bindings?.[ attributeName ]?.args?.field ?? ''; + const fieldTypes = getFieldTypesForAttribute( blockName, attributeName ); + const label = getAttributeLabel( attributeName ); + const args = attributes.metadata?.bindings?.[ attributeName ]?.args; + + return ( +
- ) } - - ); - case 'core/button': - return ( - <> - - - - - - ); - - case 'remote-data-blocks/remote-html': - return ( - <> - - - ); - } - - return null; + { attributeName === 'content' && fieldValue && ( + updateFieldLabel( attributeName, showLabel ) } + /> + ) } +
+ ); + } ) } + + ); } From 84783a732a45c93a5096e364f51aee779f5a95c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:01:23 +0000 Subject: [PATCH 7/9] Optimize BlockBindingControls performance and DOM structure - Use useSelect hook to memoize editor settings and prevent unnecessary re-renders - Use useMemo to cache supported attributes computation - Remove unnecessary div wrapper around controls - Apply key prop directly to components instead of wrapper Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- .../components/BlockBindingControls.tsx | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/blocks/remote-data-container/components/BlockBindingControls.tsx b/src/blocks/remote-data-container/components/BlockBindingControls.tsx index 24f61fa07..dd96baa1a 100644 --- a/src/blocks/remote-data-container/components/BlockBindingControls.tsx +++ b/src/blocks/remote-data-container/components/BlockBindingControls.tsx @@ -1,6 +1,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; import { CheckboxControl, SelectControl } from '@wordpress/components'; -import { select } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; import { BUTTON_LINK_TARGET_FIELD_TYPES, @@ -56,35 +57,6 @@ interface BlockBindingControlsProps { updateBinding: ( target: string, args: Omit< RemoteDataBlockBindingArgs, 'block' > ) => void; } -/** - * Get the list of bindable attributes for a specific block from WordPress core. - * - * @param blockName The name of the block - * @returns Array of attribute names that support bindings - */ -function getSupportedAttributesForBlock( blockName: string ): string[] { - try { - const editorSettings = select( blockEditorStore )?.getSettings?.(); - // @ts-ignore - __experimentalBlockBindingsSupportedAttributes is not in types - const supportedBindings = editorSettings?.__experimentalBlockBindingsSupportedAttributes ?? {}; - - // Check if this block has registered bindings in WordPress - if ( supportedBindings[ blockName ] ) { - return supportedBindings[ blockName ]; - } - - // Fallback for custom blocks like remote-data-blocks/remote-html - // that may not register with WordPress but still support bindings - if ( blockName === 'remote-data-blocks/remote-html' ) { - return [ 'content' ]; - } - - return []; - } catch ( error ) { - return []; - } -} - /** * Get appropriate field types for a given attribute name on a specific block. * This provides sensible defaults based on common attribute names and block-specific overrides. @@ -149,6 +121,36 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { const { attributes, availableBindings, blockName, remoteDataName, removeBinding, updateBinding } = props; + // Use useSelect to efficiently get supported bindings from WordPress + const supportedBindingsFromWP = useSelect( + ( select ) => { + try { + const editorSettings = select( blockEditorStore )?.getSettings?.(); + // @ts-ignore - __experimentalBlockBindingsSupportedAttributes is not in types + return editorSettings?.__experimentalBlockBindingsSupportedAttributes ?? {}; + } catch ( error ) { + return {}; + } + }, + [] + ); + + // Memoize the supported attributes for this block + const supportedAttributes = useMemo( () => { + // Check if this block has registered bindings in WordPress + if ( supportedBindingsFromWP[ blockName ] ) { + return supportedBindingsFromWP[ blockName ]; + } + + // Fallback for custom blocks like remote-data-blocks/remote-html + // that may not register with WordPress but still support bindings + if ( blockName === 'remote-data-blocks/remote-html' ) { + return [ 'content' ]; + } + + return []; + }, [ blockName, supportedBindingsFromWP ] ); + function updateFieldBinding( target: string, field: string ): void { if ( ! field ) { removeBinding( target ); @@ -190,9 +192,6 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { } ); } - // Get the list of bindable attributes for this block - const supportedAttributes = getSupportedAttributesForBlock( blockName ); - // If no attributes support bindings, don't render anything if ( supportedAttributes.length === 0 ) { return null; @@ -208,8 +207,9 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { const args = attributes.metadata?.bindings?.[ attributeName ]?.args; return ( -
+ <> { attributeName === 'content' && fieldValue && ( updateFieldLabel( attributeName, showLabel ) } /> ) } -
+ ); } ) } From 145ca17b9c68ee62e4306f77dd6a9125c0ad060b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:02:36 +0000 Subject: [PATCH 8/9] Fix Fragment key prop and use ts-expect-error - Add key prop to Fragment wrapper in map for proper React reconciliation - Change @ts-ignore to @ts-expect-error for experimental API suppression Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- .../components/BlockBindingControls.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/blocks/remote-data-container/components/BlockBindingControls.tsx b/src/blocks/remote-data-container/components/BlockBindingControls.tsx index dd96baa1a..bb95aff51 100644 --- a/src/blocks/remote-data-container/components/BlockBindingControls.tsx +++ b/src/blocks/remote-data-container/components/BlockBindingControls.tsx @@ -1,7 +1,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; import { CheckboxControl, SelectControl } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; +import { Fragment, useMemo } from '@wordpress/element'; import { BUTTON_LINK_TARGET_FIELD_TYPES, @@ -126,7 +126,7 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { ( select ) => { try { const editorSettings = select( blockEditorStore )?.getSettings?.(); - // @ts-ignore - __experimentalBlockBindingsSupportedAttributes is not in types + // @ts-expect-error - __experimentalBlockBindingsSupportedAttributes is not in types yet return editorSettings?.__experimentalBlockBindingsSupportedAttributes ?? {}; } catch ( error ) { return {}; @@ -207,9 +207,8 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { const args = attributes.metadata?.bindings?.[ attributeName ]?.args; return ( - <> + { attributeName === 'content' && fieldValue && ( updateFieldLabel( attributeName, showLabel ) } /> ) } - + ); } ) } From e3b7558f989c79bc45190f55d21e9075af8e6d50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:03:10 +0000 Subject: [PATCH 9/9] Remove trailing whitespace Co-authored-by: maxschmeling <112691+maxschmeling@users.noreply.github.com> --- .../components/BlockBindingControls.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/blocks/remote-data-container/components/BlockBindingControls.tsx b/src/blocks/remote-data-container/components/BlockBindingControls.tsx index bb95aff51..f6cc9c84f 100644 --- a/src/blocks/remote-data-container/components/BlockBindingControls.tsx +++ b/src/blocks/remote-data-container/components/BlockBindingControls.tsx @@ -70,22 +70,22 @@ function getFieldTypesForAttribute( blockName: string, attributeName: string ): if ( blockName === 'remote-data-blocks/remote-html' && attributeName === 'content' ) { return HTML_FIELD_TYPES; } - + // Map attribute names to their appropriate field types const attributeFieldTypeMap: Record< string, string[] > = { // Text content attributes content: TEXT_FIELD_TYPES, - + // Image attributes url: IMAGE_URL_FIELD_TYPES, alt: IMAGE_ALT_FIELD_TYPES, title: IMAGE_TITLE_FIELD_TYPES, - + // Button attributes text: BUTTON_TEXT_FIELD_TYPES, linkTarget: BUTTON_LINK_TARGET_FIELD_TYPES, rel: BUTTON_REL_FIELD_TYPES, - + // HTML content html: HTML_FIELD_TYPES, }; @@ -141,13 +141,13 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { if ( supportedBindingsFromWP[ blockName ] ) { return supportedBindingsFromWP[ blockName ]; } - + // Fallback for custom blocks like remote-data-blocks/remote-html // that may not register with WordPress but still support bindings if ( blockName === 'remote-data-blocks/remote-html' ) { return [ 'content' ]; } - + return []; }, [ blockName, supportedBindingsFromWP ] ); @@ -175,7 +175,7 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { function updateFieldLabel( target: string, showLabel: boolean ): void { const currentField = attributes.metadata?.bindings?.[ target ]?.args?.field; - + if ( ! currentField ) { return; } @@ -183,7 +183,7 @@ export function BlockBindingControls( props: BlockBindingControlsProps ) { const label = showLabel ? Object.entries( availableBindings ).find( ( [ key ] ) => key === currentField )?.[ 1 ]?.name : undefined; - + const currentArgs = attributes.metadata?.bindings?.[ target ]?.args ?? {}; updateBinding( target, { ...currentArgs, field: currentField, label } ); sendTracksEvent( 'remote_data_container_actions', {