@wordpress/block-editor 15.15.0 → 15.16.1-next.v.202604091042.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 (197) 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-inspector/index.cjs +2 -1
  5. package/build/components/block-inspector/index.cjs.map +2 -2
  6. package/build/components/block-list/block-crash-warning.cjs +4 -2
  7. package/build/components/block-list/block-crash-warning.cjs.map +2 -2
  8. package/build/components/block-list/block-html.cjs +6 -2
  9. package/build/components/block-list/block-html.cjs.map +2 -2
  10. package/build/components/block-mover/button.cjs +4 -3
  11. package/build/components/block-mover/button.cjs.map +2 -2
  12. package/build/components/block-mover/index.cjs +1 -8
  13. package/build/components/block-mover/index.cjs.map +2 -2
  14. package/build/components/block-visibility/modal.cjs +2 -2
  15. package/build/components/block-visibility/modal.cjs.map +1 -1
  16. package/build/components/block-visibility/viewport-visibility-info.cjs +6 -1
  17. package/build/components/block-visibility/viewport-visibility-info.cjs.map +2 -2
  18. package/build/components/global-styles/background-panel.cjs +142 -33
  19. package/build/components/global-styles/background-panel.cjs.map +2 -2
  20. package/build/components/global-styles/color-panel.cjs +18 -7
  21. package/build/components/global-styles/color-panel.cjs.map +2 -2
  22. package/build/components/global-styles/hooks.cjs +8 -4
  23. package/build/components/global-styles/hooks.cjs.map +2 -2
  24. package/build/components/iframe/get-compatibility-styles.cjs +1 -1
  25. package/build/components/iframe/get-compatibility-styles.cjs.map +2 -2
  26. package/build/components/inserter/media-tab/hooks.cjs +1 -1
  27. package/build/components/inserter/media-tab/hooks.cjs.map +2 -2
  28. package/build/components/inspector-controls-tabs/styles-tab.cjs +2 -1
  29. package/build/components/inspector-controls-tabs/styles-tab.cjs.map +2 -2
  30. package/build/components/link-control/search-input.cjs +1 -1
  31. package/build/components/link-control/search-input.cjs.map +2 -2
  32. package/build/components/link-picker/link-preview.cjs +2 -1
  33. package/build/components/link-picker/link-preview.cjs.map +3 -3
  34. package/build/components/rich-text/index.cjs +1 -1
  35. package/build/components/rich-text/index.cjs.map +2 -2
  36. package/build/components/rich-text/input-event.cjs +3 -3
  37. package/build/components/rich-text/input-event.cjs.map +2 -2
  38. package/build/components/use-on-block-drop/index.cjs +1 -1
  39. package/build/components/use-on-block-drop/index.cjs.map +2 -2
  40. package/build/components/use-paste-styles/index.cjs +2 -2
  41. package/build/components/use-paste-styles/index.cjs.map +2 -2
  42. package/build/hooks/background.cjs +74 -21
  43. package/build/hooks/background.cjs.map +3 -3
  44. package/build/hooks/block-style-variation.cjs +5 -6
  45. package/build/hooks/block-style-variation.cjs.map +3 -3
  46. package/build/hooks/cross-origin-isolation.cjs +6 -6
  47. package/build/hooks/cross-origin-isolation.cjs.map +2 -2
  48. package/build/hooks/custom-css.cjs +5 -0
  49. package/build/hooks/custom-css.cjs.map +2 -2
  50. package/build/hooks/fit-text.cjs +46 -58
  51. package/build/hooks/fit-text.cjs.map +3 -3
  52. package/build/hooks/index.cjs +2 -2
  53. package/build/hooks/index.cjs.map +2 -2
  54. package/build/hooks/utils.cjs +5 -1
  55. package/build/hooks/utils.cjs.map +2 -2
  56. package/build/private-apis.cjs +1 -1
  57. package/build/private-apis.cjs.map +1 -1
  58. package/build/store/actions.cjs +8 -12
  59. package/build/store/actions.cjs.map +2 -2
  60. package/build/store/private-selectors.cjs +2 -2
  61. package/build/store/private-selectors.cjs.map +2 -2
  62. package/build/store/reducer.cjs +112 -146
  63. package/build/store/reducer.cjs.map +2 -2
  64. package/build/store/selectors.cjs +14 -15
  65. package/build/store/selectors.cjs.map +2 -2
  66. package/build/store/utils.cjs +1 -1
  67. package/build/store/utils.cjs.map +2 -2
  68. package/build/utils/pasting.cjs +1 -1
  69. package/build/utils/pasting.cjs.map +2 -2
  70. package/build-module/components/autocomplete/index.mjs +1 -2
  71. package/build-module/components/autocomplete/index.mjs.map +2 -2
  72. package/build-module/components/block-inspector/index.mjs +2 -1
  73. package/build-module/components/block-inspector/index.mjs.map +2 -2
  74. package/build-module/components/block-list/block-crash-warning.mjs +4 -2
  75. package/build-module/components/block-list/block-crash-warning.mjs.map +2 -2
  76. package/build-module/components/block-list/block-html.mjs +7 -3
  77. package/build-module/components/block-list/block-html.mjs.map +2 -2
  78. package/build-module/components/block-mover/button.mjs +4 -3
  79. package/build-module/components/block-mover/button.mjs.map +2 -2
  80. package/build-module/components/block-mover/index.mjs +1 -8
  81. package/build-module/components/block-mover/index.mjs.map +2 -2
  82. package/build-module/components/block-visibility/modal.mjs +2 -2
  83. package/build-module/components/block-visibility/modal.mjs.map +1 -1
  84. package/build-module/components/block-visibility/viewport-visibility-info.mjs +6 -1
  85. package/build-module/components/block-visibility/viewport-visibility-info.mjs.map +2 -2
  86. package/build-module/components/global-styles/background-panel.mjs +141 -34
  87. package/build-module/components/global-styles/background-panel.mjs.map +2 -2
  88. package/build-module/components/global-styles/color-panel.mjs +17 -7
  89. package/build-module/components/global-styles/color-panel.mjs.map +2 -2
  90. package/build-module/components/global-styles/hooks.mjs +8 -4
  91. package/build-module/components/global-styles/hooks.mjs.map +2 -2
  92. package/build-module/components/iframe/get-compatibility-styles.mjs +1 -1
  93. package/build-module/components/iframe/get-compatibility-styles.mjs.map +2 -2
  94. package/build-module/components/inserter/media-tab/hooks.mjs +1 -1
  95. package/build-module/components/inserter/media-tab/hooks.mjs.map +2 -2
  96. package/build-module/components/inspector-controls-tabs/styles-tab.mjs +2 -1
  97. package/build-module/components/inspector-controls-tabs/styles-tab.mjs.map +2 -2
  98. package/build-module/components/link-control/search-input.mjs +1 -1
  99. package/build-module/components/link-control/search-input.mjs.map +2 -2
  100. package/build-module/components/link-picker/link-preview.mjs +2 -1
  101. package/build-module/components/link-picker/link-preview.mjs.map +2 -2
  102. package/build-module/components/rich-text/index.mjs +2 -2
  103. package/build-module/components/rich-text/index.mjs.map +2 -2
  104. package/build-module/components/rich-text/input-event.mjs +2 -2
  105. package/build-module/components/rich-text/input-event.mjs.map +2 -2
  106. package/build-module/components/use-on-block-drop/index.mjs +1 -1
  107. package/build-module/components/use-on-block-drop/index.mjs.map +2 -2
  108. package/build-module/components/use-paste-styles/index.mjs +2 -2
  109. package/build-module/components/use-paste-styles/index.mjs.map +2 -2
  110. package/build-module/hooks/background.mjs +76 -22
  111. package/build-module/hooks/background.mjs.map +2 -2
  112. package/build-module/hooks/block-style-variation.mjs +4 -5
  113. package/build-module/hooks/block-style-variation.mjs.map +2 -2
  114. package/build-module/hooks/cross-origin-isolation.mjs +6 -6
  115. package/build-module/hooks/cross-origin-isolation.mjs.map +2 -2
  116. package/build-module/hooks/custom-css.mjs +5 -0
  117. package/build-module/hooks/custom-css.mjs.map +2 -2
  118. package/build-module/hooks/fit-text.mjs +46 -58
  119. package/build-module/hooks/fit-text.mjs.map +2 -2
  120. package/build-module/hooks/index.mjs +2 -2
  121. package/build-module/hooks/index.mjs.map +2 -2
  122. package/build-module/hooks/utils.mjs +5 -1
  123. package/build-module/hooks/utils.mjs.map +2 -2
  124. package/build-module/private-apis.mjs +2 -2
  125. package/build-module/private-apis.mjs.map +1 -1
  126. package/build-module/store/actions.mjs +8 -12
  127. package/build-module/store/actions.mjs.map +2 -2
  128. package/build-module/store/private-selectors.mjs +2 -2
  129. package/build-module/store/private-selectors.mjs.map +2 -2
  130. package/build-module/store/reducer.mjs +112 -145
  131. package/build-module/store/reducer.mjs.map +2 -2
  132. package/build-module/store/selectors.mjs +14 -15
  133. package/build-module/store/selectors.mjs.map +2 -2
  134. package/build-module/store/utils.mjs +1 -1
  135. package/build-module/store/utils.mjs.map +2 -2
  136. package/build-module/utils/pasting.mjs +1 -1
  137. package/build-module/utils/pasting.mjs.map +2 -2
  138. package/build-style/content-rtl.css +2 -2
  139. package/build-style/content.css +2 -2
  140. package/build-style/style-rtl.css +35 -14
  141. package/build-style/style.css +35 -14
  142. package/package.json +38 -39
  143. package/src/autocompleters/style.scss +0 -8
  144. package/src/components/autocomplete/index.js +1 -2
  145. package/src/components/background-image-control/style.scss +0 -4
  146. package/src/components/block-draggable/test/helpers.native.js +1 -1
  147. package/src/components/block-inspector/index.js +1 -0
  148. package/src/components/block-list/block-crash-warning.js +3 -1
  149. package/src/components/block-list/block-crash-warning.native.js +3 -1
  150. package/src/components/block-list/block-html.js +13 -3
  151. package/src/components/block-mover/button.js +7 -4
  152. package/src/components/block-mover/index.js +1 -8
  153. package/src/components/block-visibility/viewport-visibility-info.js +8 -1
  154. package/src/components/fit-text-size-warning/style.scss +1 -5
  155. package/src/components/global-styles/background-panel.js +157 -11
  156. package/src/components/global-styles/color-panel.js +23 -7
  157. package/src/components/global-styles/hooks.js +12 -4
  158. package/src/components/global-styles/test/background-panel.js +44 -1
  159. package/src/components/iframe/get-compatibility-styles.js +1 -1
  160. package/src/components/inserter/media-tab/hooks.js +1 -1
  161. package/src/components/inspector-controls-tabs/styles-tab.js +1 -0
  162. package/src/components/link-control/README.md +2 -2
  163. package/src/components/link-control/search-input.js +1 -1
  164. package/src/components/link-picker/link-preview.js +2 -1
  165. package/src/components/rich-text/index.js +1 -1
  166. package/src/components/rich-text/index.native.js +1 -1
  167. package/src/components/rich-text/input-event.js +1 -1
  168. package/src/components/rich-text/input-event.native.js +1 -1
  169. package/src/components/rich-text/native/index.native.js +18 -17
  170. package/src/components/use-on-block-drop/index.js +1 -1
  171. package/src/components/use-paste-styles/index.js +2 -2
  172. package/src/hooks/background.js +122 -21
  173. package/src/hooks/background.scss +45 -0
  174. package/src/hooks/block-style-variation.js +3 -4
  175. package/src/hooks/cross-origin-isolation.js +6 -6
  176. package/src/hooks/custom-css.js +6 -0
  177. package/src/hooks/fit-text.js +73 -83
  178. package/src/hooks/index.js +1 -1
  179. package/src/hooks/test/cross-origin-isolation.js +7 -3
  180. package/src/hooks/utils.js +4 -0
  181. package/src/private-apis.js +2 -2
  182. package/src/store/actions.js +9 -16
  183. package/src/store/private-selectors.js +2 -2
  184. package/src/store/reducer.js +144 -193
  185. package/src/store/selectors.js +33 -23
  186. package/src/store/test/private-selectors.js +107 -71
  187. package/src/store/test/reducer.js +593 -152
  188. package/src/store/test/registry-selectors.js +1 -1
  189. package/src/store/test/selectors.js +345 -262
  190. package/src/store/utils.js +1 -1
  191. package/src/style.scss +1 -0
  192. package/src/utils/pasting.js +1 -1
  193. package/build/autocompleters/link.cjs +0 -81
  194. package/build/autocompleters/link.cjs.map +0 -7
  195. package/build-module/autocompleters/link.mjs +0 -50
  196. package/build-module/autocompleters/link.mjs.map +0 -7
  197. package/src/autocompleters/link.js +0 -63
@@ -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 );
@@ -403,31 +403,6 @@ const withBlockTree =
403
403
  );
404
404
  break;
405
405
  }
406
- case 'SAVE_REUSABLE_BLOCK_SUCCESS': {
407
- const updatedBlockUids = [];
408
- newState.attributes.forEach( ( attributes, clientId ) => {
409
- if (
410
- newState.byClientId.get( clientId ).name ===
411
- 'core/block' &&
412
- attributes.ref === action.updatedId
413
- ) {
414
- updatedBlockUids.push( clientId );
415
- }
416
- } );
417
- newState.tree = new Map( newState.tree );
418
- updatedBlockUids.forEach( ( clientId ) => {
419
- newState.tree.set( clientId, {
420
- ...newState.byClientId.get( clientId ),
421
- attributes: newState.attributes.get( clientId ),
422
- innerBlocks: newState.tree.get( clientId ).innerBlocks,
423
- } );
424
- } );
425
- updateParentInnerBlocksInTree(
426
- newState,
427
- updatedBlockUids,
428
- false
429
- );
430
- }
431
406
  }
432
407
 
433
408
  return newState;
@@ -601,25 +576,22 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
601
576
  if ( action.type === 'RESET_BLOCKS' ) {
602
577
  /**
603
578
  * Preserve controlled inner block flags across RESET_BLOCKS.
604
- * Previously this was cleared to `{}`, which caused nested
605
- * controllers (e.g. post-content, patterns) to lose their
606
- * controlled status and unnecessarily re-clone blocks. Stale
607
- * flags are cleaned up naturally by unsetControlledBlocks()
608
- * when useBlockSync unmounts.
579
+ * If there are old and new blocks that:
580
+ * - have the same `clientId`
581
+ * - have the `controlledInnerBlocks` flag
582
+ * - don't have any own, uncontrolled children
583
+ * then we preserve the `controlledInnerBlocks` flag and the controlled sub-trees.
584
+ * Nested controllers (e.g., `post-content`, patterns) don't lose their
585
+ * controlled status and don't unnecessarily re-clone blocks.
609
586
  */
610
- const preservedControlledInnerBlocks =
611
- state?.controlledInnerBlocks ?? {};
587
+ const newState = reducer( undefined, {
588
+ type: 'INSERT_BLOCKS',
589
+ rootClientId: '',
590
+ blocks: action.blocks,
591
+ } );
612
592
 
613
- const newState = {
614
- ...state,
615
- byClientId: new Map(
616
- getFlattenedBlocksWithoutAttributes( action.blocks )
617
- ),
618
- attributes: new Map( getFlattenedBlockAttributes( action.blocks ) ),
619
- order: mapBlockOrder( action.blocks ),
620
- parents: new Map( mapBlockParents( action.blocks ) ),
621
- controlledInnerBlocks: preservedControlledInnerBlocks,
622
- };
593
+ const preservedControlledInnerBlocks =
594
+ state?.controlledInnerBlocks ?? new Set();
623
595
 
624
596
  // Preserve controlled inner blocks data from the old state.
625
597
  // The maps above are rebuilt solely from action.blocks, but
@@ -627,33 +599,29 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
627
599
  // present in action.blocks. Re-inject them so the state
628
600
  // remains consistent with the preserved flags.
629
601
  if ( state?.order ) {
630
- for ( const clientId of Object.keys(
631
- preservedControlledInnerBlocks
632
- ) ) {
633
- if ( ! preservedControlledInnerBlocks[ clientId ] ) {
634
- continue;
635
- }
602
+ for ( const clientId of preservedControlledInnerBlocks ) {
636
603
  // Only preserve if the parent block still exists.
637
604
  if ( ! newState.byClientId.has( clientId ) ) {
638
605
  continue;
639
606
  }
607
+ newState.controlledInnerBlocks.add( clientId );
640
608
  const oldOrder = state.order.get( clientId );
641
609
  if ( ! oldOrder?.length ) {
642
610
  continue;
643
611
  }
644
612
  newState.order.set( clientId, oldOrder );
645
613
  const preserveBlock = ( blockId, parentId ) => {
646
- const blockData = state.byClientId?.get( blockId );
614
+ const blockData = state.byClientId.get( blockId );
647
615
  if ( ! blockData ) {
648
616
  return;
649
617
  }
650
618
  newState.byClientId.set( blockId, blockData );
651
619
  newState.attributes.set(
652
620
  blockId,
653
- state.attributes?.get( blockId )
621
+ state.attributes.get( blockId )
654
622
  );
655
623
  newState.parents.set( blockId, parentId );
656
- const childOrder = state.order?.get( blockId ) || [];
624
+ const childOrder = state.order.get( blockId ) || [];
657
625
  newState.order.set( blockId, childOrder );
658
626
  childOrder.forEach( ( childId ) =>
659
627
  preserveBlock( childId, blockId )
@@ -663,42 +631,45 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
663
631
  }
664
632
  }
665
633
 
666
- newState.tree = new Map( state?.tree );
667
- updateBlockTreeForBlocks( newState, action.blocks );
668
-
669
634
  // Fix tree entries for controlled blocks. updateBlockTreeForBlocks
670
635
  // built tree entries using action.blocks' inner block structure
671
636
  // (entity-level IDs), but we need them to reference the preserved
672
637
  // cloned inner blocks instead. Mutating the existing object
673
638
  // preserves references held by ancestor tree entries.
674
- for ( const clientId of Object.keys(
675
- preservedControlledInnerBlocks
676
- ) ) {
677
- if ( ! preservedControlledInnerBlocks[ clientId ] ) {
678
- continue;
679
- }
680
- if ( ! newState.byClientId.has( clientId ) ) {
681
- continue;
682
- }
639
+ for ( const clientId of newState.controlledInnerBlocks ) {
683
640
  const controlledOrder = newState.order.get( clientId );
684
641
  if ( ! controlledOrder?.length ) {
685
642
  continue;
686
643
  }
687
644
  const innerBlocks = controlledOrder.map( ( id ) =>
688
- newState.tree.get( id )
645
+ state.tree.get( id )
689
646
  );
690
647
  const existingEntry = newState.tree.get( clientId );
691
648
  if ( existingEntry ) {
692
649
  existingEntry.innerBlocks = innerBlocks;
693
650
  }
694
651
  newState.tree.set( 'controlled||' + clientId, { innerBlocks } );
652
+ const preserveTreeEntry = ( blockId ) => {
653
+ const treeEntry = state.tree.get( blockId );
654
+ if ( ! treeEntry ) {
655
+ return;
656
+ }
657
+ newState.tree.set( blockId, treeEntry );
658
+ const childOrder = newState.order.get( blockId ) || [];
659
+ childOrder.forEach( preserveTreeEntry );
660
+ };
661
+ controlledOrder.forEach( preserveTreeEntry );
695
662
  }
696
663
 
697
- newState.tree.set( '', {
698
- innerBlocks: action.blocks.map( ( subBlock ) =>
699
- newState.tree.get( subBlock.clientId )
700
- ),
701
- } );
664
+ // Preserve block editing modes for blocks that are not removed.
665
+ const preservedBlockEditingModes =
666
+ state?.blockEditingModes ?? new Map();
667
+ for ( const [ clientId, mode ] of preservedBlockEditingModes ) {
668
+ if ( ! newState.tree.has( clientId ) ) {
669
+ continue;
670
+ }
671
+ newState.blockEditingModes.set( clientId, mode );
672
+ }
702
673
 
703
674
  return newState;
704
675
  }
@@ -729,12 +700,12 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => {
729
700
  // inner blocks from the block state because its inner blocks will not be
730
701
  // attached to the block in the action.
731
702
  const nestedControllers = {};
732
- if ( Object.keys( state.controlledInnerBlocks ).length ) {
703
+ if ( state.controlledInnerBlocks.size ) {
733
704
  const stack = [ ...action.blocks ];
734
705
  while ( stack.length ) {
735
706
  const { innerBlocks, ...block } = stack.shift();
736
707
  stack.push( ...innerBlocks );
737
- if ( !! state.controlledInnerBlocks[ block.clientId ] ) {
708
+ if ( state.controlledInnerBlocks.has( block.clientId ) ) {
738
709
  nestedControllers[ block.clientId ] = true;
739
710
  }
740
711
  }
@@ -780,40 +751,6 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => {
780
751
  return stateAfterInsert;
781
752
  };
782
753
 
783
- /**
784
- * Higher-order reducer which targets the combined blocks reducer and handles
785
- * the `SAVE_REUSABLE_BLOCK_SUCCESS` action. This action can't be handled by
786
- * regular reducers and needs a higher-order reducer since it needs access to
787
- * both `byClientId` and `attributes` simultaneously.
788
- *
789
- * @param {Function} reducer Original reducer function.
790
- *
791
- * @return {Function} Enhanced reducer function.
792
- */
793
- const withSaveReusableBlock = ( reducer ) => ( state, action ) => {
794
- if ( state && action.type === 'SAVE_REUSABLE_BLOCK_SUCCESS' ) {
795
- const { id, updatedId } = action;
796
-
797
- // If a temporary reusable block is saved, we swap the temporary id with the final one.
798
- if ( id === updatedId ) {
799
- return state;
800
- }
801
-
802
- state = { ...state };
803
- state.attributes = new Map( state.attributes );
804
- state.attributes.forEach( ( attributes, clientId ) => {
805
- const { name } = state.byClientId.get( clientId );
806
- if ( name === 'core/block' && attributes.ref === id ) {
807
- state.attributes.set( clientId, {
808
- ...attributes,
809
- ref: updatedId,
810
- } );
811
- }
812
- } );
813
- }
814
-
815
- return reducer( state, action );
816
- };
817
754
  /**
818
755
  * Higher-order reducer which removes blocks from state when switching parent block controlled state.
819
756
  *
@@ -854,7 +791,6 @@ const withResetControlledBlocks = ( reducer ) => ( state, action ) => {
854
791
  */
855
792
  export const blocks = pipe(
856
793
  combineReducers,
857
- withSaveReusableBlock, // Needs to be before withBlockCache.
858
794
  withBlockTree, // Needs to be before withInnerBlocksRemoveCascade.
859
795
  withInnerBlocksRemoveCascade,
860
796
  withReplaceInnerBlocks, // Needs to be after withInnerBlocksRemoveCascade.
@@ -1279,14 +1215,41 @@ export const blocks = pipe(
1279
1215
  },
1280
1216
 
1281
1217
  controlledInnerBlocks(
1282
- state = {},
1218
+ state = new Set(),
1283
1219
  { type, clientId, hasControlledInnerBlocks }
1284
1220
  ) {
1285
1221
  if ( type === 'SET_HAS_CONTROLLED_INNER_BLOCKS' ) {
1286
- return {
1287
- ...state,
1288
- [ clientId ]: hasControlledInnerBlocks,
1289
- };
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;
1234
+ }
1235
+ return state;
1236
+ },
1237
+
1238
+ blockEditingModes( state = new Map(), action ) {
1239
+ switch ( action.type ) {
1240
+ case 'SET_BLOCK_EDITING_MODE':
1241
+ if ( state.get( action.clientId ) === action.mode ) {
1242
+ return state;
1243
+ }
1244
+ return new Map( state ).set( action.clientId, action.mode );
1245
+ case 'UNSET_BLOCK_EDITING_MODE': {
1246
+ if ( ! state.has( action.clientId ) ) {
1247
+ return state;
1248
+ }
1249
+ const newState = new Map( state );
1250
+ newState.delete( action.clientId );
1251
+ return newState;
1252
+ }
1290
1253
  }
1291
1254
  return state;
1292
1255
  },
@@ -1866,12 +1829,12 @@ export function preferences( state = PREFERENCES_DEFAULTS, action ) {
1866
1829
  * Reducer returning an object where each key is a block client ID, its value
1867
1830
  * representing the settings for its nested blocks.
1868
1831
  *
1869
- * @param {Object} state Current state.
1832
+ * @param {Map} state Current state.
1870
1833
  * @param {Object} action Dispatched action.
1871
1834
  *
1872
1835
  * @return {Object} Updated state.
1873
1836
  */
1874
- export const blockListSettings = ( state = {}, action ) => {
1837
+ export const blockListSettings = ( state = new Map(), action ) => {
1875
1838
  switch ( action.type ) {
1876
1839
  case 'REPLACE_BLOCKS': {
1877
1840
  // Collect all clientIds from replacement blocks. If a clientId
@@ -1886,53 +1849,48 @@ export const blockListSettings = ( state = {}, action ) => {
1886
1849
  replacementIds.add( block.clientId );
1887
1850
  stack.push( ...block.innerBlocks );
1888
1851
  }
1889
- return Object.fromEntries(
1890
- Object.entries( state ).filter(
1891
- ( [ id ] ) =>
1892
- ! action.clientIds.includes( id ) ||
1893
- replacementIds.has( id )
1894
- )
1895
- );
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;
1896
1859
  }
1897
1860
  case 'REMOVE_BLOCKS': {
1898
- return Object.fromEntries(
1899
- Object.entries( state ).filter(
1900
- ( [ id ] ) => ! action.clientIds.includes( id )
1901
- )
1902
- );
1861
+ const newState = new Map( state );
1862
+ for ( const clientId of action.clientIds ) {
1863
+ newState.delete( clientId );
1864
+ }
1865
+ return newState;
1903
1866
  }
1904
1867
  case 'UPDATE_BLOCK_LIST_SETTINGS': {
1905
1868
  const updates =
1906
1869
  typeof action.clientId === 'string'
1907
- ? { [ action.clientId ]: action.settings }
1908
- : action.clientId;
1909
-
1910
- // Remove settings that are the same as the current state.
1911
- for ( const clientId in updates ) {
1912
- if ( ! updates[ clientId ] ) {
1913
- if ( ! state[ clientId ] ) {
1914
- delete updates[ clientId ];
1915
- }
1916
- } else if (
1917
- fastDeepEqual( state[ clientId ], updates[ clientId ] )
1918
- ) {
1919
- delete updates[ clientId ];
1920
- }
1921
- }
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
+ );
1922
1879
 
1923
- if ( Object.keys( updates ).length === 0 ) {
1880
+ if ( ! relevantUpdates.length ) {
1924
1881
  return state;
1925
1882
  }
1926
1883
 
1927
- const merged = { ...state, ...updates };
1928
-
1929
- for ( const clientId in updates ) {
1930
- if ( ! updates[ clientId ] ) {
1931
- 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 );
1932
1890
  }
1933
1891
  }
1934
1892
 
1935
- return merged;
1893
+ return newState;
1936
1894
  }
1937
1895
  }
1938
1896
  return state;
@@ -2104,38 +2062,33 @@ export function editedContentOnlySection( state, action ) {
2104
2062
  if ( action.type === 'EDIT_CONTENT_ONLY_SECTION' ) {
2105
2063
  return action.clientId;
2106
2064
  }
2107
- return state;
2108
- }
2109
2065
 
2110
- /**
2111
- * Reducer returning a map of block client IDs to block editing modes.
2112
- *
2113
- * @param {Map} state Current state.
2114
- * @param {Object} action Dispatched action.
2115
- *
2116
- * @return {Map} Updated state.
2117
- */
2118
- export function blockEditingModes( state = new Map(), action ) {
2066
+ // Early return if there's no section being edited.
2067
+ if ( ! state ) {
2068
+ return state;
2069
+ }
2070
+
2119
2071
  switch ( action.type ) {
2120
- case 'SET_BLOCK_EDITING_MODE':
2121
- if ( state.get( action.clientId ) === action.mode ) {
2122
- return state;
2123
- }
2124
- return new Map( state ).set( action.clientId, action.mode );
2125
- case 'UNSET_BLOCK_EDITING_MODE': {
2126
- if ( ! state.has( action.clientId ) ) {
2127
- return state;
2072
+ case 'REMOVE_BLOCKS':
2073
+ case 'REPLACE_BLOCKS':
2074
+ // Clear if the edited section is directly among the removed/replaced blocks.
2075
+ // Note: this doesn't catch the case where a parent of the edited section
2076
+ // is removed, since action.clientIds only contains the top-level IDs.
2077
+ // That edge case is handled by the StopEditingContentOnlySectionOnOutsideSelect
2078
+ // component in block-list/index.js.
2079
+ if ( action.clientIds.includes( state ) ) {
2080
+ return undefined;
2081
+ }
2082
+ break;
2083
+ case 'RESET_BLOCKS':
2084
+ // When all blocks are reset (e.g. navigating to a different post),
2085
+ // check whether the edited section still exists in the new block tree.
2086
+ if ( ! getFlattenedClientIds( action.blocks )[ state ] ) {
2087
+ return undefined;
2128
2088
  }
2129
- const newState = new Map( state );
2130
- newState.delete( action.clientId );
2131
- return newState;
2132
- }
2133
- case 'RESET_BLOCKS': {
2134
- return state.has( '' )
2135
- ? new Map().set( '', state.get( '' ) )
2136
- : state;
2137
- }
2089
+ break;
2138
2090
  }
2091
+
2139
2092
  return state;
2140
2093
  }
2141
2094
 
@@ -2375,7 +2328,6 @@ const combinedReducers = combineReducers( {
2375
2328
  editedContentOnlySection,
2376
2329
  blockVisibility,
2377
2330
  viewportModalClientIds,
2378
- blockEditingModes,
2379
2331
  styleOverrides,
2380
2332
  removalPromptData,
2381
2333
  blockRemovalRules,
@@ -2413,7 +2365,7 @@ function getBlockTreeBlock( state, clientId ) {
2413
2365
  };
2414
2366
  }
2415
2367
 
2416
- if ( ! state.blocks.controlledInnerBlocks[ clientId ] ) {
2368
+ if ( ! state.blocks.controlledInnerBlocks.has( clientId ) ) {
2417
2369
  return state.blocks.tree.get( clientId );
2418
2370
  }
2419
2371
 
@@ -2506,13 +2458,13 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2506
2458
  // so the default block editing mode is set to disabled.
2507
2459
  const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ];
2508
2460
  const sectionClientIds = state.blocks.order.get( sectionRootClientId );
2509
- const hasDisabledBlocks = Array.from( state.blockEditingModes ).some(
2461
+ const hasDisabledBlocks = Array.from( state.blocks.blockEditingModes ).some(
2510
2462
  ( [ , mode ] ) => mode === 'disabled'
2511
2463
  );
2512
2464
  const templatePartClientIds = [];
2513
2465
  const syncedPatternClientIds = [];
2514
2466
 
2515
- Object.keys( state.blocks.controlledInnerBlocks ).forEach( ( clientId ) => {
2467
+ state.blocks.controlledInnerBlocks.forEach( ( clientId ) => {
2516
2468
  const block = state.blocks.byClientId?.get( clientId );
2517
2469
 
2518
2470
  if ( block?.name === 'core/template-part' ) {
@@ -2523,11 +2475,10 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2523
2475
  syncedPatternClientIds.push( clientId );
2524
2476
  }
2525
2477
  } );
2526
- const contentOnlyTemplateLockedClientIds = Object.keys(
2478
+ const contentOnlyTemplateLockedClientIds = Array.from(
2527
2479
  state.blockListSettings
2528
- ).filter(
2529
- ( clientId ) =>
2530
- state.blockListSettings[ clientId ]?.templateLock === 'contentOnly'
2480
+ ).flatMap( ( [ clientId, listSettings ] ) =>
2481
+ listSettings?.templateLock === 'contentOnly' ? [ clientId ] : []
2531
2482
  );
2532
2483
 
2533
2484
  // When in an isolated editing context (e.g., editing a template part or pattern directly),
@@ -2582,7 +2533,7 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2582
2533
 
2583
2534
  // If the block already has an explicit block editing mode set,
2584
2535
  // don't override it.
2585
- if ( state.blockEditingModes.has( clientId ) ) {
2536
+ if ( state.blocks.blockEditingModes.has( clientId ) ) {
2586
2537
  return;
2587
2538
  }
2588
2539
 
@@ -2593,12 +2544,12 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2593
2544
  let ancestorBlockEditingMode;
2594
2545
  let parent = state.blocks.parents.get( clientId );
2595
2546
  while ( parent !== undefined ) {
2596
- if ( state.blockEditingModes.has( parent ) ) {
2547
+ if ( state.blocks.blockEditingModes.has( parent ) ) {
2597
2548
  // Checking the explicit block editing mode will be slower,
2598
2549
  // as the block editing mode is more likely to be set on a
2599
2550
  // distant ancestor.
2600
2551
  ancestorBlockEditingMode =
2601
- state.blockEditingModes.get( parent );
2552
+ state.blocks.blockEditingModes.get( parent );
2602
2553
  }
2603
2554
  if ( ancestorBlockEditingMode ) {
2604
2555
  break;
@@ -2933,15 +2884,15 @@ export function withDerivedBlockEditingModes( reducer ) {
2933
2884
 
2934
2885
  for ( const clientId in updates ) {
2935
2886
  const isNewContentOnlyBlock =
2936
- state.blockListSettings[ clientId ]?.templateLock !==
2937
- 'contentOnly' &&
2938
- nextState.blockListSettings[ clientId ]
2887
+ state.blockListSettings.get( clientId )
2888
+ ?.templateLock !== 'contentOnly' &&
2889
+ nextState.blockListSettings.get( clientId )
2939
2890
  ?.templateLock === 'contentOnly';
2940
2891
 
2941
2892
  const wasContentOnlyBlock =
2942
- state.blockListSettings[ clientId ]?.templateLock ===
2943
- 'contentOnly' &&
2944
- nextState.blockListSettings[ clientId ]
2893
+ state.blockListSettings.get( clientId )
2894
+ ?.templateLock === 'contentOnly' &&
2895
+ nextState.blockListSettings.get( clientId )
2945
2896
  ?.templateLock !== 'contentOnly';
2946
2897
 
2947
2898
  if ( isNewContentOnlyBlock ) {
@@ -1747,14 +1747,23 @@ const canInsertBlockTypeUnmemoized = (
1747
1747
  return false;
1748
1748
  }
1749
1749
 
1750
- // In content only mode, check if this container allows insertion.
1751
- // We need the `isParentSectionBlock` check because section blocks
1752
- // (synced patterns, contentOnly groups) have a `getBlockEditingMode`
1753
- // of 'default', not 'contentOnly' the 'contentOnly' mode is only
1754
- // set on their *children*.
1750
+ /*
1751
+ * In content only mode, check if this container allows insertion.
1752
+ * We need the `isParentSectionBlock` check because section blocks
1753
+ * (synced patterns, contentOnly groups) have a `getBlockEditingMode`
1754
+ * of 'default', not 'contentOnly' — the 'contentOnly' mode is only
1755
+ * set on their *children*.
1756
+ *
1757
+ * Also include `disabled` alongside `contentOnly`: structural inner blocks
1758
+ * (e.g. Column) inside a content-only section use `disabled` mode, and they
1759
+ * need the same default-block sibling rules so insertion stays aligned with
1760
+ * `canRemoveBlock`.
1761
+ */
1755
1762
  if (
1756
1763
  isWithinSection &&
1757
- ( isParentSectionBlock || blockEditingMode === 'contentOnly' ) &&
1764
+ ( isParentSectionBlock ||
1765
+ blockEditingMode === 'contentOnly' ||
1766
+ blockEditingMode === 'disabled' ) &&
1758
1767
  ! isContainerInsertableToInContentOnlyMode(
1759
1768
  state,
1760
1769
  blockName,
@@ -1762,8 +1771,11 @@ const canInsertBlockTypeUnmemoized = (
1762
1771
  )
1763
1772
  ) {
1764
1773
  const defaultBlockName = getDefaultBlockName();
1765
- // Allow inserting the default block anywhere that another default block already exists
1766
- // when in contentOnly mode.
1774
+ /*
1775
+ * Allow inserting the default block anywhere that another default block already exists
1776
+ * when in contentOnly mode. The same sibling rule applies when the parent is `disabled`
1777
+ * within a content-only section (see the condition above).
1778
+ */
1767
1779
  if ( blockName === defaultBlockName ) {
1768
1780
  const existingBlocks = getBlockOrder( state, rootClientId );
1769
1781
  const hasDefaultBlock = existingBlocks.some(
@@ -1980,9 +1992,9 @@ export function canRemoveBlock( state, clientId ) {
1980
1992
  if ( defaultBlocks.length > 1 ) {
1981
1993
  return true;
1982
1994
  }
1983
- } else {
1984
1995
  return false;
1985
1996
  }
1997
+ return false;
1986
1998
  }
1987
1999
 
1988
2000
  return rootBlockEditingMode !== 'disabled';
@@ -2624,7 +2636,7 @@ export function getDirectInsertBlock( state, rootClientId = null ) {
2624
2636
  return;
2625
2637
  }
2626
2638
  const { defaultBlock, directInsert } =
2627
- state.blockListSettings[ rootClientId ] ?? {};
2639
+ state.blockListSettings.get( rootClientId ) ?? {};
2628
2640
  if ( ! defaultBlock || ! directInsert ) {
2629
2641
  return;
2630
2642
  }
@@ -2857,7 +2869,7 @@ export const __experimentalGetPatternTransformItems = createRegistrySelector(
2857
2869
  * @return {?Object} Block settings of the block if set.
2858
2870
  */
2859
2871
  export function getBlockListSettings( state, clientId ) {
2860
- return state.blockListSettings[ clientId ];
2872
+ return state.blockListSettings.get( clientId );
2861
2873
  }
2862
2874
 
2863
2875
  /**
@@ -2895,16 +2907,14 @@ export function isLastBlockChangePersistent( state ) {
2895
2907
  */
2896
2908
  export const __experimentalGetBlockListSettingsForBlocks = createSelector(
2897
2909
  ( state, clientIds = [] ) => {
2898
- return clientIds.reduce( ( blockListSettingsForBlocks, clientId ) => {
2899
- if ( ! state.blockListSettings[ clientId ] ) {
2900
- return blockListSettingsForBlocks;
2910
+ const blockListSettingsForBlocks = {};
2911
+ for ( const clientId of clientIds ) {
2912
+ const settings = getBlockListSettings( state, clientId );
2913
+ if ( settings ) {
2914
+ blockListSettingsForBlocks[ clientId ] = settings;
2901
2915
  }
2902
-
2903
- return {
2904
- ...blockListSettingsForBlocks,
2905
- [ clientId ]: state.blockListSettings[ clientId ],
2906
- };
2907
- }, {} );
2916
+ }
2917
+ return blockListSettingsForBlocks;
2908
2918
  },
2909
2919
  ( state ) => [ state.blockListSettings ]
2910
2920
  );
@@ -3021,7 +3031,7 @@ export function isBlockHighlighted( state, clientId ) {
3021
3031
  * @return {boolean} True if the block has controlled inner blocks.
3022
3032
  */
3023
3033
  export function areInnerBlocksControlled( state, clientId ) {
3024
- return !! state.blocks.controlledInnerBlocks[ clientId ];
3034
+ return state.blocks.controlledInnerBlocks.has( clientId );
3025
3035
  }
3026
3036
 
3027
3037
  /**
@@ -3242,8 +3252,8 @@ export function getBlockEditingMode( state, clientId = '' ) {
3242
3252
  }
3243
3253
 
3244
3254
  // In normal mode, consider that an explicitly set editing mode takes over.
3245
- if ( state.blockEditingModes.has( clientId ) ) {
3246
- return state.blockEditingModes.get( clientId );
3255
+ if ( state.blocks.blockEditingModes.has( clientId ) ) {
3256
+ return state.blocks.blockEditingModes.get( clientId );
3247
3257
  }
3248
3258
 
3249
3259
  return 'default';