diff --git a/packages/block-editor/src/components/grid/use-grid-layout-sync.js b/packages/block-editor/src/components/grid/use-grid-layout-sync.js index a5d6aa4d3fbdd7..ed368714d63d34 100644 --- a/packages/block-editor/src/components/grid/use-grid-layout-sync.js +++ b/packages/block-editor/src/components/grid/use-grid-layout-sync.js @@ -2,7 +2,8 @@ * WordPress dependencies */ import { useDispatch, useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useMemo } from '@wordpress/element'; +import { usePrevious } from '@wordpress/compose'; /** * Internal dependencies @@ -11,13 +12,15 @@ import { store as blockEditorStore } from '../../store'; import { GridRect } from './utils'; export function useGridLayoutSync( { clientId: gridClientId } ) { - const { gridLayout, blockOrder } = useSelect( + const { gridLayout, blockOrder, selectedBlockLayout } = useSelect( ( select ) => { const { getBlockAttributes, getBlockOrder } = select( blockEditorStore ); + const selectedBlock = select( blockEditorStore ).getSelectedBlock(); return { gridLayout: getBlockAttributes( gridClientId ).layout ?? {}, blockOrder: getBlockOrder( gridClientId ), + selectedBlockLayout: selectedBlock?.attributes.style?.layout, }; }, [ gridClientId ] @@ -27,27 +30,32 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { const { updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); + const selectedBlockRect = useMemo( + () => + selectedBlockLayout ? new GridRect( selectedBlockLayout ) : null, + [ selectedBlockLayout ] + ); + + const previouslySelectedBlockRect = usePrevious( selectedBlockRect ); + useEffect( () => { const updates = {}; - const { columnCount, rowCount, isManualPlacement } = gridLayout; - - if ( isManualPlacement ) { - const rects = []; + if ( gridLayout.isManualPlacement ) { + const occupiedRects = []; // Respect the position of blocks that already have a columnStart and rowStart value. for ( const clientId of blockOrder ) { - const attributes = getBlockAttributes( clientId ); const { columnStart, rowStart, columnSpan = 1, rowSpan = 1, - } = attributes.style?.layout || {}; + } = getBlockAttributes( clientId ).style?.layout ?? {}; if ( ! columnStart || ! rowStart ) { continue; } - rects.push( + occupiedRects.push( new GridRect( { columnStart, rowStart, @@ -65,17 +73,19 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { rowStart, columnSpan = 1, rowSpan = 1, - } = attributes.style?.layout || {}; + } = attributes.style?.layout ?? {}; if ( columnStart && rowStart ) { continue; } - const [ newColumnStart, newRowStart ] = getFirstEmptyCell( - rects, - columnCount, + const [ newColumnStart, newRowStart ] = placeBlock( + occupiedRects, + gridLayout.columnCount, columnSpan, - rowSpan + rowSpan, + previouslySelectedBlockRect?.columnEnd, + previouslySelectedBlockRect?.rowEnd ); - rects.push( + occupiedRects.push( new GridRect( { columnStart: newColumnStart, rowStart: newRowStart, @@ -96,8 +106,13 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { } // Ensure there's enough rows to fit all blocks. - const bottomMostRow = Math.max( ...rects.map( ( r ) => r.rowEnd ) ); - if ( ! rowCount || rowCount < bottomMostRow ) { + const bottomMostRow = Math.max( + ...occupiedRects.map( ( r ) => r.rowEnd ) + ); + if ( + ! gridLayout.rowCount || + gridLayout.rowCount < bottomMostRow + ) { updates[ gridClientId ] = { layout: { ...gridLayout, @@ -110,7 +125,7 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId ); const { columnStart, rowStart, ...layout } = - attributes.style?.layout || {}; + attributes.style?.layout ?? {}; // Only update attributes if columnStart or rowStart are set. if ( columnStart || rowStart ) { updates[ clientId ] = { @@ -123,7 +138,7 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { } // Remove row styles in auto mode - if ( rowCount ) { + if ( gridLayout.rowCount ) { updates[ gridClientId ] = { layout: { ...gridLayout, @@ -146,23 +161,47 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { gridClientId, gridLayout, blockOrder, - // Needed for linter: + previouslySelectedBlockRect, + // These won't change, but the linter thinks they might: __unstableMarkNextChangeAsNotPersistent, getBlockAttributes, updateBlockAttributes, ] ); } -function getFirstEmptyCell( rects, columnCount, columnSpan = 1, rowSpan = 1 ) { - for ( let row = 1; ; row++ ) { - for ( let column = 1; column <= columnCount; column++ ) { - const rect = new GridRect( { +/** + * @param {GridRect[]} occupiedRects + * @param {number} gridColumnCount + * @param {number} blockColumnSpan + * @param {number} blockRowSpan + * @param {number?} startColumn + * @param {number?} startRow + */ +function placeBlock( + occupiedRects, + gridColumnCount, + blockColumnSpan, + blockRowSpan, + startColumn = 1, + startRow = 1 +) { + for ( let row = startRow; ; row++ ) { + for ( + let column = row === startRow ? startColumn : 1; + column <= gridColumnCount; + column++ + ) { + const candidateRect = new GridRect( { columnStart: column, rowStart: row, - columnSpan, - rowSpan, + columnSpan: blockColumnSpan, + rowSpan: blockRowSpan, } ); - if ( ! rects.some( ( r ) => r.intersectsRect( rect ) ) ) { + if ( + ! occupiedRects.some( ( r ) => + r.intersectsRect( candidateRect ) + ) + ) { return [ column, row ]; } } diff --git a/packages/block-editor/src/hooks/grid-visualizer.js b/packages/block-editor/src/hooks/grid-visualizer.js index 42b45952d45d15..ffad5edd9842c4 100644 --- a/packages/block-editor/src/hooks/grid-visualizer.js +++ b/packages/block-editor/src/hooks/grid-visualizer.js @@ -26,14 +26,12 @@ function GridTools( { clientId, layout } ) { }; } ); - if ( ! isSelected && ! isDragging ) { - return null; - } - return ( <> - + { ( isSelected || isDragging ) && ( + + ) } ); }