From 539bb86d59f5c52a5e8ea4d2291f8a2582b5924a Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 24 Apr 2024 15:30:31 +1000 Subject: [PATCH 01/31] Try version of grid where manual mode means all blocks have a columnStart and rowStart --- .../src/components/block-mover/index.js | 60 +++-- .../src/components/grid-visualizer/index.js | 2 - .../src/components/grid-visualizer/utils.js | 5 - .../src/components/grid/grid-item-movers.js | 66 +++++ .../src/components/grid/grid-item-resizer.js | 58 +++++ .../src/components/grid/grid-visualizer.js | 241 ++++++++++++++++++ .../block-editor/src/components/grid/index.js | 4 + .../src/components/grid/style.scss | 63 +++++ .../components/grid/use-grid-layout-sync.js | 147 +++++++++++ .../block-editor/src/components/grid/utils.js | 124 +++++++++ .../block-editor/src/hooks/layout-child.js | 42 ++- packages/block-editor/src/layouts/grid.js | 80 ++---- packages/block-editor/src/style.scss | 2 +- packages/block-library/src/group/edit.js | 3 +- 14 files changed, 799 insertions(+), 98 deletions(-) delete mode 100644 packages/block-editor/src/components/grid-visualizer/index.js delete mode 100644 packages/block-editor/src/components/grid-visualizer/utils.js create mode 100644 packages/block-editor/src/components/grid/grid-item-movers.js create mode 100644 packages/block-editor/src/components/grid/grid-item-resizer.js create mode 100644 packages/block-editor/src/components/grid/grid-visualizer.js create mode 100644 packages/block-editor/src/components/grid/index.js create mode 100644 packages/block-editor/src/components/grid/style.scss create mode 100644 packages/block-editor/src/components/grid/use-grid-layout-sync.js create mode 100644 packages/block-editor/src/components/grid/utils.js diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index a143ab391a43f..f478135c71850 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -25,7 +25,14 @@ function BlockMover( { isBlockMoverUpButtonDisabled, isBlockMoverDownButtonDisabled, } ) { - const { canMove, rootClientId, isFirst, isLast, orientation } = useSelect( + const { + canMove, + rootClientId, + isFirst, + isLast, + orientation, + isManualGrid, + } = useSelect( ( select ) => { const { getBlockIndex, @@ -33,6 +40,7 @@ function BlockMover( { canMoveBlocks, getBlockOrder, getBlockRootClientId, + getBlockAttributes, } = select( blockEditorStore ); const normalizedClientIds = Array.isArray( clientIds ) ? clientIds @@ -44,6 +52,7 @@ function BlockMover( { normalizedClientIds[ normalizedClientIds.length - 1 ] ); const blockOrder = getBlockOrder( _rootClientId ); + const { layout = {} } = getBlockAttributes( _rootClientId ) ?? {}; return { canMove: canMoveBlocks( clientIds, _rootClientId ), @@ -51,6 +60,9 @@ function BlockMover( { isFirst: firstIndex === 0, isLast: lastIndex === blockOrder.length - 1, orientation: getBlockListSettings( _rootClientId )?.orientation, + // TODO: Doesn't feel great to couple BlockMover and grid layouts. + // TODO: Can we use useLayout() instead? + isManualGrid: layout.type === 'grid' && !! layout.columnCount, }; }, [ clientIds ] @@ -60,8 +72,6 @@ function BlockMover( { return null; } - const dragHandleLabel = __( 'Drag' ); - return ( ); } diff --git a/packages/block-editor/src/components/grid-visualizer/index.js b/packages/block-editor/src/components/grid-visualizer/index.js deleted file mode 100644 index add845d702203..0000000000000 --- a/packages/block-editor/src/components/grid-visualizer/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { GridVisualizer } from './grid-visualizer'; -export { GridItemResizer } from './grid-item-resizer'; diff --git a/packages/block-editor/src/components/grid-visualizer/utils.js b/packages/block-editor/src/components/grid-visualizer/utils.js deleted file mode 100644 index a100e596a4e24..0000000000000 --- a/packages/block-editor/src/components/grid-visualizer/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -export function getComputedCSS( element, property ) { - return element.ownerDocument.defaultView - .getComputedStyle( element ) - .getPropertyValue( property ); -} diff --git a/packages/block-editor/src/components/grid/grid-item-movers.js b/packages/block-editor/src/components/grid/grid-item-movers.js new file mode 100644 index 0000000000000..a36105fc6db86 --- /dev/null +++ b/packages/block-editor/src/components/grid/grid-item-movers.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { ToolbarButton } from '@wordpress/components'; +import { arrowLeft, arrowUp, arrowDown, arrowRight } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import BlockControls from '../block-controls'; + +export function GridItemMovers( { layout, parentLayout, onChange } ) { + const columnStart = layout?.columnStart ?? 1; + const rowStart = layout?.rowStart ?? 1; + const columnSpan = layout?.columnSpan ?? 1; + const rowSpan = layout?.rowSpan ?? 1; + const columnEnd = columnStart + columnSpan - 1; + const rowEnd = rowStart + rowSpan - 1; + const columnCount = parentLayout?.columnCount; + const rowCount = parentLayout?.rowCount; + return ( + + + onChange( { + rowStart: rowStart - 1, + } ) + } + /> + = rowCount } + onClick={ () => + onChange( { + rowStart: rowStart + 1, + } ) + } + /> + + onChange( { + columnStart: columnStart - 1, + } ) + } + /> + = columnCount } + onClick={ () => + onChange( { + columnStart: columnStart + 1, + } ) + } + /> + + ); +} diff --git a/packages/block-editor/src/components/grid/grid-item-resizer.js b/packages/block-editor/src/components/grid/grid-item-resizer.js new file mode 100644 index 0000000000000..f628f081286f7 --- /dev/null +++ b/packages/block-editor/src/components/grid/grid-item-resizer.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { ResizableBox } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; +import BlockPopoverCover from '../block-popover/cover'; +import { getGridRect } from './utils'; + +export function GridItemResizer( { clientId, onChange } ) { + const blockElement = useBlockElement( clientId ); + if ( ! blockElement ) { + return null; + } + return ( + + { + const rect = getGridRect( + blockElement.parentElement, + new window.DOMRect( + blockElement.offsetLeft, + blockElement.offsetTop, + boxElement.offsetWidth, + boxElement.offsetHeight + ) + ); + onChange( { + columnSpan: rect.columnSpan, + rowSpan: rect.rowSpan, + } ); + } } + /> + + ); +} diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js new file mode 100644 index 0000000000000..bbe68c0b29a04 --- /dev/null +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -0,0 +1,241 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; +import BlockPopoverCover from '../block-popover/cover'; +import { getComputedCSS, range, GridRect, getGridItemRect } from './utils'; +import { store as blockEditorStore } from '../../store'; + +export function GridVisualizer( { clientId } ) { + const gridElement = useBlockElement( clientId ); + if ( ! gridElement ) { + return null; + } + return ( + + ); +} + +function getGridInfo( gridElement ) { + const gridTemplateColumns = getComputedCSS( + gridElement, + 'grid-template-columns' + ); + const gridTemplateRows = getComputedCSS( + gridElement, + 'grid-template-rows' + ); + const numColumns = gridTemplateColumns.split( ' ' ).length; + const numRows = gridTemplateRows.split( ' ' ).length; + const numItems = numColumns * numRows; + return { + numColumns, + numRows, + numItems, + style: { + gridTemplateColumns, + gridTemplateRows, + gap: getComputedCSS( gridElement, 'gap' ), + padding: getComputedCSS( gridElement, 'padding' ), + }, + }; +} + +function GridVisualizerGrid( { clientId, gridElement } ) { + const [ gridInfo, setGridInfo ] = useState( () => + getGridInfo( gridElement ) + ); + const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); + const [ highlightedRect, setHighlightedRect ] = useState( null ); + + const { getBlockAttributes } = useSelect( blockEditorStore ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + + useEffect( () => { + const observers = []; + for ( const element of [ gridElement, ...gridElement.children ] ) { + const observer = new window.ResizeObserver( () => { + setGridInfo( getGridInfo( gridElement ) ); + } ); + observer.observe( element ); + observers.push( observer ); + } + return () => { + for ( const observer of observers ) { + observer.disconnect(); + } + }; + }, [ gridElement ] ); + + useEffect( () => { + function onGlobalDrag() { + setIsDroppingAllowed( true ); + } + function onGlobalDragEnd() { + setIsDroppingAllowed( false ); + } + document.addEventListener( 'drag', onGlobalDrag ); + document.addEventListener( 'dragend', onGlobalDragEnd ); + return () => { + document.removeEventListener( 'drag', onGlobalDrag ); + document.removeEventListener( 'dragend', onGlobalDragEnd ); + }; + }, [] ); + + return ( + +
+ { range( 1, gridInfo.numRows ).map( ( row ) => + range( 1, gridInfo.numColumns ).map( ( column ) => ( + { + const attributes = + getBlockAttributes( srcClientId ); + const rect = new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: + attributes.style?.layout?.columnSpan, + rowSpan: attributes.style?.layout?.rowSpan, + } ); + + const isInBounds = new GridRect( { + columnSpan: gridInfo.numColumns, + rowSpan: gridInfo.numRows, + } ).containsRect( rect ); + if ( ! isInBounds ) { + return false; + } + + const isOverlapping = Array.from( + gridElement.children + ).some( + ( child ) => + child.dataset.block !== srcClientId && + rect.intersectsRect( + getGridItemRect( child ) + ) + ); + if ( isOverlapping ) { + return false; + } + + return true; + } } + onDragEnter={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + setHighlightedRect( + new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout?.rowSpan, + } ) + ); + } } + onDragLeave={ () => { + // onDragEnter can be called before onDragLeave if the user moves + // their mouse quickly, so only clear the highlight if it was set + // by this cell. + setHighlightedRect( ( prevHighlightedRect ) => + prevHighlightedRect?.columnStart === + column && + prevHighlightedRect?.rowStart === row + ? null + : prevHighlightedRect + ); + } } + onDrop={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, + }, + } ); + setHighlightedRect( null ); + } } + /> + ) ) + ) } +
+
+ ); +} + +function GridVisualizerCell( { + isHighlighted, + validateDrag, + onDragEnter, + onDragLeave, + onDrop, +} ) { + const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); + + const ref = useDropZone( { + onDragEnter() { + const [ srcClientId ] = getDraggedBlockClientIds(); + if ( srcClientId && validateDrag( srcClientId ) ) { + onDragEnter( srcClientId ); + } + }, + onDragLeave() { + onDragLeave(); + }, + onDrop() { + const [ srcClientId ] = getDraggedBlockClientIds(); + if ( srcClientId && validateDrag( srcClientId ) ) { + onDrop( srcClientId ); + } + }, + } ); + + return ( +
+
+
+ ); +} diff --git a/packages/block-editor/src/components/grid/index.js b/packages/block-editor/src/components/grid/index.js new file mode 100644 index 0000000000000..a1552610102a0 --- /dev/null +++ b/packages/block-editor/src/components/grid/index.js @@ -0,0 +1,4 @@ +export { GridVisualizer } from './grid-visualizer'; +export { GridItemResizer } from './grid-item-resizer'; +export { GridItemMovers } from './grid-item-movers'; +export { useGridLayoutSync } from './use-grid-layout-sync'; diff --git a/packages/block-editor/src/components/grid/style.scss b/packages/block-editor/src/components/grid/style.scss new file mode 100644 index 0000000000000..dfb3e57f84ae0 --- /dev/null +++ b/packages/block-editor/src/components/grid/style.scss @@ -0,0 +1,63 @@ +.block-editor-grid-visualizer { + // Specificity to override the z-index and pointer-events set by .components-popover. + &.block-editor-grid-visualizer.block-editor-grid-visualizer { + z-index: z-index(".block-editor-grid-visualizer"); + + .components-popover__content * { + pointer-events: none; + } + + &.is-dropping-allowed { + .block-editor-grid-visualizer__drop-zone { + pointer-events: all; + } + } + } +} + +.block-editor-grid-visualizer__grid { + display: grid; +} + +.block-editor-grid-visualizer__cell { + align-items: center; + display: flex; + justify-content: center; +} + +.block-editor-grid-visualizer__drop-zone { + background: rgba($gray-400, 0.1); + border: $border-width dotted $gray-300; + width: 100%; + height: 100%; + + // Make drop zone 8x8 at minimum so that it's easier to drag into. This will overflow the parent. + min-width: $grid-unit-10; + min-height: $grid-unit-10; + + &.is-highlighted { + background: var(--wp-admin-theme-color); + } +} + +.block-editor-grid-item-resizer { + // Specificity to override the z-index and pointer-events set by .components-popover. + &.block-editor-grid-item-resizer.block-editor-grid-item-resizer { + z-index: z-index(".block-editor-grid-visualizer"); + + .components-popover__content * { + pointer-events: none; + } + } +} + +.block-editor-grid-item-resizer__box { + border: $border-width solid var(--wp-admin-theme-color); + + .components-resizable-box__handle { + // Specificity to override the pointer-events set by .components-popover. + &.components-resizable-box__handle.components-resizable-box__handle { + pointer-events: all; + } + } +} 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 new file mode 100644 index 0000000000000..afa8f5cbcc132 --- /dev/null +++ b/packages/block-editor/src/components/grid/use-grid-layout-sync.js @@ -0,0 +1,147 @@ +/** + * WordPress dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import { GridRect } from './utils'; + +export function useGridLayoutSync( { clientId: gridClientId } ) { + const { gridLayout, blockOrder } = useSelect( + ( select ) => { + const { getBlockAttributes, getBlockOrder } = + select( blockEditorStore ); + return { + gridLayout: getBlockAttributes( gridClientId ).layout ?? {}, + blockOrder: getBlockOrder( gridClientId ), + }; + }, + [ gridClientId ] + ); + + const { getBlockAttributes } = useSelect( blockEditorStore ); + const { updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); + + useEffect( () => { + const updates = {}; + + const { columnCount, rowCount } = gridLayout; + const isManualGrid = !! columnCount; + + if ( isManualGrid ) { + const rects = []; + + // 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, rowSpan } = + attributes.style?.layout; + if ( ! columnStart || ! rowStart ) { + continue; + } + rects.push( + new GridRect( { + columnStart, + rowStart, + columnSpan, + rowSpan, + } ) + ); + } + + // When in manual mode, ensure that every block has a columnStart and rowStart value. + for ( const clientId of blockOrder ) { + const attributes = getBlockAttributes( clientId ); + const { columnStart, rowStart, columnSpan, rowSpan } = + attributes.style?.layout; + if ( columnStart && rowStart ) { + continue; + } + const [ newColumnStart, newRowStart ] = getFirstEmptyCell( + rects, + columnCount, + rowCount, + columnSpan, + rowSpan + ); + rects.push( + new GridRect( { + columnStart: newColumnStart, + rowStart: newRowStart, + columnSpan, + rowSpan, + } ) + ); + updates[ clientId ] = { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: newColumnStart, + rowStart: newRowStart, + }, + }, + }; + } + } else { + // When in auto mode, remove all of the columnStart and rowStart values. + for ( const clientId of blockOrder ) { + const attributes = getBlockAttributes( clientId ); + const { columnStart, rowStart, ...layout } = + attributes.style?.layout; + updates[ clientId ] = { + style: { + ...attributes.style, + layout, + }, + }; + } + } + + if ( Object.keys( updates ).length ) { + // TODO: remove this console log + // eslint-disable-next-line no-console + console.log( 'useGridLayoutSync updates', updates ); + __unstableMarkNextChangeAsNotPersistent(); + updateBlockAttributes( + Object.keys( updates ), + updates, + /* uniqueByBlock: */ true + ); + } + }, [ + __unstableMarkNextChangeAsNotPersistent, + blockOrder, + getBlockAttributes, + gridLayout, + updateBlockAttributes, + ] ); +} + +function getFirstEmptyCell( + rects, + columnCount, + rowCount, + columnSpan = 1, + rowSpan = 1 +) { + for ( let row = 1; row <= rowCount; row++ ) { + for ( let column = 1; column <= columnCount; column++ ) { + const rect = new GridRect( { + columnStart: column, + rowStart: row, + columnSpan, + rowSpan, + } ); + if ( ! rects.some( ( r ) => r.intersectsRect( rect ) ) ) { + return [ column, row ]; + } + } + } + return [ 1, 1 ]; +} diff --git a/packages/block-editor/src/components/grid/utils.js b/packages/block-editor/src/components/grid/utils.js new file mode 100644 index 0000000000000..b377ce079084b --- /dev/null +++ b/packages/block-editor/src/components/grid/utils.js @@ -0,0 +1,124 @@ +export function range( start, length ) { + return Array.from( { length }, ( _, i ) => start + i ); +} + +export class GridRect { + constructor( { + columnStart, + rowStart, + columnEnd, + rowEnd, + columnSpan, + rowSpan, + } = {} ) { + this.columnStart = columnStart ?? 1; + this.rowStart = rowStart ?? 1; + if ( columnSpan !== undefined ) { + this.columnEnd = this.columnStart + columnSpan - 1; + } else { + this.columnEnd = columnEnd ?? this.columnStart; + } + if ( rowSpan !== undefined ) { + this.rowEnd = this.rowStart + rowSpan - 1; + } else { + this.rowEnd = rowEnd ?? this.rowStart; + } + } + + get columnSpan() { + return this.columnEnd - this.columnStart + 1; + } + + get rowSpan() { + return this.rowEnd - this.rowStart + 1; + } + + contains( column, row ) { + return ( + column >= this.columnStart && + column <= this.columnEnd && + row >= this.rowStart && + row <= this.rowEnd + ); + } + + containsRect( rect ) { + return ( + this.contains( rect.columnStart, rect.rowStart ) && + this.contains( rect.columnEnd, rect.rowEnd ) + ); + } + + intersectsRect( rect ) { + return ( + this.columnStart <= rect.columnEnd && + this.columnEnd >= rect.columnStart && + this.rowStart <= rect.rowEnd && + this.rowEnd >= rect.rowStart + ); + } +} + +export function getComputedCSS( element, property ) { + return element.ownerDocument.defaultView + .getComputedStyle( element ) + .getPropertyValue( property ); +} + +function getGridTracks( template, gap ) { + const tracks = []; + for ( const size of template.split( ' ' ) ) { + const previousTrack = tracks[ tracks.length - 1 ]; + const start = previousTrack ? previousTrack.end + gap : 0; + const end = start + parseFloat( size ); + tracks.push( { start, end } ); + } + return tracks; +} + +function getClosestTrack( tracks, position, edge = 'start' ) { + return tracks.reduce( + ( closest, track, index ) => + Math.abs( track[ edge ] - position ) < + Math.abs( tracks[ closest ][ edge ] - position ) + ? index + : closest, + 0 + ); +} + +export function getGridRect( gridElement, rect ) { + const columnGap = parseFloat( getComputedCSS( gridElement, 'column-gap' ) ); + const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); + const gridColumnTracks = getGridTracks( + getComputedCSS( gridElement, 'grid-template-columns' ), + columnGap + ); + const gridRowTracks = getGridTracks( + getComputedCSS( gridElement, 'grid-template-rows' ), + rowGap + ); + const columnStart = getClosestTrack( gridColumnTracks, rect.left ) + 1; + const rowStart = getClosestTrack( gridRowTracks, rect.top ) + 1; + const columnEnd = + getClosestTrack( gridColumnTracks, rect.right, 'end' ) + 1; + const rowEnd = getClosestTrack( gridRowTracks, rect.bottom, 'end' ) + 1; + return new GridRect( { + columnStart, + columnEnd, + rowStart, + rowEnd, + } ); +} + +export function getGridItemRect( gridItemElement ) { + return getGridRect( + gridItemElement.parentElement, + new window.DOMRect( + gridItemElement.offsetLeft, + gridItemElement.offsetTop, + gridItemElement.offsetWidth, + gridItemElement.offsetHeight + ) + ); +} diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index 860b4aaf04179..a0c931d2aae11 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -11,7 +11,11 @@ import { useState } from '@wordpress/element'; import { store as blockEditorStore } from '../store'; import { useStyleOverride } from './utils'; import { useLayout } from '../components/block-list/layout'; -import { GridVisualizer, GridItemResizer } from '../components/grid-visualizer'; +import { + GridVisualizer, + GridItemResizer, + GridItemMovers, +} from '../components/grid'; function useBlockPropsChildLayoutStyles( { style } ) { const shouldRenderChildLayoutStyles = useSelect( ( select ) => { @@ -138,6 +142,7 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { const { type: parentLayoutType = 'default', allowSizingOnChildren = false, + parentLayout, } = useLayout() || {}; const rootClientId = useSelect( @@ -154,6 +159,20 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { return null; } + const isManualGrid = !! parentLayout.columnCount; + + function updateLayout( layout ) { + setAttributes( { + style: { + ...style, + layout: { + ...style?.layout, + ...layout, + }, + }, + } ); + } + return ( <> { - setAttributes( { - style: { - ...style, - layout: { - ...style?.layout, - columnSpan, - rowSpan, - }, - }, - } ); - } } + onChange={ updateLayout } /> ) } + { isManualGrid && + window.__experimentalEnableGridInteractivity( + + ) } ); } diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 4528de117c45b..c7a5f76d4fbc0 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -23,7 +23,7 @@ import { appendSelectors, getBlockGapCSS } from './utils'; import { getGapCSSValue } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; import { LAYOUT_DEFINITIONS } from './definitions'; -import { GridVisualizer } from '../components/grid-visualizer'; +import { GridVisualizer, useGridLayoutSync } from '../components/grid'; const RANGE_CONTROL_MAX_VALUES = { px: 600, @@ -93,7 +93,14 @@ export default { ); }, toolBarControls: function GridLayoutToolbarControls( { clientId } ) { - return ; + return ( + <> + { window.__experimentalEnableGridInteractivity && ( + + ) } + + + ); }, getLayoutStyle: function getLayoutStyle( { selector, @@ -245,9 +252,6 @@ function GridLayoutColumnsAndRowsControl( { return ( <>
- - { __( 'Columns' ) } - - - - - onChange( { - ...layout, - columnCount: value, - } ) - } - min={ 1 } - max={ 16 } - withInputField={ false } - label={ __( 'Columns' ) } - hideLabelFromVision /> - -
- { allowSizingOnChildren && - window.__experimentalEnableGridInteractivity && ( -
- - { __( 'Rows' ) } - - + { allowSizingOnChildren && + window.__experimentalEnableGridInteractivity && ( - - - onChange( { - ...layout, - rowCount: value, - } ) - } - min={ 1 } - max={ 16 } - withInputField={ false } - label={ __( 'Rows' ) } - hideLabelFromVision - /> - - -
- ) } + ) } + + ); } @@ -366,10 +329,19 @@ function GridLayoutTypeControl( { layout, onChange } ) { return ( ); } + +function GridLayoutSync( props ) { + useGridLayoutSync( props ); +} diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index cf4683b02c707..0772ddf0d31e5 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -27,7 +27,7 @@ @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; @import "./components/global-styles/style.scss"; -@import "./components/grid-visualizer/style.scss"; +@import "./components/grid/style.scss"; @import "./components/height-control/style.scss"; @import "./components/image-size-control/style.scss"; @import "./components/inserter-list-item/style.scss"; diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index a763bc95e60d7..09cd1f99dad3e 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -96,6 +96,7 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { const { type = 'default' } = layout; const layoutSupportEnabled = themeSupportsLayout || type === 'flex' || type === 'grid'; + const isManualGrid = type === 'grid' && !! layout.columnCount; // Hooks. const ref = useRef(); @@ -109,7 +110,7 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { // Default to the regular appender being rendered. let renderAppender; - if ( showPlaceholder ) { + if ( showPlaceholder || isManualGrid ) { // In the placeholder state, ensure the appender is not rendered. // This is needed because `...innerBlocksProps` is used in the placeholder // state so that blocks can dragged onto the placeholder area From 37ff369f78e25cd098b191a92f4c312e4253ff74 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 14 Jun 2024 13:24:23 +1000 Subject: [PATCH 02/31] Fix some rebase breakage --- .../src/components/grid/grid-item-resizer.js | 205 ++++++++++++++++-- .../src/components/grid/grid-visualizer.js | 13 +- .../block-editor/src/hooks/layout-child.js | 22 +- 3 files changed, 204 insertions(+), 36 deletions(-) diff --git a/packages/block-editor/src/components/grid/grid-item-resizer.js b/packages/block-editor/src/components/grid/grid-item-resizer.js index f628f081286f7..21e9bfccee754 100644 --- a/packages/block-editor/src/components/grid/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid/grid-item-resizer.js @@ -2,24 +2,93 @@ * WordPress dependencies */ import { ResizableBox } from '@wordpress/components'; +import { useState, useEffect } from '@wordpress/element'; /** * Internal dependencies */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { getGridRect } from './utils'; +import { getComputedCSS } from './utils'; -export function GridItemResizer( { clientId, onChange } ) { +export function GridItemResizer( { clientId, bounds, onChange } ) { const blockElement = useBlockElement( clientId ); - if ( ! blockElement ) { + const rootBlockElement = blockElement?.parentElement; + + if ( ! blockElement || ! rootBlockElement ) { return null; } + + return ( + + ); +} + +function GridItemResizerInner( { + clientId, + bounds, + blockElement, + rootBlockElement, + onChange, +} ) { + const [ resizeDirection, setResizeDirection ] = useState( null ); + const [ enableSide, setEnableSide ] = useState( { + top: false, + bottom: false, + left: false, + right: false, + } ); + + useEffect( () => { + const observer = new window.ResizeObserver( () => { + const blockClientRect = blockElement.getBoundingClientRect(); + const rootBlockClientRect = + rootBlockElement.getBoundingClientRect(); + setEnableSide( { + top: blockClientRect.top > rootBlockClientRect.top, + bottom: blockClientRect.bottom < rootBlockClientRect.bottom, + left: blockClientRect.left > rootBlockClientRect.left, + right: blockClientRect.right < rootBlockClientRect.right, + } ); + } ); + observer.observe( blockElement ); + return () => observer.disconnect(); + }, [ blockElement, rootBlockElement ] ); + + const justification = { + right: 'flex-start', + left: 'flex-end', + }; + + const alignment = { + top: 'flex-end', + bottom: 'flex-start', + }; + + const styles = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + ...( justification[ resizeDirection ] && { + justifyContent: justification[ resizeDirection ], + } ), + ...( alignment[ resizeDirection ] && { + alignItems: alignment[ resizeDirection ], + } ), + }; + return ( { + /* + * The container justification and alignment need to be set + * according to the direction the resizer is being dragged in, + * so that it resizes in the right direction. + */ + setResizeDirection( direction ); + + /* + * The mouseup event on the resize handle doesn't trigger if the mouse + * isn't directly above the handle, so we try to detect if it happens + * outside the grid and dispatch a mouseup event on the handle. + */ + blockElement.ownerDocument.addEventListener( + 'mouseup', + () => { + event.target.dispatchEvent( + new Event( 'mouseup', { bubbles: true } ) + ); + }, + { once: true } + ); + } } onResizeStop={ ( event, direction, boxElement ) => { - const rect = getGridRect( - blockElement.parentElement, - new window.DOMRect( - blockElement.offsetLeft, - blockElement.offsetTop, - boxElement.offsetWidth, - boxElement.offsetHeight - ) + const columnGap = parseFloat( + getComputedCSS( rootBlockElement, 'column-gap' ) ); + const rowGap = parseFloat( + getComputedCSS( rootBlockElement, 'row-gap' ) + ); + const gridColumnTracks = getGridTracks( + getComputedCSS( + rootBlockElement, + 'grid-template-columns' + ), + columnGap + ); + const gridRowTracks = getGridTracks( + getComputedCSS( + rootBlockElement, + 'grid-template-rows' + ), + rowGap + ); + const rect = new window.DOMRect( + blockElement.offsetLeft + boxElement.offsetLeft, + blockElement.offsetTop + boxElement.offsetTop, + boxElement.offsetWidth, + boxElement.offsetHeight + ); + const columnStart = + getClosestTrack( gridColumnTracks, rect.left ) + 1; + const rowStart = + getClosestTrack( gridRowTracks, rect.top ) + 1; + const columnEnd = + getClosestTrack( gridColumnTracks, rect.right, 'end' ) + + 1; + const rowEnd = + getClosestTrack( gridRowTracks, rect.bottom, 'end' ) + + 1; onChange( { - columnSpan: rect.columnSpan, - rowSpan: rect.rowSpan, + columnSpan: columnEnd - columnStart + 1, + rowSpan: rowEnd - rowStart + 1, } ); } } /> ); } + +/** + * Given a grid-template-columns or grid-template-rows CSS property value, gets the start and end + * position in pixels of each grid track. + * + * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track + * + * @param {string} template The grid-template-columns or grid-template-rows CSS property value. + * Only supports fixed sizes in pixels. + * @param {number} gap The gap between grid tracks in pixels. + * + * @return {Array<{start: number, end: number}>} An array of objects with the start and end + * position in pixels of each grid track. + */ +function getGridTracks( template, gap ) { + const tracks = []; + for ( const size of template.split( ' ' ) ) { + const previousTrack = tracks[ tracks.length - 1 ]; + const start = previousTrack ? previousTrack.end + gap : 0; + const end = start + parseFloat( size ); + tracks.push( { start, end } ); + } + return tracks; +} + +/** + * Given an array of grid tracks and a position in pixels, gets the index of the closest track to + * that position. + * + * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track + * + * @param {Array<{start: number, end: number}>} tracks An array of objects with the start and end + * position in pixels of each grid track. + * @param {number} position The position in pixels. + * @param {string} edge The edge of the track to compare the + * position to. Either 'start' or 'end'. + * + * @return {number} The index of the closest track to the position. 0-based, unlike CSS grid which + * is 1-based. + */ +function getClosestTrack( tracks, position, edge = 'start' ) { + return tracks.reduce( + ( closest, track, index ) => + Math.abs( track[ edge ] - position ) < + Math.abs( tracks[ closest ][ edge ] - position ) + ? index + : closest, + 0 + ); +} diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index bbe68c0b29a04..ee9fc0a8c2a70 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import classnames from 'classnames'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -96,7 +96,7 @@ function GridVisualizerGrid( { clientId, gridElement } ) { return (
); diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index a0c931d2aae11..f6f166d9e2a2e 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -139,11 +139,12 @@ function useBlockPropsChildLayoutStyles( { style } ) { } function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { + const parentLayout = useLayout() || {}; const { type: parentLayoutType = 'default', allowSizingOnChildren = false, - parentLayout, - } = useLayout() || {}; + columnCount, + } = parentLayout; const rootClientId = useSelect( ( select ) => { @@ -159,7 +160,7 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { return null; } - const isManualGrid = !! parentLayout.columnCount; + const isManualGrid = !! columnCount; function updateLayout( layout ) { setAttributes( { @@ -187,14 +188,13 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { onChange={ updateLayout } /> ) } - { isManualGrid && - window.__experimentalEnableGridInteractivity( - - ) } + { isManualGrid && window.__experimentalEnableGridInteractivity && ( + + ) } ); } From b3e7d95cdcd285678318fb59bf0d1b801446b524 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 14 Jun 2024 15:22:32 +1000 Subject: [PATCH 03/31] Clean up deleted files --- .../grid-visualizer/grid-item-resizer.js | 229 ------------------ .../grid-visualizer/grid-visualizer.js | 101 -------- .../src/components/grid-visualizer/style.scss | 34 --- .../src/components/grid/grid-item-resizer.js | 52 +--- .../src/components/grid/grid-visualizer.js | 15 +- .../block-editor/src/components/grid/utils.js | 32 ++- 6 files changed, 41 insertions(+), 422 deletions(-) delete mode 100644 packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js delete mode 100644 packages/block-editor/src/components/grid-visualizer/grid-visualizer.js delete mode 100644 packages/block-editor/src/components/grid-visualizer/style.scss diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js deleted file mode 100644 index 21e9bfccee754..0000000000000 --- a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js +++ /dev/null @@ -1,229 +0,0 @@ -/** - * WordPress dependencies - */ -import { ResizableBox } from '@wordpress/components'; -import { useState, useEffect } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import BlockPopoverCover from '../block-popover/cover'; -import { getComputedCSS } from './utils'; - -export function GridItemResizer( { clientId, bounds, onChange } ) { - const blockElement = useBlockElement( clientId ); - const rootBlockElement = blockElement?.parentElement; - - if ( ! blockElement || ! rootBlockElement ) { - return null; - } - - return ( - - ); -} - -function GridItemResizerInner( { - clientId, - bounds, - blockElement, - rootBlockElement, - onChange, -} ) { - const [ resizeDirection, setResizeDirection ] = useState( null ); - const [ enableSide, setEnableSide ] = useState( { - top: false, - bottom: false, - left: false, - right: false, - } ); - - useEffect( () => { - const observer = new window.ResizeObserver( () => { - const blockClientRect = blockElement.getBoundingClientRect(); - const rootBlockClientRect = - rootBlockElement.getBoundingClientRect(); - setEnableSide( { - top: blockClientRect.top > rootBlockClientRect.top, - bottom: blockClientRect.bottom < rootBlockClientRect.bottom, - left: blockClientRect.left > rootBlockClientRect.left, - right: blockClientRect.right < rootBlockClientRect.right, - } ); - } ); - observer.observe( blockElement ); - return () => observer.disconnect(); - }, [ blockElement, rootBlockElement ] ); - - const justification = { - right: 'flex-start', - left: 'flex-end', - }; - - const alignment = { - top: 'flex-end', - bottom: 'flex-start', - }; - - const styles = { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - ...( justification[ resizeDirection ] && { - justifyContent: justification[ resizeDirection ], - } ), - ...( alignment[ resizeDirection ] && { - alignItems: alignment[ resizeDirection ], - } ), - }; - - return ( - - { - /* - * The container justification and alignment need to be set - * according to the direction the resizer is being dragged in, - * so that it resizes in the right direction. - */ - setResizeDirection( direction ); - - /* - * The mouseup event on the resize handle doesn't trigger if the mouse - * isn't directly above the handle, so we try to detect if it happens - * outside the grid and dispatch a mouseup event on the handle. - */ - blockElement.ownerDocument.addEventListener( - 'mouseup', - () => { - event.target.dispatchEvent( - new Event( 'mouseup', { bubbles: true } ) - ); - }, - { once: true } - ); - } } - onResizeStop={ ( event, direction, boxElement ) => { - const columnGap = parseFloat( - getComputedCSS( rootBlockElement, 'column-gap' ) - ); - const rowGap = parseFloat( - getComputedCSS( rootBlockElement, 'row-gap' ) - ); - const gridColumnTracks = getGridTracks( - getComputedCSS( - rootBlockElement, - 'grid-template-columns' - ), - columnGap - ); - const gridRowTracks = getGridTracks( - getComputedCSS( - rootBlockElement, - 'grid-template-rows' - ), - rowGap - ); - const rect = new window.DOMRect( - blockElement.offsetLeft + boxElement.offsetLeft, - blockElement.offsetTop + boxElement.offsetTop, - boxElement.offsetWidth, - boxElement.offsetHeight - ); - const columnStart = - getClosestTrack( gridColumnTracks, rect.left ) + 1; - const rowStart = - getClosestTrack( gridRowTracks, rect.top ) + 1; - const columnEnd = - getClosestTrack( gridColumnTracks, rect.right, 'end' ) + - 1; - const rowEnd = - getClosestTrack( gridRowTracks, rect.bottom, 'end' ) + - 1; - onChange( { - columnSpan: columnEnd - columnStart + 1, - rowSpan: rowEnd - rowStart + 1, - } ); - } } - /> - - ); -} - -/** - * Given a grid-template-columns or grid-template-rows CSS property value, gets the start and end - * position in pixels of each grid track. - * - * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track - * - * @param {string} template The grid-template-columns or grid-template-rows CSS property value. - * Only supports fixed sizes in pixels. - * @param {number} gap The gap between grid tracks in pixels. - * - * @return {Array<{start: number, end: number}>} An array of objects with the start and end - * position in pixels of each grid track. - */ -function getGridTracks( template, gap ) { - const tracks = []; - for ( const size of template.split( ' ' ) ) { - const previousTrack = tracks[ tracks.length - 1 ]; - const start = previousTrack ? previousTrack.end + gap : 0; - const end = start + parseFloat( size ); - tracks.push( { start, end } ); - } - return tracks; -} - -/** - * Given an array of grid tracks and a position in pixels, gets the index of the closest track to - * that position. - * - * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track - * - * @param {Array<{start: number, end: number}>} tracks An array of objects with the start and end - * position in pixels of each grid track. - * @param {number} position The position in pixels. - * @param {string} edge The edge of the track to compare the - * position to. Either 'start' or 'end'. - * - * @return {number} The index of the closest track to the position. 0-based, unlike CSS grid which - * is 1-based. - */ -function getClosestTrack( tracks, position, edge = 'start' ) { - return tracks.reduce( - ( closest, track, index ) => - Math.abs( track[ edge ] - position ) < - Math.abs( tracks[ closest ][ edge ] - position ) - ? index - : closest, - 0 - ); -} diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js deleted file mode 100644 index cff5efc5218e1..0000000000000 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * WordPress dependencies - */ -import { useState, useEffect, forwardRef } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import BlockPopoverCover from '../block-popover/cover'; -import { store as blockEditorStore } from '../../store'; -import { getComputedCSS } from './utils'; - -export function GridVisualizer( { clientId, contentRef } ) { - const isDistractionFree = useSelect( - ( select ) => - select( blockEditorStore ).getSettings().isDistractionFree, - [] - ); - const blockElement = useBlockElement( clientId ); - - if ( isDistractionFree || ! blockElement ) { - return null; - } - - return ( - - - - ); -} - -const GridVisualizerGrid = forwardRef( ( { blockElement }, ref ) => { - const [ gridInfo, setGridInfo ] = useState( () => - getGridInfo( blockElement ) - ); - useEffect( () => { - const observers = []; - for ( const element of [ blockElement, ...blockElement.children ] ) { - const observer = new window.ResizeObserver( () => { - setGridInfo( getGridInfo( blockElement ) ); - } ); - observer.observe( element ); - observers.push( observer ); - } - return () => { - for ( const observer of observers ) { - observer.disconnect(); - } - }; - }, [ blockElement ] ); - return ( -
- { Array.from( { length: gridInfo.numItems }, ( _, i ) => ( -
- ) ) } -
- ); -} ); - -function getGridInfo( blockElement ) { - const gridTemplateColumns = getComputedCSS( - blockElement, - 'grid-template-columns' - ); - const gridTemplateRows = getComputedCSS( - blockElement, - 'grid-template-rows' - ); - const numColumns = gridTemplateColumns.split( ' ' ).length; - const numRows = gridTemplateRows.split( ' ' ).length; - const numItems = numColumns * numRows; - return { - numItems, - currentColor: getComputedCSS( blockElement, 'color' ), - style: { - gridTemplateColumns, - gridTemplateRows, - gap: getComputedCSS( blockElement, 'gap' ), - padding: getComputedCSS( blockElement, 'padding' ), - }, - }; -} diff --git a/packages/block-editor/src/components/grid-visualizer/style.scss b/packages/block-editor/src/components/grid-visualizer/style.scss deleted file mode 100644 index 2adaf18f52470..0000000000000 --- a/packages/block-editor/src/components/grid-visualizer/style.scss +++ /dev/null @@ -1,34 +0,0 @@ -// TODO: Specificity hacks to get rid of all these darn !importants. - -.block-editor-grid-visualizer { - z-index: z-index(".block-editor-grid-visualizer") !important; -} - -.block-editor-grid-visualizer .components-popover__content * { - pointer-events: none !important; -} - -.block-editor-grid-visualizer__grid { - display: grid; -} - -.block-editor-grid-visualizer__item { - outline: 1px solid transparent; - border-radius: $radius-block-ui; -} - -.block-editor-grid-item-resizer { - z-index: z-index(".block-editor-grid-visualizer") !important; -} - -.block-editor-grid-item-resizer .components-popover__content * { - pointer-events: none !important; -} - -.block-editor-grid-item-resizer__box { - border: $border-width solid var(--wp-admin-theme-color); - - .components-resizable-box__handle { - pointer-events: all !important; - } -} diff --git a/packages/block-editor/src/components/grid/grid-item-resizer.js b/packages/block-editor/src/components/grid/grid-item-resizer.js index 21e9bfccee754..1877fa995016e 100644 --- a/packages/block-editor/src/components/grid/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid/grid-item-resizer.js @@ -9,7 +9,7 @@ import { useState, useEffect } from '@wordpress/element'; */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { getComputedCSS } from './utils'; +import { getComputedCSS, getGridTracks, getClosestTrack } from './utils'; export function GridItemResizer( { clientId, bounds, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -177,53 +177,3 @@ function GridItemResizerInner( { ); } - -/** - * Given a grid-template-columns or grid-template-rows CSS property value, gets the start and end - * position in pixels of each grid track. - * - * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track - * - * @param {string} template The grid-template-columns or grid-template-rows CSS property value. - * Only supports fixed sizes in pixels. - * @param {number} gap The gap between grid tracks in pixels. - * - * @return {Array<{start: number, end: number}>} An array of objects with the start and end - * position in pixels of each grid track. - */ -function getGridTracks( template, gap ) { - const tracks = []; - for ( const size of template.split( ' ' ) ) { - const previousTrack = tracks[ tracks.length - 1 ]; - const start = previousTrack ? previousTrack.end + gap : 0; - const end = start + parseFloat( size ); - tracks.push( { start, end } ); - } - return tracks; -} - -/** - * Given an array of grid tracks and a position in pixels, gets the index of the closest track to - * that position. - * - * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track - * - * @param {Array<{start: number, end: number}>} tracks An array of objects with the start and end - * position in pixels of each grid track. - * @param {number} position The position in pixels. - * @param {string} edge The edge of the track to compare the - * position to. Either 'start' or 'end'. - * - * @return {number} The index of the closest track to the position. 0-based, unlike CSS grid which - * is 1-based. - */ -function getClosestTrack( tracks, position, edge = 'start' ) { - return tracks.reduce( - ( closest, track, index ) => - Math.abs( track[ edge ] - position ) < - Math.abs( tracks[ closest ][ edge ] - position ) - ? index - : closest, - 0 - ); -} diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index ee9fc0a8c2a70..bb7bf9d1f4bf7 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -6,7 +6,7 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { useState, useEffect } from '@wordpress/element'; +import { useState, useEffect, forwardRef } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; @@ -18,13 +18,17 @@ import BlockPopoverCover from '../block-popover/cover'; import { getComputedCSS, range, GridRect, getGridItemRect } from './utils'; import { store as blockEditorStore } from '../../store'; -export function GridVisualizer( { clientId } ) { +export function GridVisualizer( { clientId, contentRef } ) { const gridElement = useBlockElement( clientId ); if ( ! gridElement ) { return null; } return ( - + ); } @@ -53,7 +57,7 @@ function getGridInfo( gridElement ) { }; } -function GridVisualizerGrid( { clientId, gridElement } ) { +const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( gridElement ) ); @@ -103,6 +107,7 @@ function GridVisualizerGrid( { clientId, gridElement } ) { __unstablePopoverSlot="block-toolbar" >
@@ -196,7 +201,7 @@ function GridVisualizerGrid( { clientId, gridElement } ) {
); -} +} ); function GridVisualizerCell( { isHighlighted, diff --git a/packages/block-editor/src/components/grid/utils.js b/packages/block-editor/src/components/grid/utils.js index b377ce079084b..ff02a7bda1e0d 100644 --- a/packages/block-editor/src/components/grid/utils.js +++ b/packages/block-editor/src/components/grid/utils.js @@ -65,7 +65,20 @@ export function getComputedCSS( element, property ) { .getPropertyValue( property ); } -function getGridTracks( template, gap ) { +/** + * Given a grid-template-columns or grid-template-rows CSS property value, gets the start and end + * position in pixels of each grid track. + * + * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track + * + * @param {string} template The grid-template-columns or grid-template-rows CSS property value. + * Only supports fixed sizes in pixels. + * @param {number} gap The gap between grid tracks in pixels. + * + * @return {Array<{start: number, end: number}>} An array of objects with the start and end + * position in pixels of each grid track. + */ +export function getGridTracks( template, gap ) { const tracks = []; for ( const size of template.split( ' ' ) ) { const previousTrack = tracks[ tracks.length - 1 ]; @@ -76,7 +89,22 @@ function getGridTracks( template, gap ) { return tracks; } -function getClosestTrack( tracks, position, edge = 'start' ) { +/** + * Given an array of grid tracks and a position in pixels, gets the index of the closest track to + * that position. + * + * https://css-tricks.com/snippets/css/complete-guide-grid/#aa-grid-track + * + * @param {Array<{start: number, end: number}>} tracks An array of objects with the start and end + * position in pixels of each grid track. + * @param {number} position The position in pixels. + * @param {string} edge The edge of the track to compare the + * position to. Either 'start' or 'end'. + * + * @return {number} The index of the closest track to the position. 0-based, unlike CSS grid which + * is 1-based. + */ +export function getClosestTrack( tracks, position, edge = 'start' ) { return tracks.reduce( ( closest, track, index ) => Math.abs( track[ edge ] - position ) < From eb732d14887f7c97c8da6b925ea4cb003a65c06b Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 14 Jun 2024 16:03:16 +1000 Subject: [PATCH 04/31] Fix visualiser --- .../src/components/grid/grid-visualizer.js | 11 +++++++++-- .../src/components/grid/use-grid-layout-sync.js | 17 ++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index bb7bf9d1f4bf7..1a57bf3d1dacb 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -48,6 +48,7 @@ function getGridInfo( gridElement ) { numColumns, numRows, numItems, + currentColor: getComputedCSS( gridElement, 'color' ), style: { gridTemplateColumns, gridTemplateRows, @@ -195,6 +196,7 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { } ); setHighlightedRect( null ); } } + color={ gridInfo.currentColor } /> ) ) ) } @@ -209,9 +211,9 @@ function GridVisualizerCell( { onDragEnter, onDragLeave, onDrop, + color, } ) { const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); - const ref = useDropZone( { onDragEnter() { const [ srcClientId ] = getDraggedBlockClientIds(); @@ -231,7 +233,12 @@ function GridVisualizerCell( { } ); return ( -
+
Date: Fri, 14 Jun 2024 16:11:26 +1000 Subject: [PATCH 05/31] fix booboos --- .../block-editor/src/components/grid/use-grid-layout-sync.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 68bd24c301e91..f6555ee362943 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 @@ -40,7 +40,7 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId ); const { columnStart, rowStart, columnSpan, rowSpan } = - attributes.style?.layout; + attributes.style?.layout || {}; if ( ! columnStart || ! rowStart ) { continue; } @@ -58,7 +58,7 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId ); const { columnStart, rowStart, columnSpan, rowSpan } = - attributes.style?.layout; + attributes.style?.layout || {}; if ( columnStart && rowStart ) { continue; } From 73b821ab5f5dda774bba0f2f00f95955ed97abf0 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 14 Jun 2024 16:54:35 +1000 Subject: [PATCH 06/31] Show appender on manual grid --- packages/block-library/src/group/edit.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 09cd1f99dad3e..a763bc95e60d7 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -96,7 +96,6 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { const { type = 'default' } = layout; const layoutSupportEnabled = themeSupportsLayout || type === 'flex' || type === 'grid'; - const isManualGrid = type === 'grid' && !! layout.columnCount; // Hooks. const ref = useRef(); @@ -110,7 +109,7 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { // Default to the regular appender being rendered. let renderAppender; - if ( showPlaceholder || isManualGrid ) { + if ( showPlaceholder ) { // In the placeholder state, ensure the appender is not rendered. // This is needed because `...innerBlocksProps` is used in the placeholder // state so that blocks can dragged onto the placeholder area From 9dff09b2ffef23eac10d4c843249b2129e35d084 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Mon, 17 Jun 2024 18:05:51 +1000 Subject: [PATCH 07/31] Fix row situation when converting from auto to manual --- .../src/components/grid/use-grid-layout-sync.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 f6555ee362943..99b4ff4b8e147 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 @@ -30,12 +30,16 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { useEffect( () => { const updates = {}; - const { columnCount, rowCount } = gridLayout; + const { columnCount, rowCount = 2 } = gridLayout; + const isManualGrid = !! columnCount; if ( isManualGrid ) { const rects = []; - + const minimumNeededRows = Math.max( + Math.ceil( blockOrder.length / columnCount ), + rowCount + ); // Respect the position of blocks that already have a columnStart and rowStart value. for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId ); @@ -65,7 +69,7 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { const [ newColumnStart, newRowStart ] = getFirstEmptyCell( rects, columnCount, - rowCount, + minimumNeededRows, columnSpan, rowSpan ); From eef95b2d77610c06cf792013ebd964a89bb10259 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 18 Jun 2024 13:21:13 +1000 Subject: [PATCH 08/31] Set row attribute --- .../src/components/grid/use-grid-layout-sync.js | 8 ++++++++ 1 file changed, 8 insertions(+) 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 99b4ff4b8e147..f866034a5d885 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 @@ -40,6 +40,14 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { Math.ceil( blockOrder.length / columnCount ), rowCount ); + if ( rowCount !== minimumNeededRows ) { + updates[ gridClientId ] = { + layout: { + ...gridLayout, + rowCount: minimumNeededRows, + }, + }; + } // Respect the position of blocks that already have a columnStart and rowStart value. for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId ); From ef056c193dedee2daa7df8281538df8d442a5668 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Wed, 19 Jun 2024 11:52:07 +1000 Subject: [PATCH 09/31] markup reordering on drag and drop --- .../src/components/grid/grid-visualizer.js | 192 +++++++++++------- 1 file changed, 116 insertions(+), 76 deletions(-) diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index 1a57bf3d1dacb..f418e84bdecb7 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -65,7 +65,7 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); - const { getBlockAttributes } = useSelect( blockEditorStore ); + const { getBlockAttributes, getBlockOrder } = useSelect( blockEditorStore ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); useEffect( () => { @@ -99,6 +99,23 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { }; }, [] ); + const gridBlockOrder = getBlockOrder( clientId ); + + const getBlockPositions = gridBlockOrder.map( ( blockClientId ) => { + const attributes = getBlockAttributes( blockClientId ); + const { columnStart, rowStart } = attributes.style?.layout || {}; + // If flow direction ever becomes settable this will have to change. + return ( rowStart - 1 ) * gridInfo.numColumns + columnStart - 1; + } ); + + const getBlocksBeforeCurrentCell = ( targetIndex ) => { + const blocksBefore = getBlockPositions.filter( + ( position ) => position < targetIndex + ); + return blocksBefore.length; + }; + let index = 0; + return ( { style={ gridInfo.style } > { range( 1, gridInfo.numRows ).map( ( row ) => - range( 1, gridInfo.numColumns ).map( ( column ) => ( - { - const attributes = - getBlockAttributes( srcClientId ); - const rect = new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style?.layout?.columnSpan, - rowSpan: attributes.style?.layout?.rowSpan, - } ); - - const isInBounds = new GridRect( { - columnSpan: gridInfo.numColumns, - rowSpan: gridInfo.numRows, - } ).containsRect( rect ); - if ( ! isInBounds ) { - return false; - } - - const isOverlapping = Array.from( - gridElement.children - ).some( - ( child ) => - child.dataset.block !== srcClientId && - rect.intersectsRect( - getGridItemRect( child ) - ) - ); - if ( isOverlapping ) { - return false; + range( 1, gridInfo.numColumns ).map( ( column ) => { + index++; + return ( + { - const attributes = - getBlockAttributes( srcClientId ); - setHighlightedRect( - new GridRect( { + validateDrag={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + const rect = new GridRect( { columnStart: column, rowStart: row, columnSpan: @@ -166,39 +150,85 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { ?.columnSpan, rowSpan: attributes.style?.layout?.rowSpan, - } ) - ); - } } - onDragLeave={ () => { - // onDragEnter can be called before onDragLeave if the user moves - // their mouse quickly, so only clear the highlight if it was set - // by this cell. - setHighlightedRect( ( prevHighlightedRect ) => - prevHighlightedRect?.columnStart === - column && - prevHighlightedRect?.rowStart === row - ? null - : prevHighlightedRect - ); - } } - onDrop={ ( srcClientId ) => { - const attributes = - getBlockAttributes( srcClientId ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, + } ); + + const isInBounds = new GridRect( { + columnSpan: gridInfo.numColumns, + rowSpan: gridInfo.numRows, + } ).containsRect( rect ); + if ( ! isInBounds ) { + return false; + } + + const isOverlapping = Array.from( + gridElement.children + ).some( + ( child ) => + child.dataset.block !== + srcClientId && + rect.intersectsRect( + getGridItemRect( child ) + ) + ); + if ( isOverlapping ) { + return false; + } + + return true; + } } + onDragEnter={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + setHighlightedRect( + new GridRect( { columnStart: column, rowStart: row, + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout + ?.rowSpan, + } ) + ); + } } + onDragLeave={ () => { + // onDragEnter can be called before onDragLeave if the user moves + // their mouse quickly, so only clear the highlight if it was set + // by this cell. + setHighlightedRect( + ( prevHighlightedRect ) => + prevHighlightedRect?.columnStart === + column && + prevHighlightedRect?.rowStart === + row + ? null + : prevHighlightedRect + ); + } } + onDrop={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, }, - }, - } ); - setHighlightedRect( null ); - } } - color={ gridInfo.currentColor } - /> - ) ) + } ); + setHighlightedRect( null ); + } } + color={ gridInfo.currentColor } + gridClientId={ clientId } + targetIndex={ getBlocksBeforeCurrentCell( + index + ) } + /> + ); + } ) ) }
@@ -212,8 +242,12 @@ function GridVisualizerCell( { onDragLeave, onDrop, color, + gridClientId, + targetIndex, } ) { const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); + const { moveBlocksToPosition } = useDispatch( blockEditorStore ); + const ref = useDropZone( { onDragEnter() { const [ srcClientId ] = getDraggedBlockClientIds(); @@ -228,6 +262,12 @@ function GridVisualizerCell( { const [ srcClientId ] = getDraggedBlockClientIds(); if ( srcClientId && validateDrag( srcClientId ) ) { onDrop( srcClientId ); + moveBlocksToPosition( + [ srcClientId ], + gridClientId, + gridClientId, + targetIndex + ); } }, } ); From ca959da1c2ff0a722bffdceb407f927d9fcfd9ab Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Wed, 19 Jun 2024 17:33:18 +1000 Subject: [PATCH 10/31] Reorder markup when using movers --- .../src/components/grid/grid-item-movers.js | 77 +++++++++++++++---- .../src/components/grid/grid-visualizer.js | 49 ++---------- .../use-get-blocks-before-current-cell.js | 28 +++++++ .../block-editor/src/components/grid/utils.js | 26 +++++++ .../block-editor/src/hooks/layout-child.js | 2 + 5 files changed, 127 insertions(+), 55 deletions(-) create mode 100644 packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js diff --git a/packages/block-editor/src/components/grid/grid-item-movers.js b/packages/block-editor/src/components/grid/grid-item-movers.js index a36105fc6db86..9e7a098f33baa 100644 --- a/packages/block-editor/src/components/grid/grid-item-movers.js +++ b/packages/block-editor/src/components/grid/grid-item-movers.js @@ -4,13 +4,32 @@ import { __ } from '@wordpress/i18n'; import { ToolbarButton } from '@wordpress/components'; import { arrowLeft, arrowUp, arrowDown, arrowRight } from '@wordpress/icons'; +import { useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import BlockControls from '../block-controls'; +import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; +import { getGridInfo } from './utils'; +import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; +import { store as blockEditorStore } from '../../store'; + +export function GridItemMovers( { + layout, + parentLayout, + onChange, + gridClientId, + blockClientId, +} ) { + const { moveBlocksToPosition } = useDispatch( blockEditorStore ); + + const gridInfo = getGridInfo( useBlockElement( gridClientId ) ); + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + gridClientId, + gridInfo + ); -export function GridItemMovers( { layout, parentLayout, onChange } ) { const columnStart = layout?.columnStart ?? 1; const rowStart = layout?.rowStart ?? 1; const columnSpan = layout?.columnSpan ?? 1; @@ -19,47 +38,79 @@ export function GridItemMovers( { layout, parentLayout, onChange } ) { const rowEnd = rowStart + rowSpan - 1; const columnCount = parentLayout?.columnCount; const rowCount = parentLayout?.rowCount; + + const currentBlockIndex = + ( rowStart - 1 ) * gridInfo.numColumns + columnStart - 1; + return ( + onClick={ () => { onChange( { rowStart: rowStart - 1, - } ) - } + } ); + moveBlocksToPosition( + [ blockClientId ], + gridClientId, + gridClientId, + getBlocksBeforeCurrentCell( + currentBlockIndex - gridInfo.numColumns + ) + ); + } } /> = rowCount } - onClick={ () => + onClick={ () => { onChange( { rowStart: rowStart + 1, - } ) - } + } ); + moveBlocksToPosition( + [ blockClientId ], + gridClientId, + gridClientId, + getBlocksBeforeCurrentCell( + currentBlockIndex + gridInfo.numColumns + ) + ); + } } /> + onClick={ () => { onChange( { columnStart: columnStart - 1, - } ) - } + } ); + moveBlocksToPosition( + [ blockClientId ], + gridClientId, + gridClientId, + getBlocksBeforeCurrentCell( currentBlockIndex - 1 ) + ); + } } /> = columnCount } - onClick={ () => + onClick={ () => { onChange( { columnStart: columnStart + 1, - } ) - } + } ); + moveBlocksToPosition( + [ blockClientId ], + gridClientId, + gridClientId, + getBlocksBeforeCurrentCell( currentBlockIndex + 1 ) + ); + } } /> ); diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index f418e84bdecb7..afe5094b57a17 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -15,8 +15,9 @@ import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { getComputedCSS, range, GridRect, getGridItemRect } from './utils'; +import { range, GridRect, getGridItemRect, getGridInfo } from './utils'; import { store as blockEditorStore } from '../../store'; +import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; export function GridVisualizer( { clientId, contentRef } ) { const gridElement = useBlockElement( clientId ); @@ -32,32 +33,6 @@ export function GridVisualizer( { clientId, contentRef } ) { ); } -function getGridInfo( gridElement ) { - const gridTemplateColumns = getComputedCSS( - gridElement, - 'grid-template-columns' - ); - const gridTemplateRows = getComputedCSS( - gridElement, - 'grid-template-rows' - ); - const numColumns = gridTemplateColumns.split( ' ' ).length; - const numRows = gridTemplateRows.split( ' ' ).length; - const numItems = numColumns * numRows; - return { - numColumns, - numRows, - numItems, - currentColor: getComputedCSS( gridElement, 'color' ), - style: { - gridTemplateColumns, - gridTemplateRows, - gap: getComputedCSS( gridElement, 'gap' ), - padding: getComputedCSS( gridElement, 'padding' ), - }, - }; -} - const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( gridElement ) @@ -65,7 +40,7 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); - const { getBlockAttributes, getBlockOrder } = useSelect( blockEditorStore ); + const { getBlockAttributes } = useSelect( blockEditorStore ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); useEffect( () => { @@ -99,21 +74,11 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { }; }, [] ); - const gridBlockOrder = getBlockOrder( clientId ); - - const getBlockPositions = gridBlockOrder.map( ( blockClientId ) => { - const attributes = getBlockAttributes( blockClientId ); - const { columnStart, rowStart } = attributes.style?.layout || {}; - // If flow direction ever becomes settable this will have to change. - return ( rowStart - 1 ) * gridInfo.numColumns + columnStart - 1; - } ); + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + clientId, + gridInfo + ); - const getBlocksBeforeCurrentCell = ( targetIndex ) => { - const blocksBefore = getBlockPositions.filter( - ( position ) => position < targetIndex - ); - return blocksBefore.length; - }; let index = 0; return ( diff --git a/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js b/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js new file mode 100644 index 0000000000000..81c034b725f1c --- /dev/null +++ b/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + +export function useGetBlocksBeforeCurrentCell( clientId, gridInfo ) { + const { getBlockAttributes, getBlockOrder } = useSelect( blockEditorStore ); + const gridBlockOrder = getBlockOrder( clientId ); + + const getBlockPositions = gridBlockOrder.map( ( blockClientId ) => { + const attributes = getBlockAttributes( blockClientId ); + const { columnStart, rowStart } = attributes.style?.layout || {}; + // If flow direction ever becomes settable this will have to change. + return ( rowStart - 1 ) * gridInfo.numColumns + columnStart - 1; + } ); + + return ( targetIndex ) => { + const blocksBefore = getBlockPositions.filter( + ( position ) => position < targetIndex + ); + return blocksBefore.length; + }; +} diff --git a/packages/block-editor/src/components/grid/utils.js b/packages/block-editor/src/components/grid/utils.js index ff02a7bda1e0d..fc012c645f091 100644 --- a/packages/block-editor/src/components/grid/utils.js +++ b/packages/block-editor/src/components/grid/utils.js @@ -150,3 +150,29 @@ export function getGridItemRect( gridItemElement ) { ) ); } + +export function getGridInfo( gridElement ) { + const gridTemplateColumns = getComputedCSS( + gridElement, + 'grid-template-columns' + ); + const gridTemplateRows = getComputedCSS( + gridElement, + 'grid-template-rows' + ); + const numColumns = gridTemplateColumns.split( ' ' ).length; + const numRows = gridTemplateRows.split( ' ' ).length; + const numItems = numColumns * numRows; + return { + numColumns, + numRows, + numItems, + currentColor: getComputedCSS( gridElement, 'color' ), + style: { + gridTemplateColumns, + gridTemplateRows, + gap: getComputedCSS( gridElement, 'gap' ), + padding: getComputedCSS( gridElement, 'padding' ), + }, + }; +} diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index f6f166d9e2a2e..d9499b3c5de1e 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -193,6 +193,8 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { layout={ style?.layout } parentLayout={ parentLayout } onChange={ updateLayout } + gridClientId={ rootClientId } + blockClientId={ clientId } /> ) } From 4ac5e9d2c3cf9689dc60363f2b423d20ec71c3b6 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Thu, 20 Jun 2024 14:44:34 +1000 Subject: [PATCH 11/31] Jump through a few hoops --- .../components/child-layout-control/index.js | 45 ++++++++++++++++++- .../src/components/grid/grid-item-movers.js | 20 ++++----- .../src/components/grid/grid-visualizer.js | 2 +- .../use-get-blocks-before-current-cell.js | 23 ++++++---- 4 files changed, 68 insertions(+), 22 deletions(-) diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index dfc4ee69437f6..b9e3835d82fcf 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -14,6 +14,13 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useEffect } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { useGetBlocksBeforeCurrentCell } from '../grid/use-get-blocks-before-current-cell'; +import { store as blockEditorStore } from '../../store'; function helpText( selfStretch, parentLayout ) { const { orientation = 'horizontal' } = parentLayout; @@ -60,9 +67,19 @@ export default function ChildLayoutControl( { type: parentType, default: { type: defaultParentType = 'default' } = {}, orientation = 'horizontal', + columnCount, } = parentLayout ?? {}; const parentLayoutType = parentType || defaultParentType; - + const gridColumnNumber = parseInt( columnCount, 10 ) || 3; + const rootClientId = useSelect( ( select ) => + select( blockEditorStore ).getBlockRootClientId( panelId ) + ); + const { moveBlocksToPosition } = useDispatch( blockEditorStore ); + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + rootClientId, + gridColumnNumber, + parentLayoutType === 'grid' + ); const hasFlexValue = () => !! selfStretch; const flexResetLabel = orientation === 'horizontal' ? __( 'Width' ) : __( 'Height' ); @@ -220,6 +237,19 @@ export default function ChildLayoutControl( { columnSpan, rowSpan, } ); + const currentBlockIndex = + ( parseInt( rowStart, 10 ) - 1 ) * + gridColumnNumber + + parseInt( value, 10 ) - + 1; + moveBlocksToPosition( + [ panelId ], + rootClientId, + rootClientId, + getBlocksBeforeCurrentCell( + currentBlockIndex + ) + ); } } value={ columnStart } min={ 1 } @@ -244,6 +274,19 @@ export default function ChildLayoutControl( { columnSpan, rowSpan, } ); + const currentBlockIndex = + ( parseInt( value, 10 ) - 1 ) * + gridColumnNumber + + parseInt( columnStart, 10 ) - + 1; + moveBlocksToPosition( + [ panelId ], + rootClientId, + rootClientId, + getBlocksBeforeCurrentCell( + currentBlockIndex + ) + ); } } value={ rowStart } min={ 1 } diff --git a/packages/block-editor/src/components/grid/grid-item-movers.js b/packages/block-editor/src/components/grid/grid-item-movers.js index 9e7a098f33baa..5f18c9e13481a 100644 --- a/packages/block-editor/src/components/grid/grid-item-movers.js +++ b/packages/block-editor/src/components/grid/grid-item-movers.js @@ -10,8 +10,6 @@ import { useDispatch } from '@wordpress/data'; * Internal dependencies */ import BlockControls from '../block-controls'; -import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import { getGridInfo } from './utils'; import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; import { store as blockEditorStore } from '../../store'; @@ -24,12 +22,6 @@ export function GridItemMovers( { } ) { const { moveBlocksToPosition } = useDispatch( blockEditorStore ); - const gridInfo = getGridInfo( useBlockElement( gridClientId ) ); - const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( - gridClientId, - gridInfo - ); - const columnStart = layout?.columnStart ?? 1; const rowStart = layout?.rowStart ?? 1; const columnSpan = layout?.columnSpan ?? 1; @@ -39,8 +31,12 @@ export function GridItemMovers( { const columnCount = parentLayout?.columnCount; const rowCount = parentLayout?.rowCount; - const currentBlockIndex = - ( rowStart - 1 ) * gridInfo.numColumns + columnStart - 1; + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + gridClientId, + columnCount + ); + + const currentBlockIndex = ( rowStart - 1 ) * columnCount + columnStart - 1; return ( @@ -57,7 +53,7 @@ export function GridItemMovers( { gridClientId, gridClientId, getBlocksBeforeCurrentCell( - currentBlockIndex - gridInfo.numColumns + currentBlockIndex - columnCount ) ); } } @@ -75,7 +71,7 @@ export function GridItemMovers( { gridClientId, gridClientId, getBlocksBeforeCurrentCell( - currentBlockIndex + gridInfo.numColumns + currentBlockIndex + columnCount ) ); } } diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index afe5094b57a17..608835671488c 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -76,7 +76,7 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( clientId, - gridInfo + gridInfo.numColumns ); let index = 0; diff --git a/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js b/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js index 81c034b725f1c..ece78fa43a339 100644 --- a/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js +++ b/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js @@ -8,18 +8,25 @@ import { useSelect } from '@wordpress/data'; */ import { store as blockEditorStore } from '../../store'; -export function useGetBlocksBeforeCurrentCell( clientId, gridInfo ) { +export function useGetBlocksBeforeCurrentCell( + clientId, + numColumns, + isGrid = true +) { const { getBlockAttributes, getBlockOrder } = useSelect( blockEditorStore ); - const gridBlockOrder = getBlockOrder( clientId ); - const getBlockPositions = gridBlockOrder.map( ( blockClientId ) => { - const attributes = getBlockAttributes( blockClientId ); - const { columnStart, rowStart } = attributes.style?.layout || {}; - // If flow direction ever becomes settable this will have to change. - return ( rowStart - 1 ) * gridInfo.numColumns + columnStart - 1; - } ); + if ( ! isGrid ) { + return () => null; + } return ( targetIndex ) => { + const gridBlockOrder = getBlockOrder( clientId ); + const getBlockPositions = gridBlockOrder.map( ( blockClientId ) => { + const attributes = getBlockAttributes( blockClientId ); + const { columnStart, rowStart } = attributes.style?.layout || {}; + // If flow direction ever becomes settable this will have to change. + return ( rowStart - 1 ) * numColumns + columnStart - 1; + } ); const blocksBefore = getBlockPositions.filter( ( position ) => position < targetIndex ); From 881af9153e5618bd129ddfbb3221f21855028326 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Thu, 20 Jun 2024 14:53:58 +1000 Subject: [PATCH 12/31] Only show sidebar row/col start controls in manual mode. --- .../components/child-layout-control/index.js | 178 +++++++++--------- 1 file changed, 90 insertions(+), 88 deletions(-) diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index b9e3835d82fcf..8ca83216ef890 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -213,94 +213,96 @@ export default function ChildLayoutControl( { min={ 1 } /> - { window.__experimentalEnableGridInteractivity && ( - // Use Flex with an explicit width on the FlexItem instead of HStack to - // work around an issue in webkit where inputs with a max attribute are - // sized incorrectly. - - - { - onChange( { - columnStart: value, - rowStart, - columnSpan, - rowSpan, - } ); - const currentBlockIndex = - ( parseInt( rowStart, 10 ) - 1 ) * - gridColumnNumber + - parseInt( value, 10 ) - - 1; - moveBlocksToPosition( - [ panelId ], - rootClientId, - rootClientId, - getBlocksBeforeCurrentCell( - currentBlockIndex - ) - ); - } } - value={ columnStart } - min={ 1 } - max={ - parentLayout?.columnCount - ? parentLayout.columnCount - - ( columnSpan ?? 1 ) + - 1 - : undefined - } - /> - - - { - onChange( { - columnStart, - rowStart: value, - columnSpan, - rowSpan, - } ); - const currentBlockIndex = - ( parseInt( value, 10 ) - 1 ) * - gridColumnNumber + - parseInt( columnStart, 10 ) - - 1; - moveBlocksToPosition( - [ panelId ], - rootClientId, - rootClientId, - getBlocksBeforeCurrentCell( - currentBlockIndex - ) - ); - } } - value={ rowStart } - min={ 1 } - max={ - parentLayout?.rowCount - ? parentLayout.rowCount - - ( rowSpan ?? 1 ) + - 1 - : undefined - } - /> - - - ) } + { window.__experimentalEnableGridInteractivity && + columnCount && ( + // Use Flex with an explicit width on the FlexItem instead of HStack to + // work around an issue in webkit where inputs with a max attribute are + // sized incorrectly. + + + { + onChange( { + columnStart: value, + rowStart, + columnSpan, + rowSpan, + } ); + const currentBlockIndex = + ( parseInt( rowStart, 10 ) - + 1 ) * + gridColumnNumber + + parseInt( value, 10 ) - + 1; + moveBlocksToPosition( + [ panelId ], + rootClientId, + rootClientId, + getBlocksBeforeCurrentCell( + currentBlockIndex + ) + ); + } } + value={ columnStart } + min={ 1 } + max={ + gridColumnNumber + ? gridColumnNumber - + ( columnSpan ?? 1 ) + + 1 + : undefined + } + /> + + + { + onChange( { + columnStart, + rowStart: value, + columnSpan, + rowSpan, + } ); + const currentBlockIndex = + ( parseInt( value, 10 ) - 1 ) * + gridColumnNumber + + parseInt( columnStart, 10 ) - + 1; + moveBlocksToPosition( + [ panelId ], + rootClientId, + rootClientId, + getBlocksBeforeCurrentCell( + currentBlockIndex + ) + ); + } } + value={ rowStart } + min={ 1 } + max={ + parentLayout?.rowCount + ? parentLayout.rowCount - + ( rowSpan ?? 1 ) + + 1 + : undefined + } + /> + + + ) } ) } From 5f6e8d78565adbd3e0dd8b96caf1bef91788d965 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Thu, 20 Jun 2024 16:42:15 +1000 Subject: [PATCH 13/31] Fix string concatenation bugs --- .../src/components/grid/grid-item-movers.js | 17 +++++++++++------ .../src/components/grid/grid-visualizer.js | 12 +++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/grid/grid-item-movers.js b/packages/block-editor/src/components/grid/grid-item-movers.js index 5f18c9e13481a..58a84be429a5c 100644 --- a/packages/block-editor/src/components/grid/grid-item-movers.js +++ b/packages/block-editor/src/components/grid/grid-item-movers.js @@ -31,12 +31,17 @@ export function GridItemMovers( { const columnCount = parentLayout?.columnCount; const rowCount = parentLayout?.rowCount; + const columnCountNumber = parseInt( columnCount, 10 ); + const rowStartNumber = parseInt( rowStart, 10 ); + const columnStartNumber = parseInt( columnStart, 10 ); + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( gridClientId, - columnCount + columnCountNumber ); - const currentBlockIndex = ( rowStart - 1 ) * columnCount + columnStart - 1; + const currentBlockIndex = + ( rowStartNumber - 1 ) * columnCountNumber + columnStartNumber - 1; return ( @@ -53,7 +58,7 @@ export function GridItemMovers( { gridClientId, gridClientId, getBlocksBeforeCurrentCell( - currentBlockIndex - columnCount + currentBlockIndex - columnCountNumber ) ); } } @@ -71,7 +76,7 @@ export function GridItemMovers( { gridClientId, gridClientId, getBlocksBeforeCurrentCell( - currentBlockIndex + columnCount + currentBlockIndex + columnCountNumber ) ); } } @@ -82,7 +87,7 @@ export function GridItemMovers( { isDisabled={ columnStart <= 1 } onClick={ () => { onChange( { - columnStart: columnStart - 1, + columnStart: columnStartNumber - 1, } ); moveBlocksToPosition( [ blockClientId ], @@ -98,7 +103,7 @@ export function GridItemMovers( { isDisabled={ columnCount && columnEnd >= columnCount } onClick={ () => { onChange( { - columnStart: columnStart + 1, + columnStart: columnStartNumber + 1, } ); moveBlocksToPosition( [ blockClientId ], diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index 608835671488c..b8573900afe42 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -188,9 +188,10 @@ const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { } } color={ gridInfo.currentColor } gridClientId={ clientId } - targetIndex={ getBlocksBeforeCurrentCell( - index - ) } + index={ index } + getBlocksBeforeCurrentCell={ + getBlocksBeforeCurrentCell + } /> ); } ) @@ -208,7 +209,8 @@ function GridVisualizerCell( { onDrop, color, gridClientId, - targetIndex, + index, + getBlocksBeforeCurrentCell, } ) { const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); const { moveBlocksToPosition } = useDispatch( blockEditorStore ); @@ -231,7 +233,7 @@ function GridVisualizerCell( { [ srcClientId ], gridClientId, gridClientId, - targetIndex + getBlocksBeforeCurrentCell( index ) ); } }, From 249549bcbd56114c03d8b2cc0e10bab753a31248 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 21 Jun 2024 15:59:11 +1000 Subject: [PATCH 14/31] Undo folder rename --- .../components/{grid => grid-visualizer}/grid-item-movers.js | 0 .../{grid => grid-visualizer}/grid-item-resizer.js | 0 .../components/{grid => grid-visualizer}/grid-visualizer.js | 0 .../src/components/{grid => grid-visualizer}/index.js | 0 .../src/components/{grid => grid-visualizer}/style.scss | 0 .../use-get-blocks-before-current-cell.js | 0 .../{grid => grid-visualizer}/use-grid-layout-sync.js | 0 .../src/components/{grid => grid-visualizer}/utils.js | 0 packages/block-editor/src/hooks/layout-child.js | 2 +- packages/block-editor/src/layouts/grid.js | 5 ++++- 10 files changed, 5 insertions(+), 2 deletions(-) rename packages/block-editor/src/components/{grid => grid-visualizer}/grid-item-movers.js (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/grid-item-resizer.js (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/grid-visualizer.js (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/index.js (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/style.scss (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/use-get-blocks-before-current-cell.js (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/use-grid-layout-sync.js (100%) rename packages/block-editor/src/components/{grid => grid-visualizer}/utils.js (100%) diff --git a/packages/block-editor/src/components/grid/grid-item-movers.js b/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js similarity index 100% rename from packages/block-editor/src/components/grid/grid-item-movers.js rename to packages/block-editor/src/components/grid-visualizer/grid-item-movers.js diff --git a/packages/block-editor/src/components/grid/grid-item-resizer.js b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js similarity index 100% rename from packages/block-editor/src/components/grid/grid-item-resizer.js rename to packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js similarity index 100% rename from packages/block-editor/src/components/grid/grid-visualizer.js rename to packages/block-editor/src/components/grid-visualizer/grid-visualizer.js diff --git a/packages/block-editor/src/components/grid/index.js b/packages/block-editor/src/components/grid-visualizer/index.js similarity index 100% rename from packages/block-editor/src/components/grid/index.js rename to packages/block-editor/src/components/grid-visualizer/index.js diff --git a/packages/block-editor/src/components/grid/style.scss b/packages/block-editor/src/components/grid-visualizer/style.scss similarity index 100% rename from packages/block-editor/src/components/grid/style.scss rename to packages/block-editor/src/components/grid-visualizer/style.scss diff --git a/packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js b/packages/block-editor/src/components/grid-visualizer/use-get-blocks-before-current-cell.js similarity index 100% rename from packages/block-editor/src/components/grid/use-get-blocks-before-current-cell.js rename to packages/block-editor/src/components/grid-visualizer/use-get-blocks-before-current-cell.js diff --git a/packages/block-editor/src/components/grid/use-grid-layout-sync.js b/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js similarity index 100% rename from packages/block-editor/src/components/grid/use-grid-layout-sync.js rename to packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js diff --git a/packages/block-editor/src/components/grid/utils.js b/packages/block-editor/src/components/grid-visualizer/utils.js similarity index 100% rename from packages/block-editor/src/components/grid/utils.js rename to packages/block-editor/src/components/grid-visualizer/utils.js diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index d9499b3c5de1e..c86706aba72ba 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -15,7 +15,7 @@ import { GridVisualizer, GridItemResizer, GridItemMovers, -} from '../components/grid'; +} from '../components/grid-visualizer'; function useBlockPropsChildLayoutStyles( { style } ) { const shouldRenderChildLayoutStyles = useSelect( ( select ) => { diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index c7a5f76d4fbc0..d36bcb15e00c6 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -23,7 +23,10 @@ import { appendSelectors, getBlockGapCSS } from './utils'; import { getGapCSSValue } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; import { LAYOUT_DEFINITIONS } from './definitions'; -import { GridVisualizer, useGridLayoutSync } from '../components/grid'; +import { + GridVisualizer, + useGridLayoutSync, +} from '../components/grid-visualizer'; const RANGE_CONTROL_MAX_VALUES = { px: 600, From 86f0c1c2bb84397b8d0eba16572ce83c6643dc97 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 21 Jun 2024 16:25:19 +1000 Subject: [PATCH 15/31] Fix inserters for different grid types --- .../components/child-layout-control/index.js | 2 +- .../grid-visualizer/grid-visualizer.js | 338 ++++++++++-------- .../src/components/inner-blocks/index.js | 2 +- .../block-editor/src/hooks/layout-child.js | 1 + 4 files changed, 186 insertions(+), 157 deletions(-) diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index 8ca83216ef890..3dc4e7b545f12 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -19,7 +19,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import { useGetBlocksBeforeCurrentCell } from '../grid/use-get-blocks-before-current-cell'; +import { useGetBlocksBeforeCurrentCell } from '../grid-visualizer/use-get-blocks-before-current-cell'; import { store as blockEditorStore } from '../../store'; function helpText( selfStretch, parentLayout ) { diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index b8573900afe42..1c18d1a47ff17 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -19,187 +19,215 @@ import { range, GridRect, getGridItemRect, getGridInfo } from './utils'; import { store as blockEditorStore } from '../../store'; import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; -export function GridVisualizer( { clientId, contentRef } ) { +export function GridVisualizer( { clientId, contentRef, parentLayout } ) { const gridElement = useBlockElement( clientId ); if ( ! gridElement ) { return null; } + const isManualGrid = parentLayout?.columnCount; return ( ); } -const GridVisualizerGrid = forwardRef( ( { clientId, gridElement }, ref ) => { - const [ gridInfo, setGridInfo ] = useState( () => - getGridInfo( gridElement ) - ); - const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); - const [ highlightedRect, setHighlightedRect ] = useState( null ); +const GridVisualizerGrid = forwardRef( + ( { clientId, gridElement, isManualGrid }, ref ) => { + const [ gridInfo, setGridInfo ] = useState( () => + getGridInfo( gridElement ) + ); + const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); + const [ highlightedRect, setHighlightedRect ] = useState( null ); - const { getBlockAttributes } = useSelect( blockEditorStore ); - const { updateBlockAttributes } = useDispatch( blockEditorStore ); + const { getBlockAttributes } = useSelect( blockEditorStore ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); - useEffect( () => { - const observers = []; - for ( const element of [ gridElement, ...gridElement.children ] ) { - const observer = new window.ResizeObserver( () => { - setGridInfo( getGridInfo( gridElement ) ); - } ); - observer.observe( element ); - observers.push( observer ); - } - return () => { - for ( const observer of observers ) { - observer.disconnect(); + useEffect( () => { + const observers = []; + for ( const element of [ gridElement, ...gridElement.children ] ) { + const observer = new window.ResizeObserver( () => { + setGridInfo( getGridInfo( gridElement ) ); + } ); + observer.observe( element ); + observers.push( observer ); } - }; - }, [ gridElement ] ); + return () => { + for ( const observer of observers ) { + observer.disconnect(); + } + }; + }, [ gridElement ] ); - useEffect( () => { - function onGlobalDrag() { - setIsDroppingAllowed( true ); - } - function onGlobalDragEnd() { - setIsDroppingAllowed( false ); - } - document.addEventListener( 'drag', onGlobalDrag ); - document.addEventListener( 'dragend', onGlobalDragEnd ); - return () => { - document.removeEventListener( 'drag', onGlobalDrag ); - document.removeEventListener( 'dragend', onGlobalDragEnd ); - }; - }, [] ); + useEffect( () => { + function onGlobalDrag() { + setIsDroppingAllowed( true ); + } + function onGlobalDragEnd() { + setIsDroppingAllowed( false ); + } + document.addEventListener( 'drag', onGlobalDrag ); + document.addEventListener( 'dragend', onGlobalDragEnd ); + return () => { + document.removeEventListener( 'drag', onGlobalDrag ); + document.removeEventListener( 'dragend', onGlobalDragEnd ); + }; + }, [] ); - const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( - clientId, - gridInfo.numColumns - ); + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + clientId, + gridInfo.numColumns + ); - let index = 0; + let index = 0; - return ( - -
- { range( 1, gridInfo.numRows ).map( ( row ) => - range( 1, gridInfo.numColumns ).map( ( column ) => { - index++; - return ( - { - const attributes = - getBlockAttributes( srcClientId ); - const rect = new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style?.layout - ?.columnSpan, - rowSpan: - attributes.style?.layout?.rowSpan, - } ); - - const isInBounds = new GridRect( { - columnSpan: gridInfo.numColumns, - rowSpan: gridInfo.numRows, - } ).containsRect( rect ); - if ( ! isInBounds ) { - return false; - } - - const isOverlapping = Array.from( - gridElement.children - ).some( - ( child ) => - child.dataset.block !== - srcClientId && - rect.intersectsRect( - getGridItemRect( child ) - ) - ); - if ( isOverlapping ) { - return false; - } - - return true; - } } - onDragEnter={ ( srcClientId ) => { - const attributes = - getBlockAttributes( srcClientId ); - setHighlightedRect( - new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style?.layout - ?.columnSpan, - rowSpan: - attributes.style?.layout - ?.rowSpan, - } ) - ); - } } - onDragLeave={ () => { - // onDragEnter can be called before onDragLeave if the user moves - // their mouse quickly, so only clear the highlight if it was set - // by this cell. - setHighlightedRect( - ( prevHighlightedRect ) => - prevHighlightedRect?.columnStart === - column && - prevHighlightedRect?.rowStart === +
+ { isManualGrid && + range( 1, gridInfo.numRows ).map( ( row ) => + range( 1, gridInfo.numColumns ).map( ( column ) => { + index++; + return ( + { - const attributes = - getBlockAttributes( srcClientId ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, + ) ?? false + } + validateDrag={ ( srcClientId ) => { + const attributes = + getBlockAttributes( + srcClientId + ); + const rect = new GridRect( { columnStart: column, rowStart: row, - }, - }, - } ); - setHighlightedRect( null ); + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout + ?.rowSpan, + } ); + + const isInBounds = new GridRect( { + columnSpan: gridInfo.numColumns, + rowSpan: gridInfo.numRows, + } ).containsRect( rect ); + if ( ! isInBounds ) { + return false; + } + + const isOverlapping = Array.from( + gridElement.children + ).some( + ( child ) => + child.dataset.block !== + srcClientId && + rect.intersectsRect( + getGridItemRect( child ) + ) + ); + if ( isOverlapping ) { + return false; + } + + return true; + } } + onDragEnter={ ( srcClientId ) => { + const attributes = + getBlockAttributes( + srcClientId + ); + setHighlightedRect( + new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout + ?.rowSpan, + } ) + ); + } } + onDragLeave={ () => { + // onDragEnter can be called before onDragLeave if the user moves + // their mouse quickly, so only clear the highlight if it was set + // by this cell. + setHighlightedRect( + ( prevHighlightedRect ) => + prevHighlightedRect?.columnStart === + column && + prevHighlightedRect?.rowStart === + row + ? null + : prevHighlightedRect + ); + } } + onDrop={ ( srcClientId ) => { + const attributes = + getBlockAttributes( + srcClientId + ); + updateBlockAttributes( + srcClientId, + { + style: { + ...attributes.style, + layout: { + ...attributes.style + ?.layout, + columnStart: column, + rowStart: row, + }, + }, + } + ); + setHighlightedRect( null ); + } } + color={ gridInfo.currentColor } + gridClientId={ clientId } + index={ index } + getBlocksBeforeCurrentCell={ + getBlocksBeforeCurrentCell + } + /> + ); + } ) + ) } + { ! isManualGrid && + Array.from( { length: gridInfo.numItems }, ( _, i ) => ( +
- ); - } ) - ) } -
- - ); -} ); + ) ) } +
+ + ); + } +); function GridVisualizerCell( { isHighlighted, diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 49ab36499692b..0e337247813c4 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -267,7 +267,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { const ref = useMergeRefs( [ props.ref, - __unstableDisableDropZone || isDropZoneDisabled + __unstableDisableDropZone || isDropZoneDisabled || layout?.columnCount ? null : blockDropZoneRef, ] ); diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index c86706aba72ba..ba7ff6dd73a16 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -179,6 +179,7 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { { allowSizingOnChildren && ( Date: Fri, 21 Jun 2024 16:30:13 +1000 Subject: [PATCH 16/31] Allow overlap --- .../grid-visualizer/grid-visualizer.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index 1c18d1a47ff17..bd2273db06833 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -15,7 +15,7 @@ import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { range, GridRect, getGridItemRect, getGridInfo } from './utils'; +import { range, GridRect, getGridInfo } from './utils'; import { store as blockEditorStore } from '../../store'; import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; @@ -134,20 +134,6 @@ const GridVisualizerGrid = forwardRef( return false; } - const isOverlapping = Array.from( - gridElement.children - ).some( - ( child ) => - child.dataset.block !== - srcClientId && - rect.intersectsRect( - getGridItemRect( child ) - ) - ); - if ( isOverlapping ) { - return false; - } - return true; } } onDragEnter={ ( srcClientId ) => { From 28ab6b95e3bbc7a02b8872da9cfa1b538806ca0e Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 21 Jun 2024 16:34:17 +1000 Subject: [PATCH 17/31] Update file path --- packages/block-editor/src/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 0772ddf0d31e5..cf4683b02c707 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -27,7 +27,7 @@ @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; @import "./components/global-styles/style.scss"; -@import "./components/grid/style.scss"; +@import "./components/grid-visualizer/style.scss"; @import "./components/height-control/style.scss"; @import "./components/image-size-control/style.scss"; @import "./components/inserter-list-item/style.scss"; From 29e97e1732ea3ee5e64e21517d9560f9292e0e8c Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Mon, 24 Jun 2024 11:09:21 +1000 Subject: [PATCH 18/31] Fix undo weirdness --- .../src/components/child-layout-control/index.js | 5 ++++- .../src/components/grid-visualizer/grid-item-movers.js | 7 ++++++- .../src/components/grid-visualizer/grid-visualizer.js | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index 3dc4e7b545f12..8bc354cb8ae0d 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -74,7 +74,8 @@ export default function ChildLayoutControl( { const rootClientId = useSelect( ( select ) => select( blockEditorStore ).getBlockRootClientId( panelId ) ); - const { moveBlocksToPosition } = useDispatch( blockEditorStore ); + const { moveBlocksToPosition, __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( rootClientId, gridColumnNumber, @@ -244,6 +245,7 @@ export default function ChildLayoutControl( { gridColumnNumber + parseInt( value, 10 ) - 1; + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ panelId ], rootClientId, @@ -281,6 +283,7 @@ export default function ChildLayoutControl( { gridColumnNumber + parseInt( columnStart, 10 ) - 1; + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ panelId ], rootClientId, diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js b/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js index 58a84be429a5c..9350e0717049d 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js @@ -20,7 +20,8 @@ export function GridItemMovers( { gridClientId, blockClientId, } ) { - const { moveBlocksToPosition } = useDispatch( blockEditorStore ); + const { moveBlocksToPosition, __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); const columnStart = layout?.columnStart ?? 1; const rowStart = layout?.rowStart ?? 1; @@ -53,6 +54,7 @@ export function GridItemMovers( { onChange( { rowStart: rowStart - 1, } ); + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ blockClientId ], gridClientId, @@ -71,6 +73,7 @@ export function GridItemMovers( { onChange( { rowStart: rowStart + 1, } ); + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ blockClientId ], gridClientId, @@ -89,6 +92,7 @@ export function GridItemMovers( { onChange( { columnStart: columnStartNumber - 1, } ); + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ blockClientId ], gridClientId, @@ -105,6 +109,7 @@ export function GridItemMovers( { onChange( { columnStart: columnStartNumber + 1, } ); + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ blockClientId ], gridClientId, diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index bd2273db06833..eae3a48821537 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -227,7 +227,8 @@ function GridVisualizerCell( { getBlocksBeforeCurrentCell, } ) { const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); - const { moveBlocksToPosition } = useDispatch( blockEditorStore ); + const { moveBlocksToPosition, __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); const ref = useDropZone( { onDragEnter() { @@ -243,6 +244,7 @@ function GridVisualizerCell( { const [ srcClientId ] = getDraggedBlockClientIds(); if ( srcClientId && validateDrag( srcClientId ) ) { onDrop( srcClientId ); + __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ srcClientId ], gridClientId, From e2b2897c0eed9732633a169a25a033870be293c8 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Mon, 24 Jun 2024 11:35:23 +1000 Subject: [PATCH 19/31] Fix col and row start on resize --- .../components/grid-visualizer/grid-item-resizer.js | 12 +++++++++++- packages/block-editor/src/hooks/layout-child.js | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js index 1877fa995016e..35cf27ef7a573 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js @@ -11,9 +11,15 @@ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use- import BlockPopoverCover from '../block-popover/cover'; import { getComputedCSS, getGridTracks, getClosestTrack } from './utils'; -export function GridItemResizer( { clientId, bounds, onChange } ) { +export function GridItemResizer( { + clientId, + bounds, + onChange, + parentLayout, +} ) { const blockElement = useBlockElement( clientId ); const rootBlockElement = blockElement?.parentElement; + const { columnCount } = parentLayout; if ( ! blockElement || ! rootBlockElement ) { return null; @@ -26,6 +32,7 @@ export function GridItemResizer( { clientId, bounds, onChange } ) { blockElement={ blockElement } rootBlockElement={ rootBlockElement } onChange={ onChange } + isManualGrid={ !! columnCount } /> ); } @@ -36,6 +43,7 @@ function GridItemResizerInner( { blockElement, rootBlockElement, onChange, + isManualGrid, } ) { const [ resizeDirection, setResizeDirection ] = useState( null ); const [ enableSide, setEnableSide ] = useState( { @@ -171,6 +179,8 @@ function GridItemResizerInner( { onChange( { columnSpan: columnEnd - columnStart + 1, rowSpan: rowEnd - rowStart + 1, + columnStart: isManualGrid ? columnStart : undefined, + rowStart: isManualGrid ? rowStart : undefined, } ); } } /> diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index ba7ff6dd73a16..d92d1069c2e33 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -187,6 +187,7 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { // Don't allow resizing beyond the grid visualizer. bounds={ resizerBounds } onChange={ updateLayout } + parentLayout={ parentLayout } /> ) } { isManualGrid && window.__experimentalEnableGridInteractivity && ( From b79494da1d5352fdd21359e2337ca6cf0633bafc Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 24 Jun 2024 13:18:31 +1000 Subject: [PATCH 20/31] Put back isDistractionFree check --- .../src/components/grid-visualizer/grid-visualizer.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index eae3a48821537..24305c691e0f0 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -20,10 +20,17 @@ import { store as blockEditorStore } from '../../store'; import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; export function GridVisualizer( { clientId, contentRef, parentLayout } ) { + const isDistractionFree = useSelect( + ( select ) => + select( blockEditorStore ).getSettings().isDistractionFree, + [] + ); const gridElement = useBlockElement( clientId ); - if ( ! gridElement ) { + + if ( isDistractionFree || ! gridElement ) { return null; } + const isManualGrid = parentLayout?.columnCount; return ( Date: Mon, 24 Jun 2024 13:21:40 +1000 Subject: [PATCH 21/31] Use ternary --- .../grid-visualizer/grid-visualizer.js | 228 ++++++++++-------- 1 file changed, 125 insertions(+), 103 deletions(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index 24305c691e0f0..02435b60fa7a2 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -104,118 +104,140 @@ const GridVisualizerGrid = forwardRef( className="block-editor-grid-visualizer__grid" style={ gridInfo.style } > - { isManualGrid && - range( 1, gridInfo.numRows ).map( ( row ) => - range( 1, gridInfo.numColumns ).map( ( column ) => { - index++; - return ( - { - const attributes = - getBlockAttributes( + { isManualGrid + ? range( 1, gridInfo.numRows ).map( ( row ) => + range( 1, gridInfo.numColumns ).map( + ( column ) => { + index++; + return ( + { + const attributes = + getBlockAttributes( + srcClientId + ); + const rect = new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: + attributes.style + ?.layout + ?.columnSpan, + rowSpan: + attributes.style + ?.layout + ?.rowSpan, + } ); - const isInBounds = new GridRect( { - columnSpan: gridInfo.numColumns, - rowSpan: gridInfo.numRows, - } ).containsRect( rect ); - if ( ! isInBounds ) { - return false; - } + const isInBounds = + new GridRect( { + columnSpan: + gridInfo.numColumns, + rowSpan: + gridInfo.numRows, + } ).containsRect( + rect + ); + if ( ! isInBounds ) { + return false; + } - return true; - } } - onDragEnter={ ( srcClientId ) => { - const attributes = - getBlockAttributes( - srcClientId - ); - setHighlightedRect( - new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style?.layout - ?.columnSpan, - rowSpan: - attributes.style?.layout - ?.rowSpan, - } ) - ); - } } - onDragLeave={ () => { - // onDragEnter can be called before onDragLeave if the user moves - // their mouse quickly, so only clear the highlight if it was set - // by this cell. - setHighlightedRect( - ( prevHighlightedRect ) => - prevHighlightedRect?.columnStart === - column && - prevHighlightedRect?.rowStart === - row - ? null - : prevHighlightedRect - ); - } } - onDrop={ ( srcClientId ) => { - const attributes = - getBlockAttributes( + return true; + } } + onDragEnter={ ( srcClientId - ); - updateBlockAttributes( - srcClientId, - { - style: { - ...attributes.style, - layout: { - ...attributes.style - ?.layout, + ) => { + const attributes = + getBlockAttributes( + srcClientId + ); + setHighlightedRect( + new GridRect( { columnStart: column, rowStart: row, - }, - }, + columnSpan: + attributes.style + ?.layout + ?.columnSpan, + rowSpan: + attributes.style + ?.layout + ?.rowSpan, + } ) + ); + } } + onDragLeave={ () => { + // onDragEnter can be called before onDragLeave if the user moves + // their mouse quickly, so only clear the highlight if it was set + // by this cell. + setHighlightedRect( + ( + prevHighlightedRect + ) => + prevHighlightedRect?.columnStart === + column && + prevHighlightedRect?.rowStart === + row + ? null + : prevHighlightedRect + ); + } } + onDrop={ ( srcClientId ) => { + const attributes = + getBlockAttributes( + srcClientId + ); + updateBlockAttributes( + srcClientId, + { + style: { + ...attributes.style, + layout: { + ...attributes + .style + ?.layout, + columnStart: + column, + rowStart: + row, + }, + }, + } + ); + setHighlightedRect( null ); + } } + color={ gridInfo.currentColor } + gridClientId={ clientId } + index={ index } + getBlocksBeforeCurrentCell={ + getBlocksBeforeCurrentCell } - ); - setHighlightedRect( null ); + /> + ); + } + ) + ) + : Array.from( + { length: gridInfo.numItems }, + ( _, i ) => ( +
- ); - } ) - ) } - { ! isManualGrid && - Array.from( { length: gridInfo.numItems }, ( _, i ) => ( -
- ) ) } + ) + ) }
); From 2b2e16a5ec79cce9e7c0f3e5619e5ad724041714 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 24 Jun 2024 13:37:56 +1000 Subject: [PATCH 22/31] Try to escape indent hell --- .../grid-visualizer/grid-visualizer.js | 288 ++++++++---------- 1 file changed, 126 insertions(+), 162 deletions(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index 02435b60fa7a2..58cb3bf9d1ac8 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -50,9 +50,6 @@ const GridVisualizerGrid = forwardRef( const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); - const { getBlockAttributes } = useSelect( blockEditorStore ); - const { updateBlockAttributes } = useDispatch( blockEditorStore ); - useEffect( () => { const observers = []; for ( const element of [ gridElement, ...gridElement.children ] ) { @@ -84,13 +81,6 @@ const GridVisualizerGrid = forwardRef( }; }, [] ); - const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( - clientId, - gridInfo.numColumns - ); - - let index = 0; - return ( range( 1, gridInfo.numColumns ).map( - ( column ) => { - index++; - return ( - { - const attributes = - getBlockAttributes( - srcClientId - ); - const rect = new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style - ?.layout - ?.columnSpan, - rowSpan: - attributes.style - ?.layout - ?.rowSpan, - } ); - - const isInBounds = - new GridRect( { - columnSpan: - gridInfo.numColumns, - rowSpan: - gridInfo.numRows, - } ).containsRect( - rect - ); - if ( ! isInBounds ) { - return false; - } - - return true; - } } - onDragEnter={ ( - srcClientId - ) => { - const attributes = - getBlockAttributes( - srcClientId - ); - setHighlightedRect( - new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style - ?.layout - ?.columnSpan, - rowSpan: - attributes.style - ?.layout - ?.rowSpan, - } ) - ); - } } - onDragLeave={ () => { - // onDragEnter can be called before onDragLeave if the user moves - // their mouse quickly, so only clear the highlight if it was set - // by this cell. - setHighlightedRect( - ( - prevHighlightedRect - ) => - prevHighlightedRect?.columnStart === - column && - prevHighlightedRect?.rowStart === - row - ? null - : prevHighlightedRect - ); - } } - onDrop={ ( srcClientId ) => { - const attributes = - getBlockAttributes( - srcClientId - ); - updateBlockAttributes( - srcClientId, - { - style: { - ...attributes.style, - layout: { - ...attributes - .style - ?.layout, - columnStart: - column, - rowStart: - row, - }, - }, - } - ); - setHighlightedRect( null ); - } } - color={ gridInfo.currentColor } + ( column ) => ( + + - ); - } + + ) ) ) : Array.from( { length: gridInfo.numItems }, ( _, i ) => ( -
) ) } @@ -244,22 +133,120 @@ const GridVisualizerGrid = forwardRef( } ); -function GridVisualizerCell( { - isHighlighted, +function GridVisualizerCell( { color, children } ) { + return ( +
+ { children } +
+ ); +} + +function GridVisualizerDropZone( { + column, + row, + gridClientId, + gridInfo, + highlightedRect, + setHighlightedRect, +} ) { + const { getBlockAttributes } = useSelect( blockEditorStore ); + const { + updateBlockAttributes, + moveBlocksToPosition, + __unstableMarkNextChangeAsNotPersistent, + } = useDispatch( blockEditorStore ); + + const isHighlighted = highlightedRect?.contains( column, row ) ?? false; + const index = row * gridInfo.numColumns + column; + + const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + gridClientId, + gridInfo.numColumns + ); + + const ref = useDropZoneWithValidation( { + validateDrag( srcClientId ) { + const attributes = getBlockAttributes( srcClientId ); + const rect = new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: attributes.style?.layout?.columnSpan, + rowSpan: attributes.style?.layout?.rowSpan, + } ); + const isInBounds = new GridRect( { + columnSpan: gridInfo.numColumns, + rowSpan: gridInfo.numRows, + } ).containsRect( rect ); + return isInBounds; + }, + onDragEnter( srcClientId ) { + const attributes = getBlockAttributes( srcClientId ); + setHighlightedRect( + new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: attributes.style?.layout?.columnSpan, + rowSpan: attributes.style?.layout?.rowSpan, + } ) + ); + }, + onDragLeave() { + // onDragEnter can be called before onDragLeave if the user moves + // their mouse quickly, so only clear the highlight if it was set + // by this cell. + setHighlightedRect( ( prevHighlightedRect ) => + prevHighlightedRect?.columnStart === column && + prevHighlightedRect?.rowStart === row + ? null + : prevHighlightedRect + ); + }, + onDrop( srcClientId ) { + setHighlightedRect( null ); + const attributes = getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, + }, + } ); + __unstableMarkNextChangeAsNotPersistent(); + moveBlocksToPosition( + [ srcClientId ], + gridClientId, + gridClientId, + getBlocksBeforeCurrentCell( index ) + ); + }, + } ); + + return ( +
+ ); +} + +function useDropZoneWithValidation( { validateDrag, onDragEnter, onDragLeave, onDrop, - color, - gridClientId, - index, - getBlocksBeforeCurrentCell, } ) { const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); - const { moveBlocksToPosition, __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); - - const ref = useDropZone( { + return useDropZone( { onDragEnter() { const [ srcClientId ] = getDraggedBlockClientIds(); if ( srcClientId && validateDrag( srcClientId ) ) { @@ -273,30 +260,7 @@ function GridVisualizerCell( { const [ srcClientId ] = getDraggedBlockClientIds(); if ( srcClientId && validateDrag( srcClientId ) ) { onDrop( srcClientId ); - __unstableMarkNextChangeAsNotPersistent(); - moveBlocksToPosition( - [ srcClientId ], - gridClientId, - gridClientId, - getBlocksBeforeCurrentCell( index ) - ); } }, } ); - - return ( -
-
-
- ); } From 9621ddb664b33c8f5c346b60dc85f430bb92c499 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Mon, 24 Jun 2024 16:31:51 +1000 Subject: [PATCH 23/31] Fix things for non-experimental mode --- .../src/components/grid-visualizer/grid-item-resizer.js | 4 +++- .../src/components/grid-visualizer/grid-visualizer.js | 4 +++- packages/block-editor/src/components/inner-blocks/index.js | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js index 35cf27ef7a573..a5847d852e0a9 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js @@ -32,7 +32,9 @@ export function GridItemResizer( { blockElement={ blockElement } rootBlockElement={ rootBlockElement } onChange={ onChange } - isManualGrid={ !! columnCount } + isManualGrid={ + !! columnCount && window.__experimentalEnableGridInteractivity + } /> ); } diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index 58cb3bf9d1ac8..78a7a91b3b33f 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -31,7 +31,9 @@ export function GridVisualizer( { clientId, contentRef, parentLayout } ) { return null; } - const isManualGrid = parentLayout?.columnCount; + const isManualGrid = + parentLayout?.columnCount && + window.__experimentalEnableGridInteractivity; return ( Date: Mon, 24 Jun 2024 17:09:52 +1000 Subject: [PATCH 24/31] Fix block movers when experiment disabled --- packages/block-editor/src/components/block-mover/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index f478135c71850..0f2507418f566 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -62,7 +62,10 @@ function BlockMover( { orientation: getBlockListSettings( _rootClientId )?.orientation, // TODO: Doesn't feel great to couple BlockMover and grid layouts. // TODO: Can we use useLayout() instead? - isManualGrid: layout.type === 'grid' && !! layout.columnCount, + isManualGrid: + layout.type === 'grid' && + !! layout.columnCount && + window.__experimentalEnableGridInteractivity, }; }, [ clientIds ] From f1141af0d18a6b8a5d1bf9d9dd98f570ee8f9f53 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 25 Jun 2024 11:03:35 +1000 Subject: [PATCH 25/31] Refactor useGetBlocksBeforeCurrentCell to useGetNumberOfBlocksBeforeCell for clarity, and split up ChildLayoutControl --- .../components/child-layout-control/index.js | 421 +++++++++--------- .../grid-visualizer/grid-item-movers.js | 27 +- .../grid-visualizer/grid-visualizer.js | 11 +- .../use-get-blocks-before-current-cell.js | 35 -- .../use-get-number-of-blocks-before-cell.js | 30 ++ 5 files changed, 270 insertions(+), 254 deletions(-) delete mode 100644 packages/block-editor/src/components/grid-visualizer/use-get-blocks-before-current-cell.js create mode 100644 packages/block-editor/src/components/grid-visualizer/use-get-number-of-blocks-before-cell.js diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index 8bc354cb8ae0d..b27d6cacd0697 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -19,7 +19,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import { useGetBlocksBeforeCurrentCell } from '../grid-visualizer/use-get-blocks-before-current-cell'; +import { useGetNumberOfBlocksBeforeCell } from '../grid-visualizer/use-get-number-of-blocks-before-cell'; import { store as blockEditorStore } from '../../store'; function helpText( selfStretch, parentLayout ) { @@ -55,32 +55,46 @@ export default function ChildLayoutControl( { isShownByDefault, panelId, } ) { - const { - selfStretch, - flexSize, - columnStart, - rowStart, - columnSpan, - rowSpan, - } = childLayout; const { type: parentType, default: { type: defaultParentType = 'default' } = {}, - orientation = 'horizontal', - columnCount, } = parentLayout ?? {}; const parentLayoutType = parentType || defaultParentType; - const gridColumnNumber = parseInt( columnCount, 10 ) || 3; - const rootClientId = useSelect( ( select ) => - select( blockEditorStore ).getBlockRootClientId( panelId ) - ); - const { moveBlocksToPosition, __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); - const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( - rootClientId, - gridColumnNumber, - parentLayoutType === 'grid' - ); + + if ( parentLayoutType === 'flex' ) { + return ( + + ); + } else if ( parentLayoutType === 'grid' ) { + return ( + + ); + } + + return null; +} + +function FlexControls( { + childLayout, + onChange, + parentLayout, + isShownByDefault, + panelId, +} ) { + const { selfStretch, flexSize } = childLayout; + const { orientation = 'horizontal' } = parentLayout ?? {}; const hasFlexValue = () => !! selfStretch; const flexResetLabel = orientation === 'horizontal' ? __( 'Width' ) : __( 'Height' ); @@ -91,6 +105,96 @@ export default function ChildLayoutControl( { } ); }; + useEffect( () => { + if ( selfStretch === 'fixed' && ! flexSize ) { + onChange( { + ...childLayout, + selfStretch: 'fit', + } ); + } + }, [] ); + + return ( + + { + const newFlexSize = value !== 'fixed' ? null : flexSize; + onChange( { + selfStretch: value, + flexSize: newFlexSize, + } ); + } } + isBlock + > + + + + + { selfStretch === 'fixed' && ( + { + onChange( { + selfStretch, + flexSize: value, + } ); + } } + value={ flexSize } + /> + ) } + + ); +} + +export function childLayoutOrientation( parentLayout ) { + const { orientation = 'horizontal' } = parentLayout; + return orientation === 'horizontal' ? __( 'Width' ) : __( 'Height' ); +} + +function GridControls( { + childLayout, + onChange, + parentLayout, + isShownByDefault, + panelId, +} ) { + const { columnStart, rowStart, columnSpan, rowSpan } = childLayout; + const { columnCount } = parentLayout ?? {}; + const gridColumnNumber = parseInt( columnCount, 10 ) || 3; + const rootClientId = useSelect( ( select ) => + select( blockEditorStore ).getBlockRootClientId( panelId ) + ); + const { moveBlocksToPosition, __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); + const getNumberOfBlocksBeforeCell = useGetNumberOfBlocksBeforeCell( + rootClientId, + gridColumnNumber + ); const hasStartValue = () => !! columnStart || !! rowStart; const hasSpanValue = () => !! columnSpan || !! rowSpan; const resetGridStarts = () => { @@ -106,214 +210,127 @@ export default function ChildLayoutControl( { } ); }; - useEffect( () => { - if ( selfStretch === 'fixed' && ! flexSize ) { - onChange( { - ...childLayout, - selfStretch: 'fit', - } ); - } - }, [] ); - return ( <> - { parentLayoutType === 'flex' && ( - + { + onChange( { + columnStart, + rowStart, + rowSpan, + columnSpan: value, + } ); + } } + value={ columnSpan } + min={ 1 } + /> + { + onChange( { + columnStart, + rowStart, + columnSpan, + rowSpan: value, + } ); + } } + value={ rowSpan } + min={ 1 } + /> + + { window.__experimentalEnableGridInteractivity && columnCount && ( + // Use Flex with an explicit width on the FlexItem instead of HStack to + // work around an issue in webkit where inputs with a max attribute are + // sized incorrectly. + - { - const newFlexSize = - value !== 'fixed' ? null : flexSize; - onChange( { - selfStretch: value, - flexSize: newFlexSize, - } ); - } } - isBlock - > - - - - - { selfStretch === 'fixed' && ( - { - onChange( { - selfStretch, - flexSize: value, - } ); - } } - value={ flexSize } - /> - ) } - - ) } - { parentLayoutType === 'grid' && ( - <> - + { onChange( { - columnStart, + columnStart: value, rowStart, + columnSpan, rowSpan, - columnSpan: value, } ); + __unstableMarkNextChangeAsNotPersistent(); + moveBlocksToPosition( + [ panelId ], + rootClientId, + rootClientId, + getNumberOfBlocksBeforeCell( + value, + rowStart + ) + ); } } - value={ columnSpan } + value={ columnStart } min={ 1 } + max={ + gridColumnNumber + ? gridColumnNumber - ( columnSpan ?? 1 ) + 1 + : undefined + } /> + + { onChange( { columnStart, - rowStart, + rowStart: value, columnSpan, - rowSpan: value, + rowSpan, } ); + __unstableMarkNextChangeAsNotPersistent(); + moveBlocksToPosition( + [ panelId ], + rootClientId, + rootClientId, + getNumberOfBlocksBeforeCell( + columnStart, + value + ) + ); } } - value={ rowSpan } + value={ rowStart } min={ 1 } + max={ + parentLayout?.rowCount + ? parentLayout.rowCount - + ( rowSpan ?? 1 ) + + 1 + : undefined + } /> - - { window.__experimentalEnableGridInteractivity && - columnCount && ( - // Use Flex with an explicit width on the FlexItem instead of HStack to - // work around an issue in webkit where inputs with a max attribute are - // sized incorrectly. - - - { - onChange( { - columnStart: value, - rowStart, - columnSpan, - rowSpan, - } ); - const currentBlockIndex = - ( parseInt( rowStart, 10 ) - - 1 ) * - gridColumnNumber + - parseInt( value, 10 ) - - 1; - __unstableMarkNextChangeAsNotPersistent(); - moveBlocksToPosition( - [ panelId ], - rootClientId, - rootClientId, - getBlocksBeforeCurrentCell( - currentBlockIndex - ) - ); - } } - value={ columnStart } - min={ 1 } - max={ - gridColumnNumber - ? gridColumnNumber - - ( columnSpan ?? 1 ) + - 1 - : undefined - } - /> - - - { - onChange( { - columnStart, - rowStart: value, - columnSpan, - rowSpan, - } ); - const currentBlockIndex = - ( parseInt( value, 10 ) - 1 ) * - gridColumnNumber + - parseInt( columnStart, 10 ) - - 1; - __unstableMarkNextChangeAsNotPersistent(); - moveBlocksToPosition( - [ panelId ], - rootClientId, - rootClientId, - getBlocksBeforeCurrentCell( - currentBlockIndex - ) - ); - } } - value={ rowStart } - min={ 1 } - max={ - parentLayout?.rowCount - ? parentLayout.rowCount - - ( rowSpan ?? 1 ) + - 1 - : undefined - } - /> - - - ) } - + + ) } ); } - -export function childLayoutOrientation( parentLayout ) { - const { orientation = 'horizontal' } = parentLayout; - - return orientation === 'horizontal' ? __( 'Width' ) : __( 'Height' ); -} diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js b/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js index 9350e0717049d..4f1d3853568fd 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-item-movers.js @@ -10,7 +10,7 @@ import { useDispatch } from '@wordpress/data'; * Internal dependencies */ import BlockControls from '../block-controls'; -import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; +import { useGetNumberOfBlocksBeforeCell } from './use-get-number-of-blocks-before-cell'; import { store as blockEditorStore } from '../../store'; export function GridItemMovers( { @@ -36,14 +36,11 @@ export function GridItemMovers( { const rowStartNumber = parseInt( rowStart, 10 ); const columnStartNumber = parseInt( columnStart, 10 ); - const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + const getNumberOfBlocksBeforeCell = useGetNumberOfBlocksBeforeCell( gridClientId, columnCountNumber ); - const currentBlockIndex = - ( rowStartNumber - 1 ) * columnCountNumber + columnStartNumber - 1; - return ( @@ -114,7 +116,10 @@ export function GridItemMovers( { [ blockClientId ], gridClientId, gridClientId, - getBlocksBeforeCurrentCell( currentBlockIndex + 1 ) + getNumberOfBlocksBeforeCell( + columnStartNumber + 1, + rowStartNumber + ) ); } } /> diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index 78a7a91b3b33f..4e559e15fca32 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -17,7 +17,7 @@ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use- import BlockPopoverCover from '../block-popover/cover'; import { range, GridRect, getGridInfo } from './utils'; import { store as blockEditorStore } from '../../store'; -import { useGetBlocksBeforeCurrentCell } from './use-get-blocks-before-current-cell'; +import { useGetNumberOfBlocksBeforeCell } from './use-get-number-of-blocks-before-cell'; export function GridVisualizer( { clientId, contentRef, parentLayout } ) { const isDistractionFree = useSelect( @@ -163,10 +163,7 @@ function GridVisualizerDropZone( { __unstableMarkNextChangeAsNotPersistent, } = useDispatch( blockEditorStore ); - const isHighlighted = highlightedRect?.contains( column, row ) ?? false; - const index = row * gridInfo.numColumns + column; - - const getBlocksBeforeCurrentCell = useGetBlocksBeforeCurrentCell( + const getNumberOfBlocksBeforeCell = useGetNumberOfBlocksBeforeCell( gridClientId, gridInfo.numColumns ); @@ -226,11 +223,13 @@ function GridVisualizerDropZone( { [ srcClientId ], gridClientId, gridClientId, - getBlocksBeforeCurrentCell( index ) + getNumberOfBlocksBeforeCell( column, row ) ); }, } ); + const isHighlighted = highlightedRect?.contains( column, row ) ?? false; + return (
null; - } - - return ( targetIndex ) => { - const gridBlockOrder = getBlockOrder( clientId ); - const getBlockPositions = gridBlockOrder.map( ( blockClientId ) => { - const attributes = getBlockAttributes( blockClientId ); - const { columnStart, rowStart } = attributes.style?.layout || {}; - // If flow direction ever becomes settable this will have to change. - return ( rowStart - 1 ) * numColumns + columnStart - 1; - } ); - const blocksBefore = getBlockPositions.filter( - ( position ) => position < targetIndex - ); - return blocksBefore.length; - }; -} diff --git a/packages/block-editor/src/components/grid-visualizer/use-get-number-of-blocks-before-cell.js b/packages/block-editor/src/components/grid-visualizer/use-get-number-of-blocks-before-cell.js new file mode 100644 index 0000000000000..11e991c432080 --- /dev/null +++ b/packages/block-editor/src/components/grid-visualizer/use-get-number-of-blocks-before-cell.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; + +export function useGetNumberOfBlocksBeforeCell( gridClientId, numColumns ) { + const { getBlockOrder, getBlockAttributes } = useSelect( blockEditorStore ); + + const getNumberOfBlocksBeforeCell = ( column, row ) => { + const targetIndex = ( row - 1 ) * numColumns + column - 1; + + let count = 0; + for ( const clientId of getBlockOrder( gridClientId ) ) { + const { columnStart, rowStart } = + getBlockAttributes( clientId ).style?.layout ?? {}; + const cellIndex = ( rowStart - 1 ) * numColumns + columnStart - 1; + if ( cellIndex < targetIndex ) { + count++; + } + } + return count; + }; + + return getNumberOfBlocksBeforeCell; +} From 3455e3cbc19e6dc66d3e1633a08f2efade1d3d10 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 25 Jun 2024 11:14:21 +1000 Subject: [PATCH 26/31] remove todo comment Co-authored-by: Robert Anderson --- packages/block-editor/src/components/block-mover/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index 0f2507418f566..6d4e461e6416b 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -60,8 +60,6 @@ function BlockMover( { isFirst: firstIndex === 0, isLast: lastIndex === blockOrder.length - 1, orientation: getBlockListSettings( _rootClientId )?.orientation, - // TODO: Doesn't feel great to couple BlockMover and grid layouts. - // TODO: Can we use useLayout() instead? isManualGrid: layout.type === 'grid' && !! layout.columnCount && From 51de6e8855e851dab3c254641492800290b9c5bc Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 25 Jun 2024 12:46:42 +1000 Subject: [PATCH 27/31] Tidy useGridLayoutSync --- .../grid-visualizer/use-grid-layout-sync.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js b/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js index f866034a5d885..8b4a57a178d46 100644 --- a/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js +++ b/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js @@ -31,16 +31,14 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { const updates = {}; const { columnCount, rowCount = 2 } = gridLayout; - const isManualGrid = !! columnCount; if ( isManualGrid ) { - const rects = []; - const minimumNeededRows = Math.max( - Math.ceil( blockOrder.length / columnCount ), - rowCount + // Ensure there's enough rows to fit all blocks. + const minimumNeededRows = Math.ceil( + blockOrder.length / columnCount ); - if ( rowCount !== minimumNeededRows ) { + if ( rowCount < minimumNeededRows ) { updates[ gridClientId ] = { layout: { ...gridLayout, @@ -48,6 +46,9 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { }, }; } + + const rects = []; + // Respect the position of blocks that already have a columnStart and rowStart value. for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId ); @@ -119,9 +120,6 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { } if ( Object.keys( updates ).length ) { - // TODO: remove this console log - // eslint-disable-next-line no-console - console.log( 'useGridLayoutSync updates', updates ); __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes( Object.keys( updates ), @@ -130,10 +128,13 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { ); } }, [ - __unstableMarkNextChangeAsNotPersistent, + // Actual deps to sync: + gridClientId, + gridLayout, blockOrder, + // Needed for linter: + __unstableMarkNextChangeAsNotPersistent, getBlockAttributes, - gridLayout, updateBlockAttributes, ] ); } From f1f82b2c4c88f0bfe9e03117a84468df875e6293 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 25 Jun 2024 12:49:03 +1000 Subject: [PATCH 28/31] Fix class name typo --- .../src/components/grid-visualizer/grid-visualizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js index 4e559e15fca32..5e639615a062c 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -138,7 +138,7 @@ const GridVisualizerGrid = forwardRef( function GridVisualizerCell( { color, children } ) { return (
Date: Tue, 25 Jun 2024 13:23:33 +1000 Subject: [PATCH 29/31] Fix toggle label --- packages/block-editor/src/layouts/grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index d36bcb15e00c6..6815b82269185 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -337,7 +337,7 @@ function GridLayoutTypeControl( { layout, onChange } ) { onChange={ onChangeType } isBlock help={ - isManual + isManual === 'manual' ? __( 'Grid items can be manually placed in any position on the grid.' ) From c80ce4d587b0875487f5e524842525ba78269772 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 25 Jun 2024 15:22:56 +1000 Subject: [PATCH 30/31] Bring back range control when experiment is disabled --- packages/block-editor/src/layouts/grid.js | 48 +++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 6815b82269185..6a42d6898697f 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -275,23 +275,39 @@ function GridLayoutColumnsAndRowsControl( { label={ __( 'Columns' ) } /> - { allowSizingOnChildren && - window.__experimentalEnableGridInteractivity && ( - - { - onChange( { - ...layout, - rowCount: value, - } ); - } } - value={ rowCount } - min={ 1 } - label={ __( 'Rows' ) } - /> - + + + { window.__experimentalEnableGridInteractivity && + allowSizingOnChildren ? ( + { + onChange( { + ...layout, + rowCount: value, + } ); + } } + value={ rowCount } + min={ 1 } + label={ __( 'Rows' ) } + /> + ) : ( + + onChange( { + ...layout, + columnCount: value, + } ) + } + min={ 1 } + max={ 16 } + withInputField={ false } + label={ __( 'Columns' ) } + hideLabelFromVision + /> ) } + From d982fdf43fdb0a2581ce0e8ee5ec6835bdc06d68 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 25 Jun 2024 15:45:58 +1000 Subject: [PATCH 31/31] Fix min rows needed when items span multiple cells --- .../grid-visualizer/use-grid-layout-sync.js | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js b/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js index 8b4a57a178d46..6a3a05e52fcb9 100644 --- a/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js +++ b/packages/block-editor/src/components/grid-visualizer/use-grid-layout-sync.js @@ -34,26 +34,19 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { const isManualGrid = !! columnCount; if ( isManualGrid ) { - // Ensure there's enough rows to fit all blocks. - const minimumNeededRows = Math.ceil( - blockOrder.length / columnCount - ); - if ( rowCount < minimumNeededRows ) { - updates[ gridClientId ] = { - layout: { - ...gridLayout, - rowCount: minimumNeededRows, - }, - }; - } - const rects = []; + let cellsTaken = 0; // 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, rowSpan } = - attributes.style?.layout || {}; + const { + columnStart, + rowStart, + columnSpan = 1, + rowSpan = 1, + } = attributes.style?.layout || {}; + cellsTaken += columnSpan * rowSpan; if ( ! columnStart || ! rowStart ) { continue; } @@ -67,6 +60,17 @@ export function useGridLayoutSync( { clientId: gridClientId } ) { ); } + // Ensure there's enough rows to fit all blocks. + const minimumNeededRows = Math.ceil( cellsTaken / columnCount ); + if ( rowCount < minimumNeededRows ) { + updates[ gridClientId ] = { + layout: { + ...gridLayout, + rowCount: minimumNeededRows, + }, + }; + } + // When in manual mode, ensure that every block has a columnStart and rowStart value. for ( const clientId of blockOrder ) { const attributes = getBlockAttributes( clientId );