@@ -39,135 +39,20 @@ const CONTROLS = {
3939 * Creates a configured control component that wraps a custom control
4040 * and passes configuration as props.
4141 *
42- * @param {Object } config - The control configuration
43- * @param {string } config.control - The control type (key in CONTROLS map)
42+ * @param {Component } ControlComponent The React component for the control.
43+ * @param {string } type The type of control.
44+ * @param {Object } config The control configuration passed as a prop.
45+ *
4446 * @return {Function } A wrapped control component
4547 */
46- function createConfiguredControl ( config ) {
47- const { control, ...controlConfig } = config ;
48- const ControlComponent = CONTROLS [ control ] ;
49-
48+ function createConfiguredControl ( ControlComponent , type , config ) {
5049 if ( ! ControlComponent ) {
51- throw new Error ( `Control type "${ control } " not found` ) ;
50+ throw new Error ( `Control type "${ type } " not found` ) ;
5251 }
5352
5453 return function ConfiguredControl ( props ) {
55- return < ControlComponent { ...props } config = { controlConfig } /> ;
56- } ;
57- }
58-
59- /**
60- * Normalize a media value to a canonical structure.
61- * Only includes properties that are present in the field's mapping (if provided).
62- *
63- * @param {Object } value - The mapped value from the block attributes (with canonical keys)
64- * @param {Object } fieldDef - Optional field definition containing the mapping
65- * @return {Object } Normalized media value with canonical properties
66- */
67- function normalizeMediaValue ( value , fieldDef ) {
68- const defaults = {
69- id : null ,
70- url : '' ,
71- caption : '' ,
72- alt : '' ,
73- type : 'image' ,
74- poster : '' ,
75- featuredImage : false ,
76- link : '' ,
77- } ;
78-
79- const result = { } ;
80-
81- // If there's a mapping, only include properties that are in it
82- if ( fieldDef ?. mapping ) {
83- Object . keys ( fieldDef . mapping ) . forEach ( ( key ) => {
84- result [ key ] = value ?. [ key ] ?? defaults [ key ] ?? '' ;
85- } ) ;
86- return result ;
87- }
88-
89- // Without mapping, include all default properties
90- Object . keys ( defaults ) . forEach ( ( key ) => {
91- result [ key ] = value ?. [ key ] ?? defaults [ key ] ;
92- } ) ;
93- return result ;
94- }
95-
96- /**
97- * Denormalize a media value from canonical structure back to mapped keys.
98- * Only includes properties that are present in the field's mapping.
99- *
100- * @param {Object } value - The normalized media value
101- * @param {Object } fieldDef - The field definition containing the mapping
102- * @return {Object } Value with only mapped properties
103- */
104- function denormalizeMediaValue ( value , fieldDef ) {
105- if ( ! fieldDef . mapping ) {
106- return value ;
107- }
108-
109- const result = { } ;
110- Object . entries ( fieldDef . mapping ) . forEach ( ( [ key ] ) => {
111- if ( key in value ) {
112- result [ key ] = value [ key ] ;
113- }
114- } ) ;
115- return result ;
116- }
117-
118- /**
119- * Normalize a link value to a canonical structure.
120- * Only includes properties that are present in the field's mapping (if provided).
121- *
122- * @param {Object } value - The mapped value from the block attributes (with canonical keys)
123- * @param {Object } fieldDef - Optional field definition containing the mapping
124- * @return {Object } Normalized link value with canonical properties
125- */
126- function normalizeLinkValue ( value , fieldDef ) {
127- const defaults = {
128- url : '' ,
129- rel : '' ,
130- linkTarget : '' ,
131- destination : '' ,
54+ return < ControlComponent { ...props } config = { config } /> ;
13255 } ;
133-
134- const result = { } ;
135-
136- // If there's a mapping, only include properties that are in it
137- if ( fieldDef ?. mapping ) {
138- Object . keys ( fieldDef . mapping ) . forEach ( ( key ) => {
139- result [ key ] = value ?. [ key ] ?? defaults [ key ] ?? '' ;
140- } ) ;
141- return result ;
142- }
143-
144- // Without mapping, include all default properties
145- Object . keys ( defaults ) . forEach ( ( key ) => {
146- result [ key ] = value ?. [ key ] ?? defaults [ key ] ;
147- } ) ;
148- return result ;
149- }
150-
151- /**
152- * Denormalize a link value from canonical structure back to mapped keys.
153- * Only includes properties that are present in the field's mapping.
154- *
155- * @param {Object } value - The normalized link value
156- * @param {Object } fieldDef - The field definition containing the mapping
157- * @return {Object } Value with only mapped properties
158- */
159- function denormalizeLinkValue ( value , fieldDef ) {
160- if ( ! fieldDef . mapping ) {
161- return value ;
162- }
163-
164- const result = { } ;
165- Object . entries ( fieldDef . mapping ) . forEach ( ( [ key ] ) => {
166- if ( key in value ) {
167- result [ key ] = value [ key ] ;
168- }
169- } ) ;
170- return result ;
17156}
17257
17358/**
@@ -218,100 +103,63 @@ function BlockFields( {
218103 }
219104
220105 return blockTypeFields . map ( ( fieldDef ) => {
221- const ControlComponent = CONTROLS [ fieldDef . type ] ;
222-
223- const defaultValues = { } ;
224- if ( fieldDef . mapping && blockType ?. attributes ) {
225- Object . entries ( fieldDef . mapping ) . forEach (
226- ( [ key , attrKey ] ) => {
227- defaultValues [ key ] =
228- blockType . attributes [ attrKey ] ?. defaultValue ??
229- undefined ;
230- }
231- ) ;
232- }
233-
234106 const field = {
235107 id : fieldDef . id ,
236108 label : fieldDef . label ,
237109 type : fieldDef . type , // Use the field's type; DataForm will use built-in or custom Edit
238- config : { ...fieldDef . args , defaultValues } ,
239- hideLabelFromVision : fieldDef . id === 'content' ,
240- // getValue and setValue handle the mapping to block attributes
241- getValue : ( { item } ) => {
242- if ( fieldDef . mapping ) {
243- // Extract mapped properties from the block attributes
244- const mappedValue = { } ;
245- Object . entries ( fieldDef . mapping ) . forEach (
246- ( [ key , attrKey ] ) => {
247- mappedValue [ key ] = item [ attrKey ] ;
248- }
249- ) ;
110+ } ;
250111
251- // Normalize to canonical structure based on field type
252- if ( fieldDef . type === 'media' ) {
253- return normalizeMediaValue ( mappedValue , fieldDef ) ;
112+ // If the field defines a `mapping`, then custom `getValue` and `setValue`
113+ // implementations are provided.
114+ // These functions map from the inconsistent attribute keys found on blocks
115+ // to consistent keys that the field can use internally (and back again).
116+ // When `mapping` isn't provided, we can use the field API's default
117+ // implementation of these functions.
118+ if ( fieldDef . mapping ) {
119+ field . getValue = ( { item } ) => {
120+ // Extract mapped properties from the block attributes
121+ const mappedValue = { } ;
122+ Object . entries ( fieldDef . mapping ) . forEach (
123+ ( [ key , attrKey ] ) => {
124+ mappedValue [ key ] = item [ attrKey ] ;
254125 }
255- if ( fieldDef . type === 'link' ) {
256- return normalizeLinkValue ( mappedValue , fieldDef ) ;
257- }
258-
259- // For other types, return as-is
260- return mappedValue ;
261- }
262- // For simple id-based fields, use the id as the attribute key
263- return item [ fieldDef . id ] ;
264- } ,
265- setValue : ( { item, value } ) => {
266- if ( fieldDef . mapping ) {
267- // Denormalize from canonical structure back to mapped keys
268- let denormalizedValue = value ;
269- if ( fieldDef . type === 'media' ) {
270- denormalizedValue = denormalizeMediaValue (
271- value ,
272- fieldDef
273- ) ;
274- } else if ( fieldDef . type === 'link' ) {
275- denormalizedValue = denormalizeLinkValue (
276- value ,
277- fieldDef
278- ) ;
126+ ) ;
127+ return mappedValue ;
128+ } ;
129+ field . setValue = ( { value } ) => {
130+ const attributeUpdates = { } ;
131+ Object . entries ( fieldDef . mapping ) . forEach (
132+ ( [ key , attrKey ] ) => {
133+ attributeUpdates [ attrKey ] = value [ key ] ;
279134 }
280-
281- // Build an object with all mapped attributes
282- const updates = { } ;
283- Object . entries ( fieldDef . mapping ) . forEach (
284- ( [ key , attrKey ] ) => {
285- // If key is explicitly in value, use it (even if undefined to allow clearing)
286- // Otherwise, preserve the old value
287- if ( key in denormalizedValue ) {
288- updates [ attrKey ] =
289- denormalizedValue [ key ] ;
290- } else {
291- updates [ attrKey ] = item [ attrKey ] ;
292- }
293- }
294- ) ;
295- return updates ;
296- }
297- // For simple id-based fields, use the id as the attribute key
298- return { [ fieldDef . id ] : value } ;
299- } ,
300- } ;
135+ ) ;
136+ return attributeUpdates ;
137+ } ;
138+ }
301139
302140 // Only add custom Edit component if one exists for this type
141+ const ControlComponent = CONTROLS [ fieldDef . type ] ;
303142 if ( ControlComponent ) {
304143 // Use EditConfig pattern: Edit is an object with control type and config props
305- field . Edit = createConfiguredControl ( {
306- control : fieldDef . type ,
307- clientId,
308- fieldDef,
309- } ) ;
144+ field . Edit = createConfiguredControl (
145+ ControlComponent ,
146+ fieldDef . type ,
147+ {
148+ clientId,
149+ fieldDef,
150+ }
151+ ) ;
310152 }
311153
312154 return field ;
313155 } ) ;
314- } , [ blockTypeFields , blockType ?. attributes , clientId ] ) ;
156+ } , [ blockTypeFields , clientId ] ) ;
157+
158+ if ( ! blockTypeFields ?. length ) {
159+ // TODO - we might still want to show a placeholder for blocks with no fields.
160+ // for example, a way to select the block.
161+ return null ;
162+ }
315163
316164 const handleToggleField = ( fieldId ) => {
317165 setForm ( ( prev ) => {
@@ -329,12 +177,6 @@ function BlockFields( {
329177 } ) ;
330178 } ;
331179
332- if ( ! blockTypeFields ?. length ) {
333- // TODO - we might still want to show a placeholder for blocks with no fields.
334- // for example, a way to select the block.
335- return null ;
336- }
337-
338180 return (
339181 < div className = "block-editor-block-fields__container" >
340182 < div className = "block-editor-block-fields__header" >
0 commit comments