@wordpress/block-editor 15.10.1-next.ba3aee3a2.0 → 15.10.1-next.v.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/build/components/block-bindings/attribute-control.cjs +1 -1
  2. package/build/components/block-bindings/attribute-control.cjs.map +1 -1
  3. package/build/components/block-bindings/source-fields-list.cjs +1 -1
  4. package/build/components/block-bindings/source-fields-list.cjs.map +1 -1
  5. package/build/components/block-tools/index.cjs +82 -70
  6. package/build/components/block-tools/index.cjs.map +2 -2
  7. package/build/components/block-visibility/block-visibility-info.cjs +0 -59
  8. package/build/components/block-visibility/block-visibility-info.cjs.map +3 -3
  9. package/build/components/block-visibility/constants.cjs +10 -5
  10. package/build/components/block-visibility/constants.cjs.map +2 -2
  11. package/build/components/block-visibility/index.cjs +13 -5
  12. package/build/components/block-visibility/index.cjs.map +3 -3
  13. package/build/components/block-visibility/modal.cjs +397 -0
  14. package/build/components/block-visibility/modal.cjs.map +7 -0
  15. package/build/components/block-visibility/toolbar.cjs +1 -1
  16. package/build/components/block-visibility/toolbar.cjs.map +2 -2
  17. package/build/components/block-visibility/use-block-visibility.cjs +13 -17
  18. package/build/components/block-visibility/use-block-visibility.cjs.map +2 -2
  19. package/build/components/block-visibility/utils.cjs +81 -0
  20. package/build/components/block-visibility/utils.cjs.map +7 -0
  21. package/build/components/block-visibility/viewport-menu-item.cjs +61 -0
  22. package/build/components/block-visibility/viewport-menu-item.cjs.map +7 -0
  23. package/build/components/block-visibility/viewport-toolbar.cjs +89 -0
  24. package/build/components/block-visibility/viewport-toolbar.cjs.map +7 -0
  25. package/build/components/inner-blocks/use-inner-block-template-sync.cjs +1 -1
  26. package/build/components/inner-blocks/use-inner-block-template-sync.cjs.map +1 -1
  27. package/build/components/inserter/menu.cjs +6 -2
  28. package/build/components/inserter/menu.cjs.map +2 -2
  29. package/build/components/list-view/block-select-button.cjs +2 -2
  30. package/build/components/list-view/block-select-button.cjs.map +2 -2
  31. package/build/components/list-view/block.cjs +39 -22
  32. package/build/components/list-view/block.cjs.map +2 -2
  33. package/build/components/rich-text/index.cjs +1 -1
  34. package/build/components/rich-text/index.cjs.map +1 -1
  35. package/build/components/url-input/index.cjs +2 -0
  36. package/build/components/url-input/index.cjs.map +2 -2
  37. package/build/components/use-block-commands/index.cjs +1 -1
  38. package/build/components/use-block-commands/index.cjs.map +2 -2
  39. package/build/hooks/block-fields/index.cjs +75 -166
  40. package/build/hooks/block-fields/index.cjs.map +2 -2
  41. package/build/hooks/block-fields/link/index.cjs +13 -23
  42. package/build/hooks/block-fields/link/index.cjs.map +2 -2
  43. package/build/hooks/block-fields/media/index.cjs +32 -58
  44. package/build/hooks/block-fields/media/index.cjs.map +2 -2
  45. package/build/hooks/block-fields/rich-text/index.cjs +1 -5
  46. package/build/hooks/block-fields/rich-text/index.cjs.map +2 -2
  47. package/build/hooks/cross-origin-isolation.cjs +102 -0
  48. package/build/hooks/cross-origin-isolation.cjs.map +7 -0
  49. package/build/hooks/index.cjs +1 -0
  50. package/build/hooks/index.cjs.map +2 -2
  51. package/build/layouts/flex.cjs +6 -2
  52. package/build/layouts/flex.cjs.map +2 -2
  53. package/build/store/private-selectors.cjs +33 -1
  54. package/build/store/private-selectors.cjs.map +3 -3
  55. package/build/store/reducer.cjs +1 -1
  56. package/build/store/reducer.cjs.map +1 -1
  57. package/build/store/selectors.cjs +7 -8
  58. package/build/store/selectors.cjs.map +2 -2
  59. package/build-module/components/block-bindings/attribute-control.mjs +1 -1
  60. package/build-module/components/block-bindings/attribute-control.mjs.map +1 -1
  61. package/build-module/components/block-bindings/source-fields-list.mjs +1 -1
  62. package/build-module/components/block-bindings/source-fields-list.mjs.map +1 -1
  63. package/build-module/components/block-tools/index.mjs +85 -73
  64. package/build-module/components/block-tools/index.mjs.map +2 -2
  65. package/build-module/components/block-visibility/block-visibility-info.mjs +0 -59
  66. package/build-module/components/block-visibility/block-visibility-info.mjs.map +3 -3
  67. package/build-module/components/block-visibility/constants.mjs +8 -4
  68. package/build-module/components/block-visibility/constants.mjs.map +2 -2
  69. package/build-module/components/block-visibility/index.mjs +13 -6
  70. package/build-module/components/block-visibility/index.mjs.map +2 -2
  71. package/build-module/components/block-visibility/modal.mjs +384 -0
  72. package/build-module/components/block-visibility/modal.mjs.map +7 -0
  73. package/build-module/components/block-visibility/toolbar.mjs +1 -1
  74. package/build-module/components/block-visibility/toolbar.mjs.map +2 -2
  75. package/build-module/components/block-visibility/use-block-visibility.mjs +13 -13
  76. package/build-module/components/block-visibility/use-block-visibility.mjs.map +2 -2
  77. package/build-module/components/block-visibility/utils.mjs +55 -0
  78. package/build-module/components/block-visibility/utils.mjs.map +7 -0
  79. package/build-module/components/block-visibility/viewport-menu-item.mjs +40 -0
  80. package/build-module/components/block-visibility/viewport-menu-item.mjs.map +7 -0
  81. package/build-module/components/block-visibility/viewport-toolbar.mjs +68 -0
  82. package/build-module/components/block-visibility/viewport-toolbar.mjs.map +7 -0
  83. package/build-module/components/inner-blocks/use-inner-block-template-sync.mjs +1 -1
  84. package/build-module/components/inner-blocks/use-inner-block-template-sync.mjs.map +1 -1
  85. package/build-module/components/inserter/menu.mjs +6 -2
  86. package/build-module/components/inserter/menu.mjs.map +2 -2
  87. package/build-module/components/list-view/block-select-button.mjs +2 -2
  88. package/build-module/components/list-view/block-select-button.mjs.map +2 -2
  89. package/build-module/components/list-view/block.mjs +39 -22
  90. package/build-module/components/list-view/block.mjs.map +2 -2
  91. package/build-module/components/rich-text/index.mjs +1 -1
  92. package/build-module/components/rich-text/index.mjs.map +1 -1
  93. package/build-module/components/url-input/index.mjs +2 -0
  94. package/build-module/components/url-input/index.mjs.map +2 -2
  95. package/build-module/components/use-block-commands/index.mjs +1 -1
  96. package/build-module/components/use-block-commands/index.mjs.map +2 -2
  97. package/build-module/hooks/block-fields/index.mjs +75 -166
  98. package/build-module/hooks/block-fields/index.mjs.map +2 -2
  99. package/build-module/hooks/block-fields/link/index.mjs +13 -23
  100. package/build-module/hooks/block-fields/link/index.mjs.map +2 -2
  101. package/build-module/hooks/block-fields/media/index.mjs +32 -58
  102. package/build-module/hooks/block-fields/media/index.mjs.map +2 -2
  103. package/build-module/hooks/block-fields/rich-text/index.mjs +1 -5
  104. package/build-module/hooks/block-fields/rich-text/index.mjs.map +2 -2
  105. package/build-module/hooks/cross-origin-isolation.mjs +100 -0
  106. package/build-module/hooks/cross-origin-isolation.mjs.map +7 -0
  107. package/build-module/hooks/index.mjs +1 -0
  108. package/build-module/hooks/index.mjs.map +2 -2
  109. package/build-module/layouts/flex.mjs +6 -2
  110. package/build-module/layouts/flex.mjs.map +2 -2
  111. package/build-module/store/private-selectors.mjs +34 -1
  112. package/build-module/store/private-selectors.mjs.map +2 -2
  113. package/build-module/store/reducer.mjs +1 -1
  114. package/build-module/store/reducer.mjs.map +1 -1
  115. package/build-module/store/selectors.mjs +7 -8
  116. package/build-module/store/selectors.mjs.map +2 -2
  117. package/build-style/content-rtl.css +4 -1
  118. package/build-style/content.css +4 -1
  119. package/build-style/style-rtl.css +48 -0
  120. package/build-style/style.css +48 -0
  121. package/package.json +39 -39
  122. package/src/components/block-bindings/attribute-control.js +1 -1
  123. package/src/components/block-bindings/source-fields-list.js +1 -1
  124. package/src/components/block-list/content.scss +4 -1
  125. package/src/components/block-tools/index.js +45 -33
  126. package/src/components/block-visibility/block-visibility-info.js +0 -1
  127. package/src/components/block-visibility/constants.js +7 -3
  128. package/src/components/block-visibility/index.js +21 -3
  129. package/src/components/block-visibility/modal.js +358 -0
  130. package/src/components/block-visibility/style.scss +58 -0
  131. package/src/components/block-visibility/test/use-block-visibility.js +12 -56
  132. package/src/components/block-visibility/test/utils.js +266 -0
  133. package/src/components/block-visibility/toolbar.js +1 -1
  134. package/src/components/block-visibility/use-block-visibility.js +18 -21
  135. package/src/components/block-visibility/utils.js +95 -0
  136. package/src/components/block-visibility/viewport-menu-item.js +42 -0
  137. package/src/components/block-visibility/viewport-toolbar.js +88 -0
  138. package/src/components/inner-blocks/use-inner-block-template-sync.js +1 -1
  139. package/src/components/inserter/menu.js +6 -2
  140. package/src/components/list-view/block-select-button.js +2 -2
  141. package/src/components/list-view/block.js +47 -25
  142. package/src/components/rich-text/index.js +1 -1
  143. package/src/components/url-input/index.js +2 -0
  144. package/src/components/use-block-commands/index.js +4 -3
  145. package/src/hooks/block-fields/index.js +104 -224
  146. package/src/hooks/block-fields/link/index.js +13 -39
  147. package/src/hooks/block-fields/media/index.js +31 -90
  148. package/src/hooks/block-fields/rich-text/index.js +1 -5
  149. package/src/hooks/block-fields/styles.scss +2 -0
  150. package/src/hooks/cross-origin-isolation.js +143 -0
  151. package/src/hooks/index.js +1 -0
  152. package/src/layouts/flex.js +8 -3
  153. package/src/layouts/test/flex.js +53 -0
  154. package/src/store/private-selectors.js +64 -1
  155. package/src/store/reducer.js +1 -1
  156. package/src/store/selectors.js +7 -9
  157. package/src/store/test/private-selectors.js +80 -0
  158. package/src/style.scss +1 -0
  159. package/src/components/block-visibility/styles.scss +0 -10
@@ -54,6 +54,7 @@ import AriaReferencedText from './aria-referenced-text';
54
54
  import { unlock } from '../../lock-unlock';
55
55
  import usePasteStyles from '../use-paste-styles';
56
56
  import { cleanEmptyObject } from '../../hooks/utils';
57
+ import { BlockVisibilityModal } from '../block-visibility';
57
58
 
58
59
  function ListViewBlock( {
59
60
  block: { clientId },
@@ -79,7 +80,8 @@ function ListViewBlock( {
79
80
  const settingsRef = useRef( null );
80
81
  const [ isHovered, setIsHovered ] = useState( false );
81
82
  const [ settingsAnchorRect, setSettingsAnchorRect ] = useState();
82
-
83
+ const [ visibilityModalClientIds, setVisibilityModalClientIds ] =
84
+ useState( null );
83
85
  const { isLocked } = useBlockLock( clientId );
84
86
 
85
87
  const isFirstSelectedBlock =
@@ -98,6 +100,7 @@ function ListViewBlock( {
98
100
  insertBeforeBlock,
99
101
  updateBlockAttributes,
100
102
  } = unlock( useDispatch( blockEditorStore ) );
103
+
101
104
  const debouncedToggleBlockHighlight = useDebounce(
102
105
  toggleBlockHighlight,
103
106
  50
@@ -125,15 +128,18 @@ function ListViewBlock( {
125
128
  const { block, blockName, allowRightClickOverrides, isBlockHidden } =
126
129
  useSelect(
127
130
  ( select ) => {
128
- const { getBlock, getBlockName, getSettings } =
129
- select( blockEditorStore );
131
+ const {
132
+ getBlock,
133
+ getBlockName: _getBlockName,
134
+ getSettings,
135
+ } = select( blockEditorStore );
130
136
  const { isBlockHidden: _isBlockHidden } = unlock(
131
137
  select( blockEditorStore )
132
138
  );
133
139
 
134
140
  return {
135
141
  block: getBlock( clientId ),
136
- blockName: getBlockName( clientId ),
142
+ blockName: _getBlockName( clientId ),
137
143
  allowRightClickOverrides:
138
144
  getSettings().allowRightClickOverrides,
139
145
  isBlockHidden: _isBlockHidden( clientId ),
@@ -373,30 +379,40 @@ function ListViewBlock( {
373
379
  event.preventDefault();
374
380
  const { blocksToUpdate } = getBlocksToUpdate();
375
381
  const blocks = getBlocksByClientId( blocksToUpdate );
376
- const canToggleVisibility = blocks.every( ( blockToUpdate ) =>
377
- hasBlockSupport( blockToUpdate.name, 'visibility', true )
382
+ const supportsBlockVisibility = blocks.every( ( _block ) =>
383
+ hasBlockSupport( _block.name, 'visibility', true )
378
384
  );
379
- if ( ! canToggleVisibility ) {
385
+
386
+ if ( ! supportsBlockVisibility ) {
380
387
  return;
381
388
  }
382
- const hasHiddenBlock = blocks.some(
383
- ( blockToUpdate ) =>
384
- blockToUpdate.attributes.metadata?.blockVisibility === false
385
- );
386
- const attributesByClientId = Object.fromEntries(
387
- blocks.map( ( { clientId: mapClientId, attributes } ) => [
388
- mapClientId,
389
- {
390
- metadata: cleanEmptyObject( {
391
- ...attributes?.metadata,
392
- blockVisibility: hasHiddenBlock ? undefined : false,
393
- } ),
394
- },
395
- ] )
396
- );
397
- updateBlockAttributes( blocksToUpdate, attributesByClientId, {
398
- uniqueByBlock: true,
399
- } );
389
+
390
+ if ( window.__experimentalHideBlocksBasedOnScreenSize ) {
391
+ // Open the visibility breakpoints modal.
392
+ setVisibilityModalClientIds( blocksToUpdate );
393
+ } else {
394
+ const hasHiddenBlock = blocks.some(
395
+ ( blockToUpdate ) =>
396
+ blockToUpdate.attributes.metadata?.blockVisibility ===
397
+ false
398
+ );
399
+ const attributesByClientId = Object.fromEntries(
400
+ blocks.map( ( { clientId: mapClientId, attributes } ) => [
401
+ mapClientId,
402
+ {
403
+ metadata: cleanEmptyObject( {
404
+ ...attributes?.metadata,
405
+ blockVisibility: hasHiddenBlock
406
+ ? undefined
407
+ : false,
408
+ } ),
409
+ },
410
+ ] )
411
+ );
412
+ updateBlockAttributes( blocksToUpdate, attributesByClientId, {
413
+ uniqueByBlock: true,
414
+ } );
415
+ }
400
416
  }
401
417
  }
402
418
 
@@ -699,6 +715,12 @@ function ListViewBlock( {
699
715
  ) }
700
716
  </TreeGridCell>
701
717
  ) }
718
+ { visibilityModalClientIds && (
719
+ <BlockVisibilityModal
720
+ clientIds={ visibilityModalClientIds }
721
+ onClose={ () => setVisibilityModalClientIds( null ) }
722
+ />
723
+ ) }
702
724
  </ListViewLeaf>
703
725
  );
704
726
  }
@@ -2,7 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import clsx from 'clsx';
5
- import fastDeepEqual from 'fast-deep-equal/es6';
5
+ import fastDeepEqual from 'fast-deep-equal/es6/index.js';
6
6
 
7
7
  /**
8
8
  * WordPress dependencies
@@ -452,6 +452,8 @@ class URLInput extends Component {
452
452
  value,
453
453
  required: true,
454
454
  type: 'text',
455
+ name: inputId,
456
+ autoComplete: 'off',
455
457
  onChange: disabled ? () => {} : this.onChange, // Disable onChange when disabled
456
458
  onFocus: disabled ? () => {} : this.onFocus, // Disable onFocus when disabled
457
459
  placeholder,
@@ -223,9 +223,10 @@ const getQuickActionsCommands = () =>
223
223
  } );
224
224
  const canRemove = canRemoveBlocks( clientIds );
225
225
 
226
- const canToggleBlockVisibility = blocks.every( ( { clientId } ) =>
227
- hasBlockSupport( getBlockName( clientId ), 'visibility', true )
228
- );
226
+ const canToggleBlockVisibility =
227
+ blocks.every( ( { clientId } ) =>
228
+ hasBlockSupport( getBlockName( clientId ), 'visibility', true )
229
+ ) && ! window.__experimentalHideBlocksBasedOnScreenSize;
229
230
 
230
231
  const commands = [];
231
232
 
@@ -10,6 +10,7 @@ import {
10
10
  import { createHigherOrderComponent } from '@wordpress/compose';
11
11
  import { DataForm } from '@wordpress/dataviews';
12
12
  import { useContext, useState, useMemo } from '@wordpress/element';
13
+ import { __ } from '@wordpress/i18n';
13
14
 
14
15
  /**
15
16
  * Internal dependencies
@@ -38,138 +39,41 @@ const CONTROLS = {
38
39
  * Creates a configured control component that wraps a custom control
39
40
  * and passes configuration as props.
40
41
  *
41
- * @param {Object} config - The control configuration
42
- * @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
+ *
43
46
  * @return {Function} A wrapped control component
44
47
  */
45
- function createConfiguredControl( config ) {
46
- const { control, ...controlConfig } = config;
47
- const ControlComponent = CONTROLS[ control ];
48
-
48
+ function createConfiguredControl( ControlComponent, type, config ) {
49
49
  if ( ! ControlComponent ) {
50
- throw new Error( `Control type "${ control }" not found` );
50
+ throw new Error( `Control type "${ type }" not found` );
51
51
  }
52
52
 
53
53
  return function ConfiguredControl( props ) {
54
- return <ControlComponent { ...props } config={ controlConfig } />;
55
- };
56
- }
57
-
58
- /**
59
- * Normalize a media value to a canonical structure.
60
- * Only includes properties that are present in the field's mapping (if provided).
61
- *
62
- * @param {Object} value - The mapped value from the block attributes (with canonical keys)
63
- * @param {Object} fieldDef - Optional field definition containing the mapping
64
- * @return {Object} Normalized media value with canonical properties
65
- */
66
- function normalizeMediaValue( value, fieldDef ) {
67
- const defaults = {
68
- id: null,
69
- url: '',
70
- caption: '',
71
- alt: '',
72
- type: 'image',
73
- poster: '',
74
- featuredImage: false,
75
- link: '',
54
+ return <ControlComponent { ...props } config={ config } />;
76
55
  };
77
-
78
- const result = {};
79
-
80
- // If there's a mapping, only include properties that are in it
81
- if ( fieldDef?.mapping ) {
82
- Object.keys( fieldDef.mapping ).forEach( ( key ) => {
83
- result[ key ] = value?.[ key ] ?? defaults[ key ] ?? '';
84
- } );
85
- return result;
86
- }
87
-
88
- // Without mapping, include all default properties
89
- Object.keys( defaults ).forEach( ( key ) => {
90
- result[ key ] = value?.[ key ] ?? defaults[ key ];
91
- } );
92
- return result;
93
56
  }
94
57
 
95
58
  /**
96
- * Denormalize a media value from canonical structure back to mapped keys.
97
- * Only includes properties that are present in the field's mapping.
98
- *
99
- * @param {Object} value - The normalized media value
100
- * @param {Object} fieldDef - The field definition containing the mapping
101
- * @return {Object} Value with only mapped properties
59
+ * Component that renders a DataForm for a single block's attributes
60
+ * @param {Object} props
61
+ * @param {string} props.clientId The clientId of the block.
62
+ * @param {Object} props.blockType The blockType definition.
63
+ * @param {Object} props.attributes The block's attribute values.
64
+ * @param {Function} props.setAttributes Action to set the block's attributes.
65
+ * @param {boolean} props.isCollapsed Whether the DataForm is rendered as 'collapsed' with only the first field
66
+ * displayed by default. When collapsed a dropdown is displayed to allow
67
+ * displaying additional fields. The block's title is displayed as the title.
68
+ * The collapsed mode is often used when multiple BlockForms are shown together.
102
69
  */
103
- function denormalizeMediaValue( value, fieldDef ) {
104
- if ( ! fieldDef.mapping ) {
105
- return value;
106
- }
107
-
108
- const result = {};
109
- Object.entries( fieldDef.mapping ).forEach( ( [ key ] ) => {
110
- if ( key in value ) {
111
- result[ key ] = value[ key ];
112
- }
113
- } );
114
- return result;
115
- }
116
-
117
- /**
118
- * Normalize a link value to a canonical structure.
119
- * Only includes properties that are present in the field's mapping (if provided).
120
- *
121
- * @param {Object} value - The mapped value from the block attributes (with canonical keys)
122
- * @param {Object} fieldDef - Optional field definition containing the mapping
123
- * @return {Object} Normalized link value with canonical properties
124
- */
125
- function normalizeLinkValue( value, fieldDef ) {
126
- const defaults = {
127
- url: '',
128
- rel: '',
129
- linkTarget: '',
130
- destination: '',
131
- };
132
-
133
- const result = {};
134
-
135
- // If there's a mapping, only include properties that are in it
136
- if ( fieldDef?.mapping ) {
137
- Object.keys( fieldDef.mapping ).forEach( ( key ) => {
138
- result[ key ] = value?.[ key ] ?? defaults[ key ] ?? '';
139
- } );
140
- return result;
141
- }
142
-
143
- // Without mapping, include all default properties
144
- Object.keys( defaults ).forEach( ( key ) => {
145
- result[ key ] = value?.[ key ] ?? defaults[ key ];
146
- } );
147
- return result;
148
- }
149
-
150
- /**
151
- * Denormalize a link value from canonical structure back to mapped keys.
152
- * Only includes properties that are present in the field's mapping.
153
- *
154
- * @param {Object} value - The normalized link value
155
- * @param {Object} fieldDef - The field definition containing the mapping
156
- * @return {Object} Value with only mapped properties
157
- */
158
- function denormalizeLinkValue( value, fieldDef ) {
159
- if ( ! fieldDef.mapping ) {
160
- return value;
161
- }
162
-
163
- const result = {};
164
- Object.entries( fieldDef.mapping ).forEach( ( [ key ] ) => {
165
- if ( key in value ) {
166
- result[ key ] = value[ key ];
167
- }
168
- } );
169
- return result;
170
- }
171
-
172
- function BlockFields( { clientId, blockType, attributes, setAttributes } ) {
70
+ function BlockFields( {
71
+ clientId,
72
+ blockType,
73
+ attributes,
74
+ setAttributes,
75
+ isCollapsed = false,
76
+ } ) {
173
77
  const blockTitle = useBlockDisplayTitle( {
174
78
  clientId,
175
79
  context: 'list-view',
@@ -178,9 +82,19 @@ function BlockFields( { clientId, blockType, attributes, setAttributes } ) {
178
82
 
179
83
  const blockTypeFields = blockType?.[ fieldsKey ];
180
84
 
181
- const [ form, setForm ] = useState( () => {
182
- return blockType?.[ formKey ];
183
- } );
85
+ const computedForm = useMemo( () => {
86
+ if ( ! isCollapsed ) {
87
+ return blockType?.[ formKey ];
88
+ }
89
+
90
+ // For a collapsed form only show the first field by default.
91
+ return {
92
+ ...blockType?.[ formKey ],
93
+ fields: [ blockType?.[ formKey ]?.fields?.[ 0 ] ],
94
+ };
95
+ }, [ blockType, isCollapsed ] );
96
+
97
+ const [ form, setForm ] = useState( computedForm );
184
98
 
185
99
  // Build DataForm fields with proper structure
186
100
  const dataFormFields = useMemo( () => {
@@ -189,100 +103,63 @@ function BlockFields( { clientId, blockType, attributes, setAttributes } ) {
189
103
  }
190
104
 
191
105
  return blockTypeFields.map( ( fieldDef ) => {
192
- const ControlComponent = CONTROLS[ fieldDef.type ];
193
-
194
- const defaultValues = {};
195
- if ( fieldDef.mapping && blockType?.attributes ) {
196
- Object.entries( fieldDef.mapping ).forEach(
197
- ( [ key, attrKey ] ) => {
198
- defaultValues[ key ] =
199
- blockType.attributes[ attrKey ]?.defaultValue ??
200
- undefined;
201
- }
202
- );
203
- }
204
-
205
106
  const field = {
206
107
  id: fieldDef.id,
207
108
  label: fieldDef.label,
208
109
  type: fieldDef.type, // Use the field's type; DataForm will use built-in or custom Edit
209
- config: { ...fieldDef.args, defaultValues },
210
- hideLabelFromVision: fieldDef.id === 'content',
211
- // getValue and setValue handle the mapping to block attributes
212
- getValue: ( { item } ) => {
213
- if ( fieldDef.mapping ) {
214
- // Extract mapped properties from the block attributes
215
- const mappedValue = {};
216
- Object.entries( fieldDef.mapping ).forEach(
217
- ( [ key, attrKey ] ) => {
218
- mappedValue[ key ] = item[ attrKey ];
219
- }
220
- );
110
+ };
221
111
 
222
- // Normalize to canonical structure based on field type
223
- if ( fieldDef.type === 'media' ) {
224
- return normalizeMediaValue( mappedValue, fieldDef );
225
- }
226
- if ( fieldDef.type === 'link' ) {
227
- return normalizeLinkValue( 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 ];
228
125
  }
229
-
230
- // For other types, return as-is
231
- return mappedValue;
232
- }
233
- // For simple id-based fields, use the id as the attribute key
234
- return item[ fieldDef.id ];
235
- },
236
- setValue: ( { item, value } ) => {
237
- if ( fieldDef.mapping ) {
238
- // Denormalize from canonical structure back to mapped keys
239
- let denormalizedValue = value;
240
- if ( fieldDef.type === 'media' ) {
241
- denormalizedValue = denormalizeMediaValue(
242
- value,
243
- fieldDef
244
- );
245
- } else if ( fieldDef.type === 'link' ) {
246
- denormalizedValue = denormalizeLinkValue(
247
- value,
248
- fieldDef
249
- );
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 ];
250
134
  }
251
-
252
- // Build an object with all mapped attributes
253
- const updates = {};
254
- Object.entries( fieldDef.mapping ).forEach(
255
- ( [ key, attrKey ] ) => {
256
- // If key is explicitly in value, use it (even if undefined to allow clearing)
257
- // Otherwise, preserve the old value
258
- if ( key in denormalizedValue ) {
259
- updates[ attrKey ] =
260
- denormalizedValue[ key ];
261
- } else {
262
- updates[ attrKey ] = item[ attrKey ];
263
- }
264
- }
265
- );
266
- return updates;
267
- }
268
- // For simple id-based fields, use the id as the attribute key
269
- return { [ fieldDef.id ]: value };
270
- },
271
- };
135
+ );
136
+ return attributeUpdates;
137
+ };
138
+ }
272
139
 
273
140
  // Only add custom Edit component if one exists for this type
141
+ const ControlComponent = CONTROLS[ fieldDef.type ];
274
142
  if ( ControlComponent ) {
275
143
  // Use EditConfig pattern: Edit is an object with control type and config props
276
- field.Edit = createConfiguredControl( {
277
- control: fieldDef.type,
278
- clientId,
279
- fieldDef,
280
- } );
144
+ field.Edit = createConfiguredControl(
145
+ ControlComponent,
146
+ fieldDef.type,
147
+ {
148
+ clientId,
149
+ fieldDef,
150
+ }
151
+ );
281
152
  }
282
153
 
283
154
  return field;
284
155
  } );
285
- }, [ 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
+ }
286
163
 
287
164
  const handleToggleField = ( fieldId ) => {
288
165
  setForm( ( prev ) => {
@@ -300,31 +177,33 @@ function BlockFields( { clientId, blockType, attributes, setAttributes } ) {
300
177
  } );
301
178
  };
302
179
 
303
- if ( ! blockTypeFields?.length ) {
304
- // TODO - we might still want to show a placeholder for blocks with no fields.
305
- // for example, a way to select the block.
306
- return null;
307
- }
308
-
309
180
  return (
310
181
  <div className="block-editor-block-fields__container">
311
182
  <div className="block-editor-block-fields__header">
312
183
  <HStack spacing={ 1 }>
313
- <BlockIcon
314
- className="block-editor-block-fields__header-icon"
315
- icon={ blockInformation?.icon }
316
- />
317
- <Truncate
318
- className="block-editor-block-fields__header-title"
319
- numberOfLines={ 1 }
320
- >
321
- { blockTitle }
322
- </Truncate>
323
- <FieldsDropdownMenu
324
- fields={ dataFormFields }
325
- visibleFields={ form.fields }
326
- onToggleField={ handleToggleField }
327
- />
184
+ { isCollapsed && (
185
+ <>
186
+ <BlockIcon
187
+ className="block-editor-block-fields__header-icon"
188
+ icon={ blockInformation?.icon }
189
+ />
190
+ <h2 className="block-editor-block-fields__header-title">
191
+ <Truncate numberOfLines={ 1 }>
192
+ { blockTitle }
193
+ </Truncate>
194
+ </h2>
195
+ <FieldsDropdownMenu
196
+ fields={ dataFormFields }
197
+ visibleFields={ form.fields }
198
+ onToggleField={ handleToggleField }
199
+ />
200
+ </>
201
+ ) }
202
+ { ! isCollapsed && (
203
+ <h2 className="block-editor-block-fields__header-title">
204
+ { __( 'Content' ) }
205
+ </h2>
206
+ ) }
328
207
  </HStack>
329
208
  </div>
330
209
  <DataForm
@@ -370,6 +249,7 @@ const withBlockFields = createHigherOrderComponent(
370
249
  <BlockFields
371
250
  { ...props }
372
251
  blockType={ blockType }
252
+ isCollapsed
373
253
  />
374
254
  </PrivateInspectorControlsFill>
375
255
  )
@@ -73,11 +73,6 @@ export default function Link( { data, field, onChange, config = {} } ) {
73
73
  isControl: true,
74
74
  } );
75
75
  const { fieldDef } = config;
76
- const updateAttributes = ( newValue ) => {
77
- const mappedChanges = field.setValue( { item: data, value: newValue } );
78
- onChange( mappedChanges );
79
- };
80
-
81
76
  const value = field.getValue( { item: data } );
82
77
  const url = value?.url;
83
78
  const rel = value?.rel || '';
@@ -145,30 +140,12 @@ export default function Link( { data, field, onChange, config = {} } ) {
145
140
  ...newValues,
146
141
  } );
147
142
 
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
+ );
172
149
  } }
173
150
  onRemove={ () => {
174
151
  // Remove all link-related properties based on what's in the mapping
@@ -177,20 +154,17 @@ export default function Link( { data, field, onChange, config = {} } ) {
177
154
  if ( fieldDef?.mapping ) {
178
155
  Object.keys( fieldDef.mapping ).forEach(
179
156
  ( 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;
189
158
  }
190
159
  );
191
160
  }
192
161
 
193
- updateAttributes( removeValue );
162
+ onChange(
163
+ field.setValue( {
164
+ item: data,
165
+ value: removeValue,
166
+ } )
167
+ );
194
168
  } }
195
169
  />
196
170
  </Popover>