@wordpress/block-editor 15.16.0 → 15.17.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 (157) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/autocomplete/index.cjs +1 -2
  3. package/build/components/autocomplete/index.cjs.map +3 -3
  4. package/build/components/block-list/block-crash-warning.cjs +4 -2
  5. package/build/components/block-list/block-crash-warning.cjs.map +2 -2
  6. package/build/components/block-list/block-html.cjs +6 -2
  7. package/build/components/block-list/block-html.cjs.map +2 -2
  8. package/build/components/block-mover/button.cjs +4 -3
  9. package/build/components/block-mover/button.cjs.map +2 -2
  10. package/build/components/block-mover/index.cjs +1 -8
  11. package/build/components/block-mover/index.cjs.map +2 -2
  12. package/build/components/copy-handler/index.cjs.map +2 -2
  13. package/build/components/iframe/get-compatibility-styles.cjs +1 -1
  14. package/build/components/iframe/get-compatibility-styles.cjs.map +2 -2
  15. package/build/components/inserter/media-tab/hooks.cjs +1 -1
  16. package/build/components/inserter/media-tab/hooks.cjs.map +2 -2
  17. package/build/components/link-control/link-preview.cjs +3 -1
  18. package/build/components/link-control/link-preview.cjs.map +2 -2
  19. package/build/components/link-control/search-input.cjs +1 -1
  20. package/build/components/link-control/search-input.cjs.map +2 -2
  21. package/build/components/link-picker/link-preview.cjs +2 -1
  22. package/build/components/link-picker/link-preview.cjs.map +3 -3
  23. package/build/components/preset-input-control/index.cjs +7 -4
  24. package/build/components/preset-input-control/index.cjs.map +2 -2
  25. package/build/components/rich-text/event-listeners/index.cjs.map +2 -2
  26. package/build/components/rich-text/index.cjs +1 -1
  27. package/build/components/rich-text/index.cjs.map +2 -2
  28. package/build/components/rich-text/input-event.cjs +3 -3
  29. package/build/components/rich-text/input-event.cjs.map +2 -2
  30. package/build/components/spacing-sizes-control/utils.cjs +1 -1
  31. package/build/components/spacing-sizes-control/utils.cjs.map +2 -2
  32. package/build/components/use-on-block-drop/index.cjs +1 -1
  33. package/build/components/use-on-block-drop/index.cjs.map +2 -2
  34. package/build/components/use-paste-styles/index.cjs +2 -2
  35. package/build/components/use-paste-styles/index.cjs.map +2 -2
  36. package/build/components/writing-flow/use-selection-observer.cjs +23 -1
  37. package/build/components/writing-flow/use-selection-observer.cjs.map +2 -2
  38. package/build/hooks/block-style-variation.cjs +5 -6
  39. package/build/hooks/block-style-variation.cjs.map +3 -3
  40. package/build/hooks/index.cjs +2 -2
  41. package/build/hooks/index.cjs.map +2 -2
  42. package/build/hooks/style.cjs +54 -52
  43. package/build/hooks/style.cjs.map +2 -2
  44. package/build/private-apis.cjs +1 -1
  45. package/build/private-apis.cjs.map +1 -1
  46. package/build/store/actions.cjs +0 -8
  47. package/build/store/actions.cjs.map +2 -2
  48. package/build/store/reducer.cjs +52 -53
  49. package/build/store/reducer.cjs.map +2 -2
  50. package/build/store/selectors.cjs +12 -13
  51. package/build/store/selectors.cjs.map +2 -2
  52. package/build/store/utils.cjs +1 -1
  53. package/build/store/utils.cjs.map +2 -2
  54. package/build/utils/pasting.cjs +1 -1
  55. package/build/utils/pasting.cjs.map +2 -2
  56. package/build-module/components/autocomplete/index.mjs +1 -2
  57. package/build-module/components/autocomplete/index.mjs.map +2 -2
  58. package/build-module/components/block-list/block-crash-warning.mjs +4 -2
  59. package/build-module/components/block-list/block-crash-warning.mjs.map +2 -2
  60. package/build-module/components/block-list/block-html.mjs +7 -3
  61. package/build-module/components/block-list/block-html.mjs.map +2 -2
  62. package/build-module/components/block-mover/button.mjs +4 -3
  63. package/build-module/components/block-mover/button.mjs.map +2 -2
  64. package/build-module/components/block-mover/index.mjs +1 -8
  65. package/build-module/components/block-mover/index.mjs.map +2 -2
  66. package/build-module/components/copy-handler/index.mjs.map +2 -2
  67. package/build-module/components/iframe/get-compatibility-styles.mjs +1 -1
  68. package/build-module/components/iframe/get-compatibility-styles.mjs.map +2 -2
  69. package/build-module/components/inserter/media-tab/hooks.mjs +1 -1
  70. package/build-module/components/inserter/media-tab/hooks.mjs.map +2 -2
  71. package/build-module/components/link-control/link-preview.mjs +3 -1
  72. package/build-module/components/link-control/link-preview.mjs.map +2 -2
  73. package/build-module/components/link-control/search-input.mjs +1 -1
  74. package/build-module/components/link-control/search-input.mjs.map +2 -2
  75. package/build-module/components/link-picker/link-preview.mjs +2 -1
  76. package/build-module/components/link-picker/link-preview.mjs.map +2 -2
  77. package/build-module/components/preset-input-control/index.mjs +7 -4
  78. package/build-module/components/preset-input-control/index.mjs.map +2 -2
  79. package/build-module/components/rich-text/event-listeners/index.mjs.map +2 -2
  80. package/build-module/components/rich-text/index.mjs +2 -2
  81. package/build-module/components/rich-text/index.mjs.map +2 -2
  82. package/build-module/components/rich-text/input-event.mjs +2 -2
  83. package/build-module/components/rich-text/input-event.mjs.map +2 -2
  84. package/build-module/components/spacing-sizes-control/utils.mjs +1 -1
  85. package/build-module/components/spacing-sizes-control/utils.mjs.map +2 -2
  86. package/build-module/components/use-on-block-drop/index.mjs +1 -1
  87. package/build-module/components/use-on-block-drop/index.mjs.map +2 -2
  88. package/build-module/components/use-paste-styles/index.mjs +2 -2
  89. package/build-module/components/use-paste-styles/index.mjs.map +2 -2
  90. package/build-module/components/writing-flow/use-selection-observer.mjs +23 -1
  91. package/build-module/components/writing-flow/use-selection-observer.mjs.map +2 -2
  92. package/build-module/hooks/block-style-variation.mjs +4 -5
  93. package/build-module/hooks/block-style-variation.mjs.map +2 -2
  94. package/build-module/hooks/index.mjs +2 -2
  95. package/build-module/hooks/index.mjs.map +2 -2
  96. package/build-module/hooks/style.mjs +54 -52
  97. package/build-module/hooks/style.mjs.map +2 -2
  98. package/build-module/private-apis.mjs +2 -2
  99. package/build-module/private-apis.mjs.map +1 -1
  100. package/build-module/store/actions.mjs +0 -8
  101. package/build-module/store/actions.mjs.map +2 -2
  102. package/build-module/store/reducer.mjs +52 -53
  103. package/build-module/store/reducer.mjs.map +2 -2
  104. package/build-module/store/selectors.mjs +12 -13
  105. package/build-module/store/selectors.mjs.map +2 -2
  106. package/build-module/store/utils.mjs +1 -1
  107. package/build-module/store/utils.mjs.map +2 -2
  108. package/build-module/utils/pasting.mjs +1 -1
  109. package/build-module/utils/pasting.mjs.map +2 -2
  110. package/build-style/style-rtl.css +0 -7
  111. package/build-style/style.css +0 -7
  112. package/package.json +38 -39
  113. package/src/autocompleters/style.scss +0 -8
  114. package/src/components/autocomplete/index.js +1 -2
  115. package/src/components/block-draggable/test/helpers.native.js +1 -1
  116. package/src/components/block-list/block-crash-warning.js +3 -1
  117. package/src/components/block-list/block-crash-warning.native.js +3 -1
  118. package/src/components/block-list/block-html.js +13 -3
  119. package/src/components/block-mover/button.js +7 -4
  120. package/src/components/block-mover/index.js +1 -8
  121. package/src/components/copy-handler/index.js +1 -0
  122. package/src/components/iframe/get-compatibility-styles.js +1 -1
  123. package/src/components/inserter/media-tab/hooks.js +1 -1
  124. package/src/components/link-control/README.md +2 -2
  125. package/src/components/link-control/link-preview.js +3 -1
  126. package/src/components/link-control/search-input.js +1 -1
  127. package/src/components/link-picker/link-preview.js +2 -1
  128. package/src/components/preset-input-control/index.js +10 -4
  129. package/src/components/preset-input-control/test/index.js +70 -0
  130. package/src/components/rich-text/event-listeners/index.js +1 -0
  131. package/src/components/rich-text/index.js +1 -1
  132. package/src/components/rich-text/index.native.js +1 -1
  133. package/src/components/rich-text/input-event.js +1 -1
  134. package/src/components/rich-text/input-event.native.js +1 -1
  135. package/src/components/rich-text/native/index.native.js +18 -17
  136. package/src/components/spacing-sizes-control/utils.js +1 -1
  137. package/src/components/use-on-block-drop/index.js +1 -1
  138. package/src/components/use-paste-styles/index.js +2 -2
  139. package/src/components/writing-flow/use-selection-observer.js +39 -1
  140. package/src/hooks/block-style-variation.js +3 -4
  141. package/src/hooks/index.js +1 -1
  142. package/src/hooks/style.js +75 -61
  143. package/src/private-apis.js +2 -2
  144. package/src/store/actions.js +0 -8
  145. package/src/store/reducer.js +62 -67
  146. package/src/store/selectors.js +31 -21
  147. package/src/store/test/private-selectors.js +44 -41
  148. package/src/store/test/reducer.js +159 -88
  149. package/src/store/test/registry-selectors.js +1 -1
  150. package/src/store/test/selectors.js +265 -200
  151. package/src/store/utils.js +1 -1
  152. package/src/utils/pasting.js +1 -1
  153. package/build/autocompleters/link.cjs +0 -81
  154. package/build/autocompleters/link.cjs.map +0 -7
  155. package/build-module/autocompleters/link.mjs +0 -50
  156. package/build-module/autocompleters/link.mjs.map +0 -7
  157. package/src/autocompleters/link.js +0 -63
@@ -15,7 +15,6 @@ import {
15
15
  showUserSuggestions,
16
16
  showXpostSuggestions,
17
17
  } from '@wordpress/react-native-bridge';
18
- import { BlockFormatControls } from '@wordpress/block-editor';
19
18
  import { getPxFromCssUnit } from '@wordpress/components';
20
19
  import { Component } from '@wordpress/element';
21
20
  import {
@@ -42,6 +41,7 @@ import {
42
41
  isCollapsed,
43
42
  remove,
44
43
  } from '@wordpress/rich-text';
44
+ import { BlockFormatControls } from '@wordpress/block-editor';
45
45
 
46
46
  /**
47
47
  * Internal dependencies
@@ -1342,22 +1342,23 @@ RichText.defaultProps = {
1342
1342
  tagName: 'div',
1343
1343
  };
1344
1344
 
1345
- const withFormatTypes = ( WrappedComponent ) => ( props ) => {
1346
- const {
1347
- clientId,
1348
- identifier,
1349
- withoutInteractiveFormatting,
1350
- allowedFormats,
1351
- } = props;
1352
- const { formatTypes } = useFormatTypes( {
1353
- clientId,
1354
- identifier,
1355
- withoutInteractiveFormatting,
1356
- allowedFormats,
1357
- } );
1358
-
1359
- return <WrappedComponent { ...props } formatTypes={ formatTypes } />;
1360
- };
1345
+ const withFormatTypes = ( WrappedComponent ) =>
1346
+ function WithFormatTypes( props ) {
1347
+ const {
1348
+ clientId,
1349
+ identifier,
1350
+ withoutInteractiveFormatting,
1351
+ allowedFormats,
1352
+ } = props;
1353
+ const { formatTypes } = useFormatTypes( {
1354
+ clientId,
1355
+ identifier,
1356
+ withoutInteractiveFormatting,
1357
+ allowedFormats,
1358
+ } );
1359
+
1360
+ return <WrappedComponent { ...props } formatTypes={ formatTypes } />;
1361
+ };
1361
1362
 
1362
1363
  export default compose( [
1363
1364
  withSelect( ( select, { clientId } ) => {
@@ -14,7 +14,7 @@ import {
14
14
 
15
15
  export const RANGE_CONTROL_MAX_SIZE = 8;
16
16
 
17
- export const ALL_SIDES = [ 'top', 'right', 'bottom', 'left' ];
17
+ export const ALL_SIDES = [ 'top', 'bottom', 'left', 'right' ];
18
18
 
19
19
  export const DEFAULT_VALUES = {
20
20
  top: undefined,
@@ -46,7 +46,7 @@ export function parseDropEvent( event ) {
46
46
  result,
47
47
  JSON.parse( event.dataTransfer.getData( 'wp-blocks' ) )
48
48
  );
49
- } catch ( err ) {
49
+ } catch {
50
50
  return result;
51
51
  }
52
52
 
@@ -44,7 +44,7 @@ function hasSerializedBlocks( text ) {
44
44
  return false;
45
45
  }
46
46
  return true;
47
- } catch ( err ) {
47
+ } catch {
48
48
  // Parsing error, the text is not serialized blocks.
49
49
  // (Even though that it technically won't happen)
50
50
  return false;
@@ -153,7 +153,7 @@ export default function usePasteStyles() {
153
153
  }
154
154
 
155
155
  html = await window.navigator.clipboard.readText();
156
- } catch ( error ) {
156
+ } catch {
157
157
  // Possibly the permission is denied.
158
158
  createErrorNotice(
159
159
  __(
@@ -214,7 +214,45 @@ export default function useSelectionObserver() {
214
214
  const isSingularSelection = startClientId === endClientId;
215
215
  if ( isSingularSelection ) {
216
216
  if ( ! isMultiSelecting() ) {
217
- selectBlock( startClientId );
217
+ // If the selection is not collapsed and falls
218
+ // within a RichText that doesn't have focus
219
+ // (e.g. the user started dragging from the block
220
+ // wrapper padding), dispatch a full
221
+ // selectionChange so the format toolbar appears.
222
+ const richTextElement =
223
+ ! selection.isCollapsed &&
224
+ ( getRichTextElement( startNode ) ||
225
+ getRichTextElement( endNode ) );
226
+
227
+ if (
228
+ richTextElement &&
229
+ ownerDocument.activeElement !== richTextElement
230
+ ) {
231
+ const range = selection.getRangeAt( 0 );
232
+ const richTextData = create( {
233
+ element: richTextElement,
234
+ range,
235
+ __unstableIsEditableTree: true,
236
+ } );
237
+ selectionChange( {
238
+ start: {
239
+ clientId: startClientId,
240
+ attributeKey:
241
+ richTextElement.dataset
242
+ .wpBlockAttributeKey,
243
+ offset: richTextData.start ?? 0,
244
+ },
245
+ end: {
246
+ clientId: startClientId,
247
+ attributeKey:
248
+ richTextElement.dataset
249
+ .wpBlockAttributeKey,
250
+ offset: richTextData.end,
251
+ },
252
+ } );
253
+ } else {
254
+ selectBlock( startClientId );
255
+ }
218
256
  } else {
219
257
  multiSelect( startClientId, startClientId );
220
258
  }
@@ -73,10 +73,9 @@ function OverrideStyles( { override } ) {
73
73
  * @param {Object} props.config A global styles object, containing settings and styles.
74
74
  * @return {React.JSX.Element} An array of new block variation overrides.
75
75
  */
76
- export function __unstableBlockStyleVariationOverridesWithConfig( { config } ) {
77
- const { getBlockStyles, overrides } = useSelect(
76
+ export function BlockStyleVariationOverridesWithConfig( { config } ) {
77
+ const { overrides } = useSelect(
78
78
  ( select ) => ( {
79
- getBlockStyles: select( blocksStore ).getBlockStyles,
80
79
  overrides: unlock( select( blockEditorStore ) ).getStyleOverrides(),
81
80
  } ),
82
81
  []
@@ -161,7 +160,7 @@ export function __unstableBlockStyleVariationOverridesWithConfig( { config } ) {
161
160
  }
162
161
  }
163
162
  return newOverrides;
164
- }, [ config, overrides, getBlockStyles, getBlockName ] );
163
+ }, [ config, overrides, getBlockName ] );
165
164
 
166
165
  if ( ! overridesWithConfig || ! overridesWithConfig.length ) {
167
166
  return null;
@@ -105,5 +105,5 @@ export { getGapCSSValue } from './gap';
105
105
  export { useCachedTruthy } from './use-cached-truthy';
106
106
  export { setBackgroundStyleDefaults } from './background';
107
107
  export { useZoomOut } from './use-zoom-out';
108
- export { __unstableBlockStyleVariationOverridesWithConfig } from './block-style-variation';
108
+ export { BlockStyleVariationOverridesWithConfig } from './block-style-variation';
109
109
  export { useStyleOverride } from './utils';
@@ -382,84 +382,98 @@ const elementTypes = [
382
382
  // Used for generating the instance ID
383
383
  const STYLE_BLOCK_PROPS_REFERENCE = {};
384
384
 
385
- function useBlockProps( { name, style } ) {
386
- const blockElementsContainerIdentifier = useInstanceId(
387
- STYLE_BLOCK_PROPS_REFERENCE,
388
- 'wp-elements'
389
- );
385
+ /**
386
+ * Generates CSS rules for block element styles (buttons, links, headings, etc.).
387
+ *
388
+ * Iterates over supported element types and compiles their styles, including
389
+ * pseudo-selectors (e.g. :hover) and related sub-elements (e.g. h1-h6 for headings),
390
+ * into scoped CSS rule strings.
391
+ *
392
+ * @param {Object} blockElementStyles The block's `style.elements` object.
393
+ * @param {string} blockName The block name, used for skip-serialization checks.
394
+ * @param {string} baseSelector The base CSS selector to scope rules under.
395
+ * @return {string|undefined} Concatenated CSS rules string, or undefined if none.
396
+ */
397
+ function getElementCSSRules( blockElementStyles, blockName, baseSelector ) {
398
+ if ( ! blockElementStyles ) {
399
+ return;
400
+ }
390
401
 
391
- const baseElementSelector = `.${ blockElementsContainerIdentifier }`;
392
- const blockElementStyles = style?.elements;
402
+ const rules = [];
393
403
 
394
- const styles = useMemo( () => {
395
- if ( ! blockElementStyles ) {
404
+ elementTypes.forEach( ( { elementType, pseudo, elements } ) => {
405
+ const skipSerialization = shouldSkipSerialization(
406
+ blockName,
407
+ COLOR_SUPPORT_KEY,
408
+ elementType
409
+ );
410
+
411
+ if ( skipSerialization ) {
396
412
  return;
397
413
  }
398
414
 
399
- const elementCSSRules = [];
415
+ const elementStyles = blockElementStyles?.[ elementType ];
400
416
 
401
- elementTypes.forEach( ( { elementType, pseudo, elements } ) => {
402
- const skipSerialization = shouldSkipSerialization(
403
- name,
404
- COLOR_SUPPORT_KEY,
405
- elementType
417
+ // Process primary element type styles.
418
+ if ( elementStyles ) {
419
+ const selector = scopeSelector(
420
+ baseSelector,
421
+ ELEMENTS[ elementType ]
406
422
  );
407
423
 
408
- if ( skipSerialization ) {
409
- return;
410
- }
424
+ rules.push( compileCSS( elementStyles, { selector } ) );
411
425
 
412
- const elementStyles = blockElementStyles?.[ elementType ];
413
-
414
- // Process primary element type styles.
415
- if ( elementStyles ) {
416
- const selector = scopeSelector(
417
- baseElementSelector,
418
- ELEMENTS[ elementType ]
419
- );
420
-
421
- elementCSSRules.push(
422
- compileCSS( elementStyles, { selector } )
423
- );
424
-
425
- // Process any interactive states for the element type.
426
- if ( pseudo ) {
427
- pseudo.forEach( ( pseudoSelector ) => {
428
- if ( elementStyles[ pseudoSelector ] ) {
429
- elementCSSRules.push(
430
- compileCSS( elementStyles[ pseudoSelector ], {
431
- selector: scopeSelector(
432
- baseElementSelector,
433
- `${ ELEMENTS[ elementType ] }${ pseudoSelector }`
434
- ),
435
- } )
436
- );
437
- }
438
- } );
439
- }
440
- }
441
-
442
- // Process related elements e.g. h1-h6 for headings
443
- if ( elements ) {
444
- elements.forEach( ( element ) => {
445
- if ( blockElementStyles[ element ] ) {
446
- elementCSSRules.push(
447
- compileCSS( blockElementStyles[ element ], {
426
+ // Process any interactive states for the element type.
427
+ if ( pseudo ) {
428
+ pseudo.forEach( ( pseudoSelector ) => {
429
+ if ( elementStyles[ pseudoSelector ] ) {
430
+ rules.push(
431
+ compileCSS( elementStyles[ pseudoSelector ], {
448
432
  selector: scopeSelector(
449
- baseElementSelector,
450
- ELEMENTS[ element ]
433
+ baseSelector,
434
+ `${ ELEMENTS[ elementType ] }${ pseudoSelector }`
451
435
  ),
452
436
  } )
453
437
  );
454
438
  }
455
439
  } );
456
440
  }
457
- } );
441
+ }
442
+
443
+ // Process related elements e.g. h1-h6 for headings
444
+ if ( elements ) {
445
+ elements.forEach( ( element ) => {
446
+ if ( blockElementStyles[ element ] ) {
447
+ rules.push(
448
+ compileCSS( blockElementStyles[ element ], {
449
+ selector: scopeSelector(
450
+ baseSelector,
451
+ ELEMENTS[ element ]
452
+ ),
453
+ } )
454
+ );
455
+ }
456
+ } );
457
+ }
458
+ } );
459
+
460
+ return rules.length > 0 ? rules.join( '' ) : undefined;
461
+ }
462
+
463
+ function useBlockProps( { name, style } ) {
464
+ const blockElementsContainerIdentifier = useInstanceId(
465
+ STYLE_BLOCK_PROPS_REFERENCE,
466
+ 'wp-elements'
467
+ );
458
468
 
459
- return elementCSSRules.length > 0
460
- ? elementCSSRules.join( '' )
461
- : undefined;
462
- }, [ baseElementSelector, blockElementStyles, name ] );
469
+ const baseElementSelector = `.${ blockElementsContainerIdentifier }`;
470
+ const blockElementStyles = style?.elements;
471
+
472
+ const styles = useMemo(
473
+ () =>
474
+ getElementCSSRules( blockElementStyles, name, baseElementSelector ),
475
+ [ baseElementSelector, blockElementStyles, name ]
476
+ );
463
477
 
464
478
  useStyleOverride( { css: styles } );
465
479
 
@@ -24,7 +24,7 @@ import {
24
24
  setBackgroundStyleDefaults,
25
25
  useLayoutClasses,
26
26
  useLayoutStyles,
27
- __unstableBlockStyleVariationOverridesWithConfig,
27
+ BlockStyleVariationOverridesWithConfig,
28
28
  useZoomOut,
29
29
  } from './hooks';
30
30
  import DimensionsTool from './components/dimensions-tool';
@@ -121,7 +121,7 @@ lock( privateApis, {
121
121
  PrivatePublishDateTimePicker,
122
122
  useSpacingSizes,
123
123
  useBlockDisplayTitle,
124
- __unstableBlockStyleVariationOverridesWithConfig,
124
+ BlockStyleVariationOverridesWithConfig,
125
125
  setBackgroundStyleDefaults,
126
126
  sectionRootClientIdKey,
127
127
  CommentIconSlotFill,
@@ -1768,10 +1768,6 @@ export const insertBeforeBlock =
1768
1768
  return;
1769
1769
  }
1770
1770
  const rootClientId = select.getBlockRootClientId( clientId );
1771
- const isLocked = select.getTemplateLock( rootClientId );
1772
- if ( isLocked ) {
1773
- return;
1774
- }
1775
1771
 
1776
1772
  const blockIndex = select.getBlockIndex( clientId );
1777
1773
  const directInsertBlock = rootClientId
@@ -1811,10 +1807,6 @@ export const insertAfterBlock =
1811
1807
  return;
1812
1808
  }
1813
1809
  const rootClientId = select.getBlockRootClientId( clientId );
1814
- const isLocked = select.getTemplateLock( rootClientId );
1815
- if ( isLocked ) {
1816
- return;
1817
- }
1818
1810
 
1819
1811
  const blockIndex = select.getBlockIndex( clientId );
1820
1812
  const directInsertBlock = rootClientId
@@ -213,7 +213,7 @@ function updateParentInnerBlocksInTree(
213
213
  ? clientId
214
214
  : state.parents.get( clientId );
215
215
  do {
216
- if ( state.controlledInnerBlocks[ current ] ) {
216
+ if ( state.controlledInnerBlocks.has( current ) ) {
217
217
  // Should stop on controlled blocks.
218
218
  // If we reach a controlled parent, break out of the loop.
219
219
  controlledParents.add( current );
@@ -591,7 +591,7 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
591
591
  } );
592
592
 
593
593
  const preservedControlledInnerBlocks =
594
- state?.controlledInnerBlocks ?? {};
594
+ state?.controlledInnerBlocks ?? new Set();
595
595
 
596
596
  // Preserve controlled inner blocks data from the old state.
597
597
  // The maps above are rebuilt solely from action.blocks, but
@@ -599,17 +599,12 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
599
599
  // present in action.blocks. Re-inject them so the state
600
600
  // remains consistent with the preserved flags.
601
601
  if ( state?.order ) {
602
- for ( const clientId of Object.keys(
603
- preservedControlledInnerBlocks
604
- ) ) {
605
- if ( ! preservedControlledInnerBlocks[ clientId ] ) {
606
- continue;
607
- }
602
+ for ( const clientId of preservedControlledInnerBlocks ) {
608
603
  // Only preserve if the parent block still exists.
609
604
  if ( ! newState.byClientId.has( clientId ) ) {
610
605
  continue;
611
606
  }
612
- newState.controlledInnerBlocks[ clientId ] = true;
607
+ newState.controlledInnerBlocks.add( clientId );
613
608
  const oldOrder = state.order.get( clientId );
614
609
  if ( ! oldOrder?.length ) {
615
610
  continue;
@@ -641,9 +636,7 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
641
636
  // (entity-level IDs), but we need them to reference the preserved
642
637
  // cloned inner blocks instead. Mutating the existing object
643
638
  // preserves references held by ancestor tree entries.
644
- for ( const clientId of Object.keys(
645
- newState.controlledInnerBlocks
646
- ) ) {
639
+ for ( const clientId of newState.controlledInnerBlocks ) {
647
640
  const controlledOrder = newState.order.get( clientId );
648
641
  if ( ! controlledOrder?.length ) {
649
642
  continue;
@@ -707,12 +700,12 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => {
707
700
  // inner blocks from the block state because its inner blocks will not be
708
701
  // attached to the block in the action.
709
702
  const nestedControllers = {};
710
- if ( Object.keys( state.controlledInnerBlocks ).length ) {
703
+ if ( state.controlledInnerBlocks.size ) {
711
704
  const stack = [ ...action.blocks ];
712
705
  while ( stack.length ) {
713
706
  const { innerBlocks, ...block } = stack.shift();
714
707
  stack.push( ...innerBlocks );
715
- if ( !! state.controlledInnerBlocks[ block.clientId ] ) {
708
+ if ( state.controlledInnerBlocks.has( block.clientId ) ) {
716
709
  nestedControllers[ block.clientId ] = true;
717
710
  }
718
711
  }
@@ -1222,14 +1215,22 @@ export const blocks = pipe(
1222
1215
  },
1223
1216
 
1224
1217
  controlledInnerBlocks(
1225
- state = {},
1218
+ state = new Set(),
1226
1219
  { type, clientId, hasControlledInnerBlocks }
1227
1220
  ) {
1228
1221
  if ( type === 'SET_HAS_CONTROLLED_INNER_BLOCKS' ) {
1229
- return {
1230
- ...state,
1231
- [ clientId ]: hasControlledInnerBlocks,
1232
- };
1222
+ if ( hasControlledInnerBlocks ) {
1223
+ if ( state.has( clientId ) ) {
1224
+ return state;
1225
+ }
1226
+ return new Set( state ).add( clientId );
1227
+ }
1228
+ if ( ! state.has( clientId ) ) {
1229
+ return state;
1230
+ }
1231
+ const newState = new Set( state );
1232
+ newState.delete( clientId );
1233
+ return newState;
1233
1234
  }
1234
1235
  return state;
1235
1236
  },
@@ -1828,12 +1829,12 @@ export function preferences( state = PREFERENCES_DEFAULTS, action ) {
1828
1829
  * Reducer returning an object where each key is a block client ID, its value
1829
1830
  * representing the settings for its nested blocks.
1830
1831
  *
1831
- * @param {Object} state Current state.
1832
+ * @param {Map} state Current state.
1832
1833
  * @param {Object} action Dispatched action.
1833
1834
  *
1834
1835
  * @return {Object} Updated state.
1835
1836
  */
1836
- export const blockListSettings = ( state = {}, action ) => {
1837
+ export const blockListSettings = ( state = new Map(), action ) => {
1837
1838
  switch ( action.type ) {
1838
1839
  case 'REPLACE_BLOCKS': {
1839
1840
  // Collect all clientIds from replacement blocks. If a clientId
@@ -1848,53 +1849,48 @@ export const blockListSettings = ( state = {}, action ) => {
1848
1849
  replacementIds.add( block.clientId );
1849
1850
  stack.push( ...block.innerBlocks );
1850
1851
  }
1851
- return Object.fromEntries(
1852
- Object.entries( state ).filter(
1853
- ( [ id ] ) =>
1854
- ! action.clientIds.includes( id ) ||
1855
- replacementIds.has( id )
1856
- )
1857
- );
1852
+ const newState = new Map( state );
1853
+ for ( const clientId of action.clientIds ) {
1854
+ if ( ! replacementIds.has( clientId ) ) {
1855
+ newState.delete( clientId );
1856
+ }
1857
+ }
1858
+ return newState;
1858
1859
  }
1859
1860
  case 'REMOVE_BLOCKS': {
1860
- return Object.fromEntries(
1861
- Object.entries( state ).filter(
1862
- ( [ id ] ) => ! action.clientIds.includes( id )
1863
- )
1864
- );
1861
+ const newState = new Map( state );
1862
+ for ( const clientId of action.clientIds ) {
1863
+ newState.delete( clientId );
1864
+ }
1865
+ return newState;
1865
1866
  }
1866
1867
  case 'UPDATE_BLOCK_LIST_SETTINGS': {
1867
1868
  const updates =
1868
1869
  typeof action.clientId === 'string'
1869
- ? { [ action.clientId ]: action.settings }
1870
- : action.clientId;
1871
-
1872
- // Remove settings that are the same as the current state.
1873
- for ( const clientId in updates ) {
1874
- if ( ! updates[ clientId ] ) {
1875
- if ( ! state[ clientId ] ) {
1876
- delete updates[ clientId ];
1877
- }
1878
- } else if (
1879
- fastDeepEqual( state[ clientId ], updates[ clientId ] )
1880
- ) {
1881
- delete updates[ clientId ];
1882
- }
1883
- }
1870
+ ? [ [ action.clientId, action.settings ] ]
1871
+ : Object.entries( action.clientId );
1872
+
1873
+ const relevantUpdates = updates.filter(
1874
+ ( [ clientId, nextSettings ] ) =>
1875
+ ! nextSettings
1876
+ ? state.has( clientId )
1877
+ : ! fastDeepEqual( state.get( clientId ), nextSettings )
1878
+ );
1884
1879
 
1885
- if ( Object.keys( updates ).length === 0 ) {
1880
+ if ( ! relevantUpdates.length ) {
1886
1881
  return state;
1887
1882
  }
1888
1883
 
1889
- const merged = { ...state, ...updates };
1890
-
1891
- for ( const clientId in updates ) {
1892
- if ( ! updates[ clientId ] ) {
1893
- delete merged[ clientId ];
1884
+ const newState = new Map( state );
1885
+ for ( const [ clientId, nextSettings ] of relevantUpdates ) {
1886
+ if ( ! nextSettings ) {
1887
+ newState.delete( clientId );
1888
+ } else {
1889
+ newState.set( clientId, nextSettings );
1894
1890
  }
1895
1891
  }
1896
1892
 
1897
- return merged;
1893
+ return newState;
1898
1894
  }
1899
1895
  }
1900
1896
  return state;
@@ -2369,7 +2365,7 @@ function getBlockTreeBlock( state, clientId ) {
2369
2365
  };
2370
2366
  }
2371
2367
 
2372
- if ( ! state.blocks.controlledInnerBlocks[ clientId ] ) {
2368
+ if ( ! state.blocks.controlledInnerBlocks.has( clientId ) ) {
2373
2369
  return state.blocks.tree.get( clientId );
2374
2370
  }
2375
2371
 
@@ -2468,7 +2464,7 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2468
2464
  const templatePartClientIds = [];
2469
2465
  const syncedPatternClientIds = [];
2470
2466
 
2471
- Object.keys( state.blocks.controlledInnerBlocks ).forEach( ( clientId ) => {
2467
+ state.blocks.controlledInnerBlocks.forEach( ( clientId ) => {
2472
2468
  const block = state.blocks.byClientId?.get( clientId );
2473
2469
 
2474
2470
  if ( block?.name === 'core/template-part' ) {
@@ -2479,11 +2475,10 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2479
2475
  syncedPatternClientIds.push( clientId );
2480
2476
  }
2481
2477
  } );
2482
- const contentOnlyTemplateLockedClientIds = Object.keys(
2478
+ const contentOnlyTemplateLockedClientIds = Array.from(
2483
2479
  state.blockListSettings
2484
- ).filter(
2485
- ( clientId ) =>
2486
- state.blockListSettings[ clientId ]?.templateLock === 'contentOnly'
2480
+ ).flatMap( ( [ clientId, listSettings ] ) =>
2481
+ listSettings?.templateLock === 'contentOnly' ? [ clientId ] : []
2487
2482
  );
2488
2483
 
2489
2484
  // When in an isolated editing context (e.g., editing a template part or pattern directly),
@@ -2889,15 +2884,15 @@ export function withDerivedBlockEditingModes( reducer ) {
2889
2884
 
2890
2885
  for ( const clientId in updates ) {
2891
2886
  const isNewContentOnlyBlock =
2892
- state.blockListSettings[ clientId ]?.templateLock !==
2893
- 'contentOnly' &&
2894
- nextState.blockListSettings[ clientId ]
2887
+ state.blockListSettings.get( clientId )
2888
+ ?.templateLock !== 'contentOnly' &&
2889
+ nextState.blockListSettings.get( clientId )
2895
2890
  ?.templateLock === 'contentOnly';
2896
2891
 
2897
2892
  const wasContentOnlyBlock =
2898
- state.blockListSettings[ clientId ]?.templateLock ===
2899
- 'contentOnly' &&
2900
- nextState.blockListSettings[ clientId ]
2893
+ state.blockListSettings.get( clientId )
2894
+ ?.templateLock === 'contentOnly' &&
2895
+ nextState.blockListSettings.get( clientId )
2901
2896
  ?.templateLock !== 'contentOnly';
2902
2897
 
2903
2898
  if ( isNewContentOnlyBlock ) {