@wordpress/block-editor 15.10.1-next.79a2f3cdd.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 (228) 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-edit/context.cjs +5 -5
  6. package/build/components/block-edit/context.cjs.map +1 -1
  7. package/build/components/block-list/block.cjs +24 -12
  8. package/build/components/block-list/block.cjs.map +3 -3
  9. package/build/components/block-list/use-block-props/index.cjs +8 -2
  10. package/build/components/block-list/use-block-props/index.cjs.map +2 -2
  11. package/build/components/block-tools/index.cjs +82 -70
  12. package/build/components/block-tools/index.cjs.map +2 -2
  13. package/build/components/block-visibility/block-visibility-info.cjs +0 -59
  14. package/build/components/block-visibility/block-visibility-info.cjs.map +3 -3
  15. package/build/components/block-visibility/constants.cjs +54 -0
  16. package/build/components/block-visibility/constants.cjs.map +7 -0
  17. package/build/components/block-visibility/index.cjs +15 -4
  18. package/build/components/block-visibility/index.cjs.map +3 -3
  19. package/build/components/block-visibility/modal.cjs +397 -0
  20. package/build/components/block-visibility/modal.cjs.map +7 -0
  21. package/build/components/block-visibility/toolbar.cjs +1 -1
  22. package/build/components/block-visibility/toolbar.cjs.map +2 -2
  23. package/build/components/block-visibility/use-block-visibility.cjs +65 -0
  24. package/build/components/block-visibility/use-block-visibility.cjs.map +7 -0
  25. package/build/components/block-visibility/utils.cjs +81 -0
  26. package/build/components/block-visibility/utils.cjs.map +7 -0
  27. package/build/components/block-visibility/viewport-menu-item.cjs +61 -0
  28. package/build/components/block-visibility/viewport-menu-item.cjs.map +7 -0
  29. package/build/components/block-visibility/viewport-toolbar.cjs +89 -0
  30. package/build/components/block-visibility/viewport-toolbar.cjs.map +7 -0
  31. package/build/components/collab/block-comment-icon-slot.cjs +1 -1
  32. package/build/components/collab/block-comment-icon-slot.cjs.map +1 -1
  33. package/build/components/collab/block-comment-icon-toolbar-slot.cjs +1 -1
  34. package/build/components/collab/block-comment-icon-toolbar-slot.cjs.map +1 -1
  35. package/build/components/inner-blocks/use-inner-block-template-sync.cjs +1 -1
  36. package/build/components/inner-blocks/use-inner-block-template-sync.cjs.map +1 -1
  37. package/build/components/inserter/menu.cjs +6 -2
  38. package/build/components/inserter/menu.cjs.map +2 -2
  39. package/build/components/inspector-controls/groups.cjs +1 -1
  40. package/build/components/inspector-controls/groups.cjs.map +1 -1
  41. package/build/components/inspector-controls-tabs/content-tab.cjs +1 -1
  42. package/build/components/inspector-controls-tabs/content-tab.cjs.map +2 -2
  43. package/build/components/list-view/block-select-button.cjs +2 -2
  44. package/build/components/list-view/block-select-button.cjs.map +2 -2
  45. package/build/components/list-view/block.cjs +39 -22
  46. package/build/components/list-view/block.cjs.map +2 -2
  47. package/build/components/list-view/index.cjs +11 -6
  48. package/build/components/list-view/index.cjs.map +2 -2
  49. package/build/components/list-view/utils.cjs +24 -17
  50. package/build/components/list-view/utils.cjs.map +2 -2
  51. package/build/components/rich-text/event-listeners/input-rules.cjs +13 -1
  52. package/build/components/rich-text/event-listeners/input-rules.cjs.map +2 -2
  53. package/build/components/rich-text/format-edit.cjs +1 -1
  54. package/build/components/rich-text/format-edit.cjs.map +1 -1
  55. package/build/components/rich-text/index.cjs +2 -2
  56. package/build/components/rich-text/index.cjs.map +2 -2
  57. package/build/components/url-input/index.cjs +2 -0
  58. package/build/components/url-input/index.cjs.map +2 -2
  59. package/build/components/use-block-commands/index.cjs +1 -1
  60. package/build/components/use-block-commands/index.cjs.map +2 -2
  61. package/build/components/writing-flow/utils.cjs +1 -1
  62. package/build/components/writing-flow/utils.cjs.map +1 -1
  63. package/build/hooks/block-fields/index.cjs +76 -167
  64. package/build/hooks/block-fields/index.cjs.map +2 -2
  65. package/build/hooks/block-fields/link/index.cjs +13 -23
  66. package/build/hooks/block-fields/link/index.cjs.map +2 -2
  67. package/build/hooks/block-fields/media/index.cjs +32 -58
  68. package/build/hooks/block-fields/media/index.cjs.map +2 -2
  69. package/build/hooks/block-fields/rich-text/index.cjs +1 -5
  70. package/build/hooks/block-fields/rich-text/index.cjs.map +2 -2
  71. package/build/hooks/cross-origin-isolation.cjs +102 -0
  72. package/build/hooks/cross-origin-isolation.cjs.map +7 -0
  73. package/build/hooks/fit-text.cjs +1 -1
  74. package/build/hooks/fit-text.cjs.map +1 -1
  75. package/build/hooks/index.cjs +1 -0
  76. package/build/hooks/index.cjs.map +2 -2
  77. package/build/layouts/flex.cjs +6 -2
  78. package/build/layouts/flex.cjs.map +2 -2
  79. package/build/store/private-keys.cjs +10 -10
  80. package/build/store/private-keys.cjs.map +1 -1
  81. package/build/store/private-selectors.cjs +33 -1
  82. package/build/store/private-selectors.cjs.map +3 -3
  83. package/build/store/reducer.cjs +1 -1
  84. package/build/store/reducer.cjs.map +1 -1
  85. package/build/store/selectors.cjs +7 -8
  86. package/build/store/selectors.cjs.map +2 -2
  87. package/build/store/utils.cjs +1 -1
  88. package/build/store/utils.cjs.map +1 -1
  89. package/build-module/components/block-bindings/attribute-control.mjs +1 -1
  90. package/build-module/components/block-bindings/attribute-control.mjs.map +1 -1
  91. package/build-module/components/block-bindings/source-fields-list.mjs +1 -1
  92. package/build-module/components/block-bindings/source-fields-list.mjs.map +1 -1
  93. package/build-module/components/block-edit/context.mjs +5 -5
  94. package/build-module/components/block-edit/context.mjs.map +1 -1
  95. package/build-module/components/block-list/block.mjs +24 -12
  96. package/build-module/components/block-list/block.mjs.map +3 -3
  97. package/build-module/components/block-list/use-block-props/index.mjs +8 -2
  98. package/build-module/components/block-list/use-block-props/index.mjs.map +2 -2
  99. package/build-module/components/block-tools/index.mjs +85 -73
  100. package/build-module/components/block-tools/index.mjs.map +2 -2
  101. package/build-module/components/block-visibility/block-visibility-info.mjs +0 -59
  102. package/build-module/components/block-visibility/block-visibility-info.mjs.map +3 -3
  103. package/build-module/components/block-visibility/constants.mjs +28 -0
  104. package/build-module/components/block-visibility/constants.mjs.map +7 -0
  105. package/build-module/components/block-visibility/index.mjs +13 -4
  106. package/build-module/components/block-visibility/index.mjs.map +2 -2
  107. package/build-module/components/block-visibility/modal.mjs +384 -0
  108. package/build-module/components/block-visibility/modal.mjs.map +7 -0
  109. package/build-module/components/block-visibility/toolbar.mjs +1 -1
  110. package/build-module/components/block-visibility/toolbar.mjs.map +2 -2
  111. package/build-module/components/block-visibility/use-block-visibility.mjs +44 -0
  112. package/build-module/components/block-visibility/use-block-visibility.mjs.map +7 -0
  113. package/build-module/components/block-visibility/utils.mjs +55 -0
  114. package/build-module/components/block-visibility/utils.mjs.map +7 -0
  115. package/build-module/components/block-visibility/viewport-menu-item.mjs +40 -0
  116. package/build-module/components/block-visibility/viewport-menu-item.mjs.map +7 -0
  117. package/build-module/components/block-visibility/viewport-toolbar.mjs +68 -0
  118. package/build-module/components/block-visibility/viewport-toolbar.mjs.map +7 -0
  119. package/build-module/components/collab/block-comment-icon-slot.mjs +1 -1
  120. package/build-module/components/collab/block-comment-icon-slot.mjs.map +1 -1
  121. package/build-module/components/collab/block-comment-icon-toolbar-slot.mjs +1 -1
  122. package/build-module/components/collab/block-comment-icon-toolbar-slot.mjs.map +1 -1
  123. package/build-module/components/inner-blocks/use-inner-block-template-sync.mjs +1 -1
  124. package/build-module/components/inner-blocks/use-inner-block-template-sync.mjs.map +1 -1
  125. package/build-module/components/inserter/menu.mjs +6 -2
  126. package/build-module/components/inserter/menu.mjs.map +2 -2
  127. package/build-module/components/inspector-controls/groups.mjs +1 -1
  128. package/build-module/components/inspector-controls/groups.mjs.map +1 -1
  129. package/build-module/components/inspector-controls-tabs/content-tab.mjs +1 -1
  130. package/build-module/components/inspector-controls-tabs/content-tab.mjs.map +2 -2
  131. package/build-module/components/list-view/block-select-button.mjs +2 -2
  132. package/build-module/components/list-view/block-select-button.mjs.map +2 -2
  133. package/build-module/components/list-view/block.mjs +39 -22
  134. package/build-module/components/list-view/block.mjs.map +2 -2
  135. package/build-module/components/list-view/index.mjs +11 -7
  136. package/build-module/components/list-view/index.mjs.map +2 -2
  137. package/build-module/components/list-view/utils.mjs +24 -17
  138. package/build-module/components/list-view/utils.mjs.map +2 -2
  139. package/build-module/components/rich-text/event-listeners/input-rules.mjs +13 -1
  140. package/build-module/components/rich-text/event-listeners/input-rules.mjs.map +2 -2
  141. package/build-module/components/rich-text/format-edit.mjs +1 -1
  142. package/build-module/components/rich-text/format-edit.mjs.map +1 -1
  143. package/build-module/components/rich-text/index.mjs +2 -2
  144. package/build-module/components/rich-text/index.mjs.map +2 -2
  145. package/build-module/components/url-input/index.mjs +2 -0
  146. package/build-module/components/url-input/index.mjs.map +2 -2
  147. package/build-module/components/use-block-commands/index.mjs +1 -1
  148. package/build-module/components/use-block-commands/index.mjs.map +2 -2
  149. package/build-module/components/writing-flow/utils.mjs +1 -1
  150. package/build-module/components/writing-flow/utils.mjs.map +1 -1
  151. package/build-module/hooks/block-fields/index.mjs +76 -167
  152. package/build-module/hooks/block-fields/index.mjs.map +2 -2
  153. package/build-module/hooks/block-fields/link/index.mjs +13 -23
  154. package/build-module/hooks/block-fields/link/index.mjs.map +2 -2
  155. package/build-module/hooks/block-fields/media/index.mjs +32 -58
  156. package/build-module/hooks/block-fields/media/index.mjs.map +2 -2
  157. package/build-module/hooks/block-fields/rich-text/index.mjs +1 -5
  158. package/build-module/hooks/block-fields/rich-text/index.mjs.map +2 -2
  159. package/build-module/hooks/cross-origin-isolation.mjs +100 -0
  160. package/build-module/hooks/cross-origin-isolation.mjs.map +7 -0
  161. package/build-module/hooks/fit-text.mjs +1 -1
  162. package/build-module/hooks/fit-text.mjs.map +1 -1
  163. package/build-module/hooks/index.mjs +1 -0
  164. package/build-module/hooks/index.mjs.map +2 -2
  165. package/build-module/layouts/flex.mjs +6 -2
  166. package/build-module/layouts/flex.mjs.map +2 -2
  167. package/build-module/store/private-keys.mjs +10 -10
  168. package/build-module/store/private-keys.mjs.map +1 -1
  169. package/build-module/store/private-selectors.mjs +34 -1
  170. package/build-module/store/private-selectors.mjs.map +2 -2
  171. package/build-module/store/reducer.mjs +1 -1
  172. package/build-module/store/reducer.mjs.map +1 -1
  173. package/build-module/store/selectors.mjs +7 -8
  174. package/build-module/store/selectors.mjs.map +2 -2
  175. package/build-module/store/utils.mjs +1 -1
  176. package/build-module/store/utils.mjs.map +1 -1
  177. package/build-style/content-rtl.css +4 -1
  178. package/build-style/content.css +4 -1
  179. package/build-style/style-rtl.css +54 -1
  180. package/build-style/style.css +54 -1
  181. package/package.json +39 -39
  182. package/src/components/block-bindings/attribute-control.js +1 -1
  183. package/src/components/block-bindings/source-fields-list.js +1 -1
  184. package/src/components/block-list/block.js +23 -9
  185. package/src/components/block-list/content.scss +4 -1
  186. package/src/components/block-list/use-block-props/index.js +10 -2
  187. package/src/components/block-toolbar/style.scss +0 -1
  188. package/src/components/block-tools/index.js +45 -33
  189. package/src/components/block-tools/style.scss +10 -0
  190. package/src/components/block-visibility/block-visibility-info.js +0 -1
  191. package/src/components/block-visibility/constants.js +33 -0
  192. package/src/components/block-visibility/index.js +21 -2
  193. package/src/components/block-visibility/modal.js +358 -0
  194. package/src/components/block-visibility/style.scss +58 -0
  195. package/src/components/block-visibility/test/use-block-visibility.js +316 -0
  196. package/src/components/block-visibility/test/utils.js +266 -0
  197. package/src/components/block-visibility/toolbar.js +1 -1
  198. package/src/components/block-visibility/use-block-visibility.js +70 -0
  199. package/src/components/block-visibility/utils.js +95 -0
  200. package/src/components/block-visibility/viewport-menu-item.js +42 -0
  201. package/src/components/block-visibility/viewport-toolbar.js +88 -0
  202. package/src/components/inner-blocks/use-inner-block-template-sync.js +1 -1
  203. package/src/components/inserter/menu.js +6 -2
  204. package/src/components/inspector-controls-tabs/content-tab.js +0 -1
  205. package/src/components/list-view/block-select-button.js +2 -2
  206. package/src/components/list-view/block.js +47 -25
  207. package/src/components/list-view/index.js +15 -11
  208. package/src/components/list-view/utils.js +31 -23
  209. package/src/components/rich-text/event-listeners/input-rules.js +17 -0
  210. package/src/components/rich-text/index.js +1 -1
  211. package/src/components/url-input/index.js +2 -0
  212. package/src/components/use-block-commands/index.js +4 -3
  213. package/src/hooks/block-fields/index.js +104 -225
  214. package/src/hooks/block-fields/link/index.js +13 -39
  215. package/src/hooks/block-fields/media/index.js +31 -90
  216. package/src/hooks/block-fields/rich-text/index.js +1 -5
  217. package/src/hooks/block-fields/styles.scss +2 -0
  218. package/src/hooks/cross-origin-isolation.js +143 -0
  219. package/src/hooks/fit-text.js +1 -1
  220. package/src/hooks/index.js +1 -0
  221. package/src/layouts/flex.js +8 -3
  222. package/src/layouts/test/flex.js +53 -0
  223. package/src/store/private-selectors.js +64 -1
  224. package/src/store/reducer.js +1 -1
  225. package/src/store/selectors.js +7 -9
  226. package/src/store/test/private-selectors.js +80 -0
  227. package/src/style.scss +1 -0
  228. package/src/components/block-visibility/styles.scss +0 -10
@@ -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
@@ -348,7 +227,6 @@ const withBlockFields = createHigherOrderComponent(
348
227
  } = useContext( PrivateBlockContext );
349
228
 
350
229
  const shouldShowBlockFields =
351
- window?.__experimentalContentOnlyPatternInsertion &&
352
230
  window?.__experimentalContentOnlyInspectorFields;
353
231
  const blockTypeFields = blockType?.[ fieldsKey ];
354
232
 
@@ -371,6 +249,7 @@ const withBlockFields = createHigherOrderComponent(
371
249
  <BlockFields
372
250
  { ...props }
373
251
  blockType={ blockType }
252
+ isCollapsed
374
253
  />
375
254
  </PrivateInspectorControlsFill>
376
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>
@@ -24,9 +24,9 @@ import { useInspectorPopoverPlacement } from '../use-inspector-popover-placement
24
24
  import { getMediaSelectKey } from '../../../store/private-keys';
25
25
  import { store as blockEditorStore } from '../../../store';
26
26
 
27
- function MediaThumbnail( { data, field, attachment } ) {
28
- const config = field.config || {};
29
- const { allowedTypes = [], multiple = false } = config;
27
+ function MediaThumbnail( { data, field, attachment, config } ) {
28
+ const { fieldDef } = config;
29
+ const { allowedTypes = [], multiple = false } = fieldDef.args || {};
30
30
 
31
31
  if ( multiple ) {
32
32
  return 'todo multiple';
@@ -53,7 +53,7 @@ function MediaThumbnail( { data, field, attachment } ) {
53
53
  const value = field.getValue( { item: data } );
54
54
  const url = value?.url;
55
55
 
56
- if ( url ) {
56
+ if ( allowedTypes[ 0 ] === 'image' && url ) {
57
57
  return (
58
58
  <div className="block-editor-content-only-controls__media-thumbnail">
59
59
  <img alt="" width={ 24 } height={ 24 } src={ url } />
@@ -85,15 +85,8 @@ export default function Media( { data, field, onChange, config = {} } ) {
85
85
  isControl: true,
86
86
  } );
87
87
  const value = field.getValue( { item: data } );
88
- const { allowedTypes = [], multiple = false } = field.config || {};
89
88
  const { fieldDef } = config;
90
- const updateAttributes = ( newFieldValue ) => {
91
- const mappedChanges = field.setValue( {
92
- item: data,
93
- value: newFieldValue,
94
- } );
95
- onChange( mappedChanges );
96
- };
89
+ const { allowedTypes = [], multiple = false } = fieldDef.args || {};
97
90
 
98
91
  // Check if featured image is supported by checking if it's in the mapping
99
92
  const hasFeaturedImageSupport =
@@ -152,102 +145,49 @@ export default function Media( { data, field, onChange, config = {} } ) {
152
145
 
153
146
  if ( fieldDef?.mapping ) {
154
147
  Object.keys( fieldDef.mapping ).forEach( ( key ) => {
155
- if (
156
- key === 'id' ||
157
- key === 'src' ||
158
- key === 'url'
159
- ) {
160
- resetValue[ key ] = undefined;
161
- } else if ( key === 'caption' || key === 'alt' ) {
162
- resetValue[ key ] = '';
163
- }
148
+ resetValue[ key ] = undefined;
164
149
  } );
165
150
  }
166
151
 
167
- // Turn off featured image when resetting (only if it's in the mapping)
168
- if ( hasFeaturedImageSupport ) {
169
- resetValue.featuredImage = false;
170
- }
171
-
172
- // Merge with existing value to preserve other field properties
173
- updateAttributes( { ...value, ...resetValue } );
152
+ onChange(
153
+ field.setValue( {
154
+ item: data,
155
+ value: resetValue,
156
+ } )
157
+ );
174
158
  } }
175
159
  { ...( hasFeaturedImageSupport && {
176
160
  useFeaturedImage: !! value?.featuredImage,
177
161
  onToggleFeaturedImage: () => {
178
- updateAttributes( {
179
- ...value,
180
- featuredImage: ! value?.featuredImage,
181
- } );
162
+ onChange(
163
+ field.setValue( {
164
+ item: data,
165
+ value: {
166
+ featuredImage: ! value?.featuredImage,
167
+ },
168
+ } )
169
+ );
182
170
  },
183
171
  } ) }
184
172
  onSelect={ ( selectedMedia ) => {
185
173
  if ( selectedMedia.id && selectedMedia.url ) {
186
- // Determine mediaType from MIME type, not from object type
187
- let mediaType = 'image'; // default
188
- if ( selectedMedia.mime_type ) {
189
- if (
190
- selectedMedia.mime_type.startsWith( 'video/' )
191
- ) {
192
- mediaType = 'video';
193
- } else if (
194
- selectedMedia.mime_type.startsWith( 'audio/' )
195
- ) {
196
- mediaType = 'audio';
197
- }
198
- }
199
-
200
174
  // Build new value dynamically based on what's in the mapping
201
- const newValue = {};
202
-
203
- // Iterate over mapping keys and set values for supported properties
204
- if ( fieldDef?.mapping ) {
205
- Object.keys( fieldDef.mapping ).forEach(
206
- ( key ) => {
207
- if ( key === 'id' ) {
208
- newValue[ key ] = selectedMedia.id;
209
- } else if (
210
- key === 'src' ||
211
- key === 'url'
212
- ) {
213
- newValue[ key ] = selectedMedia.url;
214
- } else if ( key === 'type' ) {
215
- newValue[ key ] = mediaType;
216
- } else if (
217
- key === 'link' &&
218
- selectedMedia.link
219
- ) {
220
- newValue[ key ] = selectedMedia.link;
221
- } else if (
222
- key === 'caption' &&
223
- ! value?.caption &&
224
- selectedMedia.caption
225
- ) {
226
- newValue[ key ] = selectedMedia.caption;
227
- } else if (
228
- key === 'alt' &&
229
- ! value?.alt &&
230
- selectedMedia.alt
231
- ) {
232
- newValue[ key ] = selectedMedia.alt;
233
- } else if (
234
- key === 'poster' &&
235
- selectedMedia.poster
236
- ) {
237
- newValue[ key ] = selectedMedia.poster;
238
- }
239
- }
240
- );
241
- }
175
+ const newValue = {
176
+ ...selectedMedia,
177
+ mediaType: selectedMedia.media_type,
178
+ };
242
179
 
243
180
  // Turn off featured image when manually selecting media
244
181
  if ( hasFeaturedImageSupport ) {
245
182
  newValue.featuredImage = false;
246
183
  }
247
184
 
248
- // Merge with existing value to preserve other field properties
249
- const finalValue = { ...value, ...newValue };
250
- updateAttributes( finalValue );
185
+ onChange(
186
+ field.setValue( {
187
+ item: data,
188
+ value: newValue,
189
+ } )
190
+ );
251
191
  }
252
192
  } }
253
193
  renderToggle={ ( buttonProps ) => (
@@ -268,6 +208,7 @@ export default function Media( { data, field, onChange, config = {} } ) {
268
208
  attachment={ attachment }
269
209
  field={ field }
270
210
  data={ data }
211
+ config={ config }
271
212
  />
272
213
  <span className="block-editor-content-only-controls__media-title">
273
214
  {
@@ -33,10 +33,6 @@ export default function RichTextControl( {
33
33
  const attrValue = field.getValue( { item: data } );
34
34
  const fieldConfig = field.config || {};
35
35
  const { clientId } = config;
36
- const updateAttributes = ( html ) => {
37
- const mappedChanges = field.setValue( { item: data, value: html } );
38
- onChange( mappedChanges );
39
- };
40
36
  const [ selection, setSelection ] = useState( {
41
37
  start: undefined,
42
38
  end: undefined,
@@ -107,7 +103,7 @@ export default function RichTextControl( {
107
103
  } = useRichText( {
108
104
  value: attrValue,
109
105
  onChange( html, { __unstableFormats, __unstableText } ) {
110
- updateAttributes( html );
106
+ onChange( field.setValue( { item: data, value: html } ) );
111
107
  Object.values( changeHandlers ).forEach( ( changeHandler ) => {
112
108
  changeHandler( __unstableFormats, __unstableText );
113
109
  } );
@@ -30,4 +30,6 @@
30
30
 
31
31
  .block-editor-block-fields__header-title {
32
32
  flex: 1;
33
+ // Override the default margin on a h2 element.
34
+ margin: 0 !important;
33
35
  }