Skip to content

Commit ce93223

Browse files
talldanramonjdandrewserong
authored
Block Fields: Remove normalization code and tidy up (#74532)
* Remove normalization functions * Update link field to remove mapping * Update media field * Update rich text * Fix allowedTypes * Remove defaultValues config and config prop that seems to not be working * Improve clarity of createConfiguredControl * Move event handler to after early return * Remove hideLabelFromVision prop * Remove use of mapping in media onChange * Add getValue/setValue comment * Fix use of config in MediaThumbnail ---- Co-authored-by: talldan <talldanwp@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
1 parent f394c53 commit ce93223

6 files changed

Lines changed: 96 additions & 343 deletions

File tree

packages/block-editor/src/hooks/block-fields/index.js

Lines changed: 49 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -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">

packages/block-editor/src/hooks/block-fields/link/index.js

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,6 @@ export default function Link( { data, field, onChange, config = {} } ) {
7373
isControl: true,
7474
} );
7575
const { fieldDef } = config;
76-
const updateAttributes = ( newValue ) => {
77-
const mappedChanges = field.setValue( { item: data, value: newValue } );
78-
onChange( mappedChanges );
79-
};
80-
8176
const value = field.getValue( { item: data } );
8277
const url = value?.url;
8378
const rel = value?.rel || '';
@@ -145,30 +140,12 @@ export default function Link( { data, field, onChange, config = {} } ) {
145140
...newValues,
146141
} );
147142

148-
// Build update object dynamically based on what's in the mapping
149-
const updateValue = { ...value };
150-
151-
if ( fieldDef?.mapping ) {
152-
Object.keys( fieldDef.mapping ).forEach(
153-
( key ) => {
154-
if ( key === 'href' || key === 'url' ) {
155-
updateValue[ key ] =
156-
updatedAttrs.url;
157-
} else if ( key === 'rel' ) {
158-
updateValue[ key ] =
159-
updatedAttrs.rel;
160-
} else if (
161-
key === 'target' ||
162-
key === 'linkTarget'
163-
) {
164-
updateValue[ key ] =
165-
updatedAttrs.linkTarget;
166-
}
167-
}
168-
);
169-
}
170-
171-
updateAttributes( updateValue );
143+
onChange(
144+
field.setValue( {
145+
item: data,
146+
value: updatedAttrs,
147+
} )
148+
);
172149
} }
173150
onRemove={ () => {
174151
// Remove all link-related properties based on what's in the mapping
@@ -177,20 +154,17 @@ export default function Link( { data, field, onChange, config = {} } ) {
177154
if ( fieldDef?.mapping ) {
178155
Object.keys( fieldDef.mapping ).forEach(
179156
( key ) => {
180-
if (
181-
key === 'href' ||
182-
key === 'url' ||
183-
key === 'rel' ||
184-
key === 'target' ||
185-
key === 'linkTarget'
186-
) {
187-
removeValue[ key ] = undefined;
188-
}
157+
removeValue[ key ] = undefined;
189158
}
190159
);
191160
}
192161

193-
updateAttributes( removeValue );
162+
onChange(
163+
field.setValue( {
164+
item: data,
165+
value: removeValue,
166+
} )
167+
);
194168
} }
195169
/>
196170
</Popover>

0 commit comments

Comments
 (0)