diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index fad2f5cfb1483..81da0457ffc5c 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -19,6 +19,7 @@ import { range, GridRect, getGridInfo } from './utils'; import { store as blockEditorStore } from '../../store'; import { useGetNumberOfBlocksBeforeCell } from './use-get-number-of-blocks-before-cell'; import ButtonBlockAppender from '../button-block-appender'; +import { unlock } from '../../lock-unlock'; export function GridVisualizer( { clientId, contentRef, parentLayout } ) { const isDistractionFree = useSelect( @@ -118,19 +119,25 @@ const GridVisualizerGrid = forwardRef( function ManualGridVisualizer( { gridClientId, gridInfo } ) { const [ highlightedRect, setHighlightedRect ] = useState( null ); - const gridItems = useSelect( - ( select ) => select( blockEditorStore ).getBlocks( gridClientId ), + const gridItemStyles = useSelect( + ( select ) => { + const { getBlockOrder, getBlockStyles } = unlock( + select( blockEditorStore ) + ); + const blockOrder = getBlockOrder( gridClientId ); + return getBlockStyles( blockOrder ); + }, [ gridClientId ] ); const occupiedRects = useMemo( () => { const rects = []; - for ( const block of gridItems ) { + for ( const style of Object.values( gridItemStyles ) ) { const { columnStart, rowStart, columnSpan = 1, rowSpan = 1, - } = block.attributes.style?.layout || {}; + } = style?.layout ?? {}; if ( ! columnStart || ! rowStart ) { continue; } @@ -144,7 +151,7 @@ function ManualGridVisualizer( { gridClientId, gridInfo } ) { ); } return rects; - }, [ gridItems ] ); + }, [ gridItemStyles ] ); return range( 1, gridInfo.numRows ).map( ( row ) => range( 1, gridInfo.numColumns ).map( ( column ) => { diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index dcd315a0ae280..7f0a4c58ab674 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -515,3 +515,24 @@ export function getTemporarilyEditingFocusModeToRevert( state ) { export function getInserterSearchInputRef( state ) { return state.inserterSearchInputRef; } + +/** + * Returns the style attributes of multiple blocks. + * + * @param {Object} state Global application state. + * @param {string[]} clientIds An array of block client IDs. + * + * @return {Object} An object where keys are client IDs and values are the corresponding block styles or undefined. + */ +export const getBlockStyles = createSelector( + ( state, clientIds ) => + clientIds.reduce( ( styles, clientId ) => { + styles[ clientId ] = state.blocks.attributes.get( clientId )?.style; + return styles; + }, {} ), + ( state, clientIds ) => [ + ...clientIds.map( + ( clientId ) => state.blocks.attributes.get( clientId )?.style + ), + ] +); diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 185da1ffb9804..45432b750bb9e 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -9,6 +9,7 @@ import { getEnabledBlockParents, getExpandedBlock, isDragging, + getBlockStyles, } from '../private-selectors'; import { getBlockEditingMode } from '../selectors'; @@ -509,4 +510,92 @@ describe( 'private selectors', () => { ); } ); } ); + + describe( 'getBlockStyles', () => { + it( 'should return an empty object when no client IDs are provided', () => { + const state = { + blocks: { + attributes: new Map(), + }, + }; + const result = getBlockStyles( state, [] ); + expect( result ).toEqual( {} ); + } ); + + it( 'should return styles for a single block', () => { + const state = { + blocks: { + attributes: new Map( [ + [ 'block-1', { style: { color: 'red' } } ], + ] ), + }, + }; + const result = getBlockStyles( state, [ 'block-1' ] ); + expect( result ).toEqual( { + 'block-1': { color: 'red' }, + } ); + } ); + + it( 'should return styles for multiple blocks', () => { + const state = { + blocks: { + attributes: new Map( [ + [ 'block-1', { style: { color: 'red' } } ], + [ 'block-2', { style: { fontSize: '16px' } } ], + [ 'block-3', { style: { margin: '10px' } } ], + ] ), + }, + }; + const result = getBlockStyles( state, [ + 'block-1', + 'block-2', + 'block-3', + ] ); + expect( result ).toEqual( { + 'block-1': { color: 'red' }, + 'block-2': { fontSize: '16px' }, + 'block-3': { margin: '10px' }, + } ); + } ); + + it( 'should return undefined for blocks without styles', () => { + const state = { + blocks: { + attributes: new Map( [ + [ 'block-1', { style: { color: 'red' } } ], + [ 'block-2', {} ], + [ 'block-3', { style: { margin: '10px' } } ], + ] ), + }, + }; + const result = getBlockStyles( state, [ + 'block-1', + 'block-2', + 'block-3', + ] ); + expect( result ).toEqual( { + 'block-1': { color: 'red' }, + 'block-2': undefined, + 'block-3': { margin: '10px' }, + } ); + } ); + + it( 'should return undefined for non-existent blocks', () => { + const state = { + blocks: { + attributes: new Map( [ + [ 'block-1', { style: { color: 'red' } } ], + ] ), + }, + }; + const result = getBlockStyles( state, [ + 'block-1', + 'non-existent-block', + ] ); + expect( result ).toEqual( { + 'block-1': { color: 'red' }, + 'non-existent-block': undefined, + } ); + } ); + } ); } );