11/**
22 * WordPress dependencies
33 */
4- import { useState , useEffect , useCallback , useMemo } from '@wordpress/element' ;
4+ import { useCallback , useMemo } from '@wordpress/element' ;
55import { addFilter } from '@wordpress/hooks' ;
66import { createHigherOrderComponent } from '@wordpress/compose' ;
77import {
@@ -14,152 +14,76 @@ import {
1414 __experimentalToolsPanelItem as ToolsPanelItem ,
1515} from '@wordpress/components' ;
1616import { __ } from '@wordpress/i18n' ;
17- import { useSelect } from '@wordpress/data' ;
18- import { store as coreDataStore } from '@wordpress/core-data' ;
19- import { store as editorStore } from '@wordpress/editor' ;
20-
21- // These constant and the function above have been copied from Gutenberg. It should be public, eventually.
22-
23- const BLOCK_BINDINGS_CONFIG = {
24- 'core/paragraph' : {
25- content : [ 'text' , 'textarea' , 'date_picker' , 'number' , 'range' ] ,
26- } ,
27- 'core/heading' : {
28- content : [ 'text' , 'textarea' , 'date_picker' , 'number' , 'range' ] ,
29- } ,
30- 'core/image' : {
31- id : [ 'image' ] ,
32- url : [ 'image' ] ,
33- title : [ 'image' ] ,
34- alt : [ 'image' ] ,
35- } ,
36- 'core/button' : {
37- url : [ 'url' ] ,
38- text : [ 'text' , 'checkbox' , 'select' , 'date_picker' ] ,
39- linkTarget : [ 'text' , 'checkbox' , 'select' ] ,
40- rel : [ 'text' , 'checkbox' , 'select' ] ,
41- } ,
42- } ;
4317
4418/**
45- * Gets the bindable attributes for a given block.
46- *
47- * @param {string } blockName The name of the block.
48- *
49- * @return {string[] } The bindable attributes for the block.
19+ * Internal dependencies
5020 */
51- function getBindableAttributes ( blockName ) {
52- const config = BLOCK_BINDINGS_CONFIG [ blockName ] ;
53- return config ? Object . keys ( config ) : [ ] ;
54- }
21+ import { BINDING_SOURCE } from './constants' ;
22+ import {
23+ getBindableAttributes ,
24+ getFilteredFieldOptions ,
25+ canUseUnifiedBinding ,
26+ fieldsToOptions ,
27+ } from './utils' ;
28+ import {
29+ useSiteEditorContext ,
30+ usePostEditorFields ,
31+ useSiteEditorFields ,
32+ useBoundFields ,
33+ } from './hooks' ;
5534
5635/**
57- * Add custom controls to all blocks
36+ * Add custom block binding controls to supported blocks.
37+ *
38+ * @since 6.5.0
5839 */
5940const withCustomControls = createHigherOrderComponent ( ( BlockEdit ) => {
6041 return ( props ) => {
6142 const bindableAttributes = getBindableAttributes ( props . name ) ;
6243 const { updateBlockBindings, removeAllBlockBindings } =
6344 useBlockBindingsUtils ( ) ;
6445
65- // Get ACF fields for current post
66- const fields = useSelect ( ( select ) => {
67- const { getEditedEntityRecord } = select ( coreDataStore ) ;
68- const { getCurrentPostType, getCurrentPostId } =
69- select ( editorStore ) ;
46+ // Get editor context
47+ const { isSiteEditor, templatePostType } = useSiteEditorContext ( ) ;
7048
71- const postType = getCurrentPostType ( ) ;
72- const postId = getCurrentPostId ( ) ;
49+ // Get fields based on editor context
50+ const postEditorFields = usePostEditorFields ( ) ;
51+ const { fields : siteEditorFields } =
52+ useSiteEditorFields ( templatePostType ) ;
7353
74- if ( ! postType || ! postId ) return { } ;
54+ // Use appropriate fields based on context
55+ const activeFields = isSiteEditor ? siteEditorFields : postEditorFields ;
7556
76- const record = getEditedEntityRecord (
77- 'postType' ,
78- postType ,
79- postId
80- ) ;
57+ // Convert fields to options format
58+ const allFieldOptions = useMemo (
59+ ( ) => fieldsToOptions ( activeFields ) ,
60+ [ activeFields ]
61+ ) ;
8162
82- // Extract fields that end with '_source' (simplified)
83- const sourcedFields = { } ;
84- Object . entries ( record ?. acf || { } ) . forEach ( ( [ key , value ] ) => {
85- if ( key . endsWith ( '_source' ) ) {
86- const baseFieldName = key . replace ( '_source' , '' ) ;
87- if ( record ?. acf . hasOwnProperty ( baseFieldName ) ) {
88- sourcedFields [ baseFieldName ] = value ;
89- }
90- }
91- } ) ;
92- return sourcedFields ;
93- } , [ ] ) ;
63+ // Track bound fields
64+ const { boundFields, setBoundFields } = useBoundFields (
65+ props . attributes
66+ ) ;
9467
95- // Get filtered field options for an attribute
96- const getFieldOptions = useCallback (
68+ // Get filtered field options for a specific attribute
69+ const getAttributeFieldOptions = useCallback (
9770 ( attribute = null ) => {
98- if ( ! fields || Object . keys ( fields ) . length === 0 ) return [ ] ;
99-
100- const blockConfig = BLOCK_BINDINGS_CONFIG [ props . name ] ;
101- let allowedTypes = null ;
102-
103- if ( blockConfig ) {
104- allowedTypes = attribute
105- ? blockConfig [ attribute ]
106- : Object . values ( blockConfig ) . flat ( ) ;
107- }
108-
109- return Object . entries ( fields )
110- . filter (
111- ( [ , fieldConfig ] ) =>
112- ! allowedTypes ||
113- allowedTypes . includes ( fieldConfig . type )
114- )
115- . map ( ( [ fieldName , fieldConfig ] ) => ( {
116- value : fieldName ,
117- label : fieldConfig . label ,
118- } ) ) ;
71+ return getFilteredFieldOptions (
72+ allFieldOptions ,
73+ props . name ,
74+ attribute
75+ ) ;
11976 } ,
120- [ fields , props . name ]
77+ [ allFieldOptions , props . name ]
12178 ) ;
12279
123- // Check if all attributes use the same field types (for "all attributes" mode)
124- const canUseAllAttributesMode = useMemo ( ( ) => {
125- if ( ! bindableAttributes || bindableAttributes . length <= 1 )
126- return false ;
127-
128- const blockConfig = BLOCK_BINDINGS_CONFIG [ props . name ] ;
129- if ( ! blockConfig ) return false ;
130-
131- const firstAttributeTypes =
132- blockConfig [ bindableAttributes [ 0 ] ] || [ ] ;
133- return bindableAttributes . every ( ( attr ) => {
134- const attrTypes = blockConfig [ attr ] || [ ] ;
135- return (
136- attrTypes . length === firstAttributeTypes . length &&
137- attrTypes . every ( ( type ) =>
138- firstAttributeTypes . includes ( type )
139- )
140- ) ;
141- } ) ;
142- } , [ bindableAttributes , props . name ] ) ;
143-
144- // Track bound fields
145- const [ boundFields , setBoundFields ] = useState ( { } ) ;
146-
147- // Sync with current bindings
148- useEffect ( ( ) => {
149- const currentBindings = props . attributes ?. metadata ?. bindings || { } ;
150- const newBoundFields = { } ;
151-
152- Object . keys ( currentBindings ) . forEach ( ( attribute ) => {
153- if ( currentBindings [ attribute ] ?. args ?. key ) {
154- newBoundFields [ attribute ] =
155- currentBindings [ attribute ] . args . key ;
156- }
157- } ) ;
158-
159- setBoundFields ( newBoundFields ) ;
160- } , [ props . attributes ?. metadata ?. bindings ] ) ;
80+ // Check if all attributes can use unified binding mode
81+ const canUseAllAttributesMode = useMemo (
82+ ( ) => canUseUnifiedBinding ( props . name , bindableAttributes ) ,
83+ [ props . name , bindableAttributes ]
84+ ) ;
16185
162- // Handle field selection
86+ // Handle field selection changes
16387 const handleFieldChange = useCallback (
16488 ( attribute , value ) => {
16589 if ( Array . isArray ( attribute ) ) {
@@ -171,7 +95,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => {
17195 newBoundFields [ attr ] = value ;
17296 bindings [ attr ] = value
17397 ? {
174- source : 'acf/field' ,
98+ source : BINDING_SOURCE ,
17599 args : { key : value } ,
176100 }
177101 : undefined ;
@@ -188,29 +112,37 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => {
188112 updateBlockBindings ( {
189113 [ attribute ] : value
190114 ? {
191- source : 'acf/field' ,
115+ source : BINDING_SOURCE ,
192116 args : { key : value } ,
193117 }
194118 : undefined ,
195119 } ) ;
196120 }
197121 } ,
198- [ boundFields , updateBlockBindings ]
122+ [ boundFields , setBoundFields , updateBlockBindings ]
199123 ) ;
200124
201- // Handle reset
125+ // Handle reset all bindings
202126 const handleReset = useCallback ( ( ) => {
203127 removeAllBlockBindings ( ) ;
204128 setBoundFields ( { } ) ;
205- } , [ removeAllBlockBindings ] ) ;
206-
207- // Don't show if no fields or attributes
208- const fieldOptions = getFieldOptions ( ) ;
209- if ( fieldOptions . length === 0 || ! bindableAttributes ) {
210- return < BlockEdit { ...props } /> ;
211- }
129+ } , [ removeAllBlockBindings , setBoundFields ] ) ;
130+
131+ // Determine if we should show the panel
132+ const shouldShowPanel = useMemo ( ( ) => {
133+ // In site editor, show panel if block has bindable attributes
134+ if ( isSiteEditor ) {
135+ return bindableAttributes && bindableAttributes . length > 0 ;
136+ }
137+ // In post editor, only show if we have fields available
138+ return (
139+ allFieldOptions . length > 0 &&
140+ bindableAttributes &&
141+ bindableAttributes . length > 0
142+ ) ;
143+ } , [ isSiteEditor , allFieldOptions , bindableAttributes ] ) ;
212144
213- if ( bindableAttributes . length === 0 ) {
145+ if ( ! shouldShowPanel ) {
214146 return < BlockEdit { ...props } /> ;
215147 }
216148
@@ -239,7 +171,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => {
239171 null
240172 )
241173 }
242- isShownByDefault = { true }
174+ isShownByDefault
243175 >
244176 < ComboboxControl
245177 label = { __ (
@@ -250,7 +182,7 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => {
250182 'Select a field' ,
251183 'secure-custom-fields'
252184 ) }
253- options = { getFieldOptions ( ) }
185+ options = { getAttributeFieldOptions ( ) }
254186 value = {
255187 boundFields [
256188 bindableAttributes [ 0 ]
@@ -269,23 +201,25 @@ const withCustomControls = createHigherOrderComponent( ( BlockEdit ) => {
269201 ) : (
270202 bindableAttributes . map ( ( attribute ) => (
271203 < ToolsPanelItem
272- key = { `scf-field -${ attribute } ` }
204+ key = { `scf-binding -${ attribute } ` }
273205 hasValue = { ( ) =>
274206 ! ! boundFields [ attribute ]
275207 }
276208 label = { attribute }
277209 onDeselect = { ( ) =>
278210 handleFieldChange ( attribute , null )
279211 }
280- isShownByDefault = { true }
212+ isShownByDefault
281213 >
282214 < ComboboxControl
283215 label = { attribute }
284216 placeholder = { __ (
285217 'Select a field' ,
286218 'secure-custom-fields'
287219 ) }
288- options = { getFieldOptions ( attribute ) }
220+ options = { getAttributeFieldOptions (
221+ attribute
222+ ) }
289223 value = { boundFields [ attribute ] || '' }
290224 onChange = { ( value ) =>
291225 handleFieldChange (
0 commit comments