diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index b449ecfaf31dd8..5975113bb28748 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -213,7 +213,7 @@ function updateParentInnerBlocksInTree( ? clientId : state.parents.get( clientId ); do { - if ( state.controlledInnerBlocks[ current ] ) { + if ( state.controlledInnerBlocks.has( current ) ) { // Should stop on controlled blocks. // If we reach a controlled parent, break out of the loop. controlledParents.add( current ); @@ -591,7 +591,7 @@ const withBlockReset = ( reducer ) => ( state, action ) => { } ); const preservedControlledInnerBlocks = - state?.controlledInnerBlocks ?? {}; + state?.controlledInnerBlocks ?? new Set(); // Preserve controlled inner blocks data from the old state. // The maps above are rebuilt solely from action.blocks, but @@ -599,17 +599,12 @@ const withBlockReset = ( reducer ) => ( state, action ) => { // present in action.blocks. Re-inject them so the state // remains consistent with the preserved flags. if ( state?.order ) { - for ( const clientId of Object.keys( - preservedControlledInnerBlocks - ) ) { - if ( ! preservedControlledInnerBlocks[ clientId ] ) { - continue; - } + for ( const clientId of preservedControlledInnerBlocks ) { // Only preserve if the parent block still exists. if ( ! newState.byClientId.has( clientId ) ) { continue; } - newState.controlledInnerBlocks[ clientId ] = true; + newState.controlledInnerBlocks.add( clientId ); const oldOrder = state.order.get( clientId ); if ( ! oldOrder?.length ) { continue; @@ -641,9 +636,7 @@ const withBlockReset = ( reducer ) => ( state, action ) => { // (entity-level IDs), but we need them to reference the preserved // cloned inner blocks instead. Mutating the existing object // preserves references held by ancestor tree entries. - for ( const clientId of Object.keys( - newState.controlledInnerBlocks - ) ) { + for ( const clientId of newState.controlledInnerBlocks ) { const controlledOrder = newState.order.get( clientId ); if ( ! controlledOrder?.length ) { continue; @@ -707,12 +700,12 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => { // inner blocks from the block state because its inner blocks will not be // attached to the block in the action. const nestedControllers = {}; - if ( Object.keys( state.controlledInnerBlocks ).length ) { + if ( state.controlledInnerBlocks.size ) { const stack = [ ...action.blocks ]; while ( stack.length ) { const { innerBlocks, ...block } = stack.shift(); stack.push( ...innerBlocks ); - if ( !! state.controlledInnerBlocks[ block.clientId ] ) { + if ( state.controlledInnerBlocks.has( block.clientId ) ) { nestedControllers[ block.clientId ] = true; } } @@ -1222,14 +1215,22 @@ export const blocks = pipe( }, controlledInnerBlocks( - state = {}, + state = new Set(), { type, clientId, hasControlledInnerBlocks } ) { if ( type === 'SET_HAS_CONTROLLED_INNER_BLOCKS' ) { - return { - ...state, - [ clientId ]: hasControlledInnerBlocks, - }; + if ( hasControlledInnerBlocks ) { + if ( state.has( clientId ) ) { + return state; + } + return new Set( state ).add( clientId ); + } + if ( ! state.has( clientId ) ) { + return state; + } + const newState = new Set( state ); + newState.delete( clientId ); + return newState; } return state; }, @@ -2369,7 +2370,7 @@ function getBlockTreeBlock( state, clientId ) { }; } - if ( ! state.blocks.controlledInnerBlocks[ clientId ] ) { + if ( ! state.blocks.controlledInnerBlocks.has( clientId ) ) { return state.blocks.tree.get( clientId ); } @@ -2468,7 +2469,7 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) { const templatePartClientIds = []; const syncedPatternClientIds = []; - Object.keys( state.blocks.controlledInnerBlocks ).forEach( ( clientId ) => { + state.blocks.controlledInnerBlocks.forEach( ( clientId ) => { const block = state.blocks.byClientId?.get( clientId ); if ( block?.name === 'core/template-part' ) { diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 875da0d3c3d03b..1cb6970c39cde0 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -3033,7 +3033,7 @@ export function isBlockHighlighted( state, clientId ) { * @return {boolean} True if the block has controlled inner blocks. */ export function areInnerBlocksControlled( state, clientId ) { - return !! state.blocks.controlledInnerBlocks[ clientId ]; + return state.blocks.controlledInnerBlocks.has( clientId ); } /** diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 98da5bd5347729..24b794e156c9bc 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -285,7 +285,7 @@ describe( 'state', () => { 'chicken-child': {}, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); @@ -344,7 +344,7 @@ describe( 'state', () => { chicken: '', } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); expect( state.tree.get( 'chicken' ) ).not.toBe( @@ -387,7 +387,7 @@ describe( 'state', () => { chicken: {}, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); @@ -446,7 +446,7 @@ describe( 'state', () => { chicken: '', } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); expect( state.tree.get( 'chicken' ) ).not.toBe( @@ -518,7 +518,7 @@ describe( 'state', () => { } ) ), tree: new Map(), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); @@ -613,7 +613,7 @@ describe( 'state', () => { [ newChildBlockId3 ]: 'chicken', } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); @@ -689,7 +689,7 @@ describe( 'state', () => { chicken: {}, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); @@ -742,7 +742,7 @@ describe( 'state', () => { [ newChildBlockId ]: 'chicken', } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); @@ -764,7 +764,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, tree: new Map(), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), } ); } ); @@ -2259,7 +2259,9 @@ describe( 'state', () => { hasControlledInnerBlocks: true, } ); - expect( state.controlledInnerBlocks.chicken ).toBe( true ); + expect( state.controlledInnerBlocks.has( 'chicken' ) ).toBe( + true + ); // The previous content of the block should be removed expect( state.byClientId.child ).toBeUndefined(); expect( state.tree.get( 'child' ) ).toBeUndefined(); @@ -2296,9 +2298,7 @@ describe( 'state', () => { 'paragraph-id': [], } ) ), - controlledInnerBlocks: { - 'reusable-id': true, - }, + controlledInnerBlocks: new Set( [ 'reusable-id' ] ), parents: new Map( Object.entries( { 'group-id': '', @@ -2421,9 +2421,9 @@ describe( 'state', () => { clientId: 'chicken', hasControlledInnerBlocks: true, } ); - expect( withControlled.controlledInnerBlocks.chicken ).toBe( - true - ); + expect( + withControlled.controlledInnerBlocks.has( 'chicken' ) + ).toBe( true ); const state = blocks( withControlled, { type: 'RESET_BLOCKS', @@ -2437,7 +2437,9 @@ describe( 'state', () => { ], } ); - expect( state.controlledInnerBlocks.chicken ).toBe( true ); + expect( state.controlledInnerBlocks.has( 'chicken' ) ).toBe( + true + ); } ); it( 'should preserve controlledInnerBlocks blocks across RESET_BLOCKS', () => { @@ -2499,7 +2501,9 @@ describe( 'state', () => { ], } ); - expect( state.controlledInnerBlocks.chicken ).toBe( true ); + expect( state.controlledInnerBlocks.has( 'chicken' ) ).toBe( + true + ); expect( getBlocks( { blocks: state }, 'chicken' ).map( ( b ) => b.clientId @@ -2656,7 +2660,9 @@ describe( 'state', () => { hasControlledInnerBlocks: true, } ); - expect( state.controlledInnerBlocks.chicken ).toBe( true ); + expect( state.controlledInnerBlocks.has( 'chicken' ) ).toBe( + true + ); // The order and byClientId Maps should be the same // reference because the block has no inner blocks to // remove, so REPLACE_INNER_BLOCKS should be skipped. diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 35196c16f5e030..07ca2f1e466dad 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -321,7 +321,7 @@ describe( 'selectors', () => { }, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; @@ -347,7 +347,7 @@ describe( 'selectors', () => { }, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; @@ -404,7 +404,7 @@ describe( 'selectors', () => { 23: {}, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; @@ -452,7 +452,7 @@ describe( 'selectors', () => { 'client-id-03': {}, 'client-id-04': {}, }, - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; @@ -509,7 +509,7 @@ describe( 'selectors', () => { 'client-id-04': {}, 'client-id-05': {}, }, - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; it( 'should return parent blocks', () => { @@ -674,7 +674,7 @@ describe( 'selectors', () => { 'uuid-30': 'uuid-28', } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; @@ -1246,7 +1246,7 @@ describe( 'selectors', () => { }, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, selection: { selectionStart: { clientId: '23' }, @@ -3894,7 +3894,7 @@ describe( 'selectors', () => { } ) ), cache: {}, - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), blockEditingModes: new Map(), }, settings: {}, @@ -3944,7 +3944,7 @@ describe( 'selectors', () => { }, } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), parents: new Map(), blockEditingModes: new Map(), }, @@ -4369,7 +4369,7 @@ describe( 'selectors', () => { 'client-id-03': [ 'client-id-04', 'client-id-05' ], } ) ), - controlledInnerBlocks: {}, + controlledInnerBlocks: new Set(), }, }; it( 'Should return first active matching block (including self) when single block selected', () => {