From 0bd9683d8e67271a2dfa965ec6c5746c8751324a Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 29 Feb 2024 14:49:52 +1100 Subject: [PATCH 01/32] Use DropZones in GridVisualizer to set positions of grid items --- .../grid-visualizer/grid-visualizer.js | 138 +++++++++++++----- .../src/components/grid-visualizer/style.scss | 15 +- 2 files changed, 112 insertions(+), 41 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 2ca65eb6722e4..769171aa1213a 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -1,7 +1,14 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { useState, useEffect } from '@wordpress/element'; +import { DropZone } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; /** * Internal dependencies @@ -9,6 +16,8 @@ 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 { parseDropEvent } from '../use-on-block-drop'; +import { store as blockEditorStore } from '../../store'; export function GridVisualizer( { clientId } ) { const blockElement = useBlockElement( clientId ); @@ -16,20 +25,48 @@ export function GridVisualizer( { clientId } ) { return null; } return ( - - - + blockElement={ blockElement } + /> + ); +} + +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 { + numColumns, + numRows, + numItems, + style: { + gridTemplateColumns, + gridTemplateRows, + gap: getComputedCSS( blockElement, 'gap' ), + padding: getComputedCSS( blockElement, 'padding' ), + }, + }; +} + +function range( start, length ) { + return Array.from( { length }, ( _, i ) => start + i ); } -function GridVisualizerGrid( { blockElement } ) { +function GridVisualizerGrid( { clientId, blockElement } ) { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( blockElement ) ); + const [ isDisabled, setIsDisabled ] = useState( true ); + useEffect( () => { const observers = []; for ( const element of [ blockElement, ...blockElement.children ] ) { @@ -45,37 +82,70 @@ function GridVisualizerGrid( { blockElement } ) { } }; }, [ blockElement ] ); + + useEffect( () => { + function onGlobalDrag() { + setIsDisabled( false ); + } + + function onGlobalDragEnd() { + setIsDisabled( true ); + } + document.addEventListener( 'drag', onGlobalDrag ); + document.addEventListener( 'dragend', onGlobalDragEnd ); + return () => { + document.removeEventListener( 'drag', onGlobalDrag ); + document.removeEventListener( 'dragend', onGlobalDragEnd ); + }; + }, [] ); + return ( -
- { Array.from( { length: gridInfo.numItems }, ( _, i ) => ( -
- ) ) } -
+
+ { range( 1, gridInfo.numRows ).map( ( row ) => + range( 1, gridInfo.numColumns ).map( ( column ) => ( + + ) ) + ) } +
+ ); } -function getGridInfo( blockElement ) { - const gridTemplateColumns = getComputedCSS( - blockElement, - 'grid-template-columns' - ); - const gridTemplateRows = getComputedCSS( - blockElement, - 'grid-template-rows' +function GridVisualizerCell( { column, row } ) { + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + + function onDrop( event ) { + const { srcClientIds } = parseDropEvent( event ); + if ( ! srcClientIds?.length ) { + return; + } + updateBlockAttributes( srcClientIds, { + style: { + layout: { + columnStart: column, + rowStart: row, + }, + }, + } ); + } + + return ( +
+ +
); - const numColumns = gridTemplateColumns.split( ' ' ).length; - const numRows = gridTemplateRows.split( ' ' ).length; - const numItems = numColumns * numRows; - return { - numItems, - 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 index 45140e59c7af9..ba6d13b19122a 100644 --- a/packages/block-editor/src/components/grid-visualizer/style.scss +++ b/packages/block-editor/src/components/grid-visualizer/style.scss @@ -1,19 +1,20 @@ -// 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 { // These rules need extra specificity. + z-index: z-index(".block-editor-grid-visualizer"); -.block-editor-grid-visualizer .components-popover__content * { - pointer-events: none !important; + &.is-disabled .components-popover__content * { + pointer-events: none; + } + } } .block-editor-grid-visualizer__grid { display: grid; } -.block-editor-grid-visualizer__item { +.block-editor-grid-visualizer__cell { border: $border-width dashed $gray-300; + position: relative; } .block-editor-grid-item-resizer { From 4be75afeb9d68306fc7483f1fa9aadeee1b30bf9 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 29 Feb 2024 15:19:12 +1100 Subject: [PATCH 02/32] Remove text and icon from drop zone --- .../grid-visualizer/grid-visualizer.js | 48 ++++++++++++------- .../src/components/grid-visualizer/style.scss | 4 ++ 2 files changed, 35 insertions(+), 17 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 769171aa1213a..03b40292a2d61 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -7,8 +7,8 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useState, useEffect } from '@wordpress/element'; -import { DropZone } from '@wordpress/components'; import { useDispatch } from '@wordpress/data'; +import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; /** * Internal dependencies @@ -126,26 +126,40 @@ function GridVisualizerGrid( { clientId, blockElement } ) { } function GridVisualizerCell( { column, row } ) { + const [ isDraggingOver, setIsDraggingOver ] = useState( false ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); - function onDrop( event ) { - const { srcClientIds } = parseDropEvent( event ); - if ( ! srcClientIds?.length ) { - return; - } - updateBlockAttributes( srcClientIds, { - style: { - layout: { - columnStart: column, - rowStart: row, + const ref = useDropZone( { + onDragEnter() { + setIsDraggingOver( true ); + }, + onDragLeave() { + setIsDraggingOver( false ); + }, + onDrop( event ) { + setIsDraggingOver( false ); + const { srcClientIds } = parseDropEvent( event ); + if ( ! srcClientIds?.length ) { + return; + } + updateBlockAttributes( srcClientIds, { + style: { + layout: { + columnStart: column, + rowStart: row, + }, }, - }, - } ); - } + } ); + console.log( srcClientIds, column, row ); + }, + } ); return ( -
- -
+
); } diff --git a/packages/block-editor/src/components/grid-visualizer/style.scss b/packages/block-editor/src/components/grid-visualizer/style.scss index ba6d13b19122a..34a6473f3b979 100644 --- a/packages/block-editor/src/components/grid-visualizer/style.scss +++ b/packages/block-editor/src/components/grid-visualizer/style.scss @@ -15,6 +15,10 @@ .block-editor-grid-visualizer__cell { border: $border-width dashed $gray-300; position: relative; + + &.is-dragging-over { + background: var(--wp-admin-theme-color); + } } .block-editor-grid-item-resizer { From 8d0160aa48c8c56d975e51adee0181d45fc3e495 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 29 Feb 2024 15:42:59 +1100 Subject: [PATCH 03/32] Don't obscure the in-between block drop zone --- .../src/components/grid-visualizer/grid-visualizer.js | 8 ++++---- .../src/components/grid-visualizer/style.scss | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 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 03b40292a2d61..fc25a5cd0a80f 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -65,7 +65,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( blockElement ) ); - const [ isDisabled, setIsDisabled ] = useState( true ); + const [ hasCellPointerEvents, setHasCellPointerEvents ] = useState( false ); useEffect( () => { const observers = []; @@ -85,11 +85,11 @@ function GridVisualizerGrid( { clientId, blockElement } ) { useEffect( () => { function onGlobalDrag() { - setIsDisabled( false ); + setHasCellPointerEvents( true ); } function onGlobalDragEnd() { - setIsDisabled( true ); + setHasCellPointerEvents( false ); } document.addEventListener( 'drag', onGlobalDrag ); document.addEventListener( 'dragend', onGlobalDragEnd ); @@ -102,7 +102,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { return ( Date: Fri, 1 Mar 2024 09:43:20 +1100 Subject: [PATCH 04/32] Output grid-start styling --- lib/block-supports/layout.php | 18 +++++++++++-- .../grid-visualizer/grid-visualizer.js | 15 +++++++---- .../block-editor/src/hooks/layout-child.js | 27 +++++++++++++++++-- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 48cb206fd7894..6f7f3fc1c23ba 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -581,11 +581,25 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $child_layout_declarations['flex-grow'] = '1'; } - if ( isset( $block['attrs']['style']['layout']['columnSpan'] ) ) { + if ( isset( $block['attrs']['style']['layout']['columnStart'] ) && isset( $block['attrs']['style']['layout']['columnSpan'] ) ) { + $column_start = $block['attrs']['style']['layout']['columnStart']; + $column_span = $block['attrs']['style']['layout']['columnSpan']; + $child_layout_declarations['grid-column'] = "$column_start / span $column_span"; + } elseif ( isset( $block['attrs']['style']['layout']['columnStart'] ) ) { + $column_start = $block['attrs']['style']['layout']['columnStart']; + $child_layout_declarations['grid-column'] = "$column_start"; + } elseif ( isset( $block['attrs']['style']['layout']['columnSpan'] ) ) { $column_span = $block['attrs']['style']['layout']['columnSpan']; $child_layout_declarations['grid-column'] = "span $column_span"; } - if ( isset( $block['attrs']['style']['layout']['rowSpan'] ) ) { + if ( isset( $block['attrs']['style']['layout']['rowStart'] ) && isset( $block['attrs']['style']['layout']['rowSpan'] ) ) { + $row_start = $block['attrs']['style']['layout']['rowStart']; + $row_span = $block['attrs']['style']['layout']['rowSpan']; + $child_layout_declarations['grid-row'] = "$row_start / span $row_span"; + } elseif ( isset( $block['attrs']['style']['layout']['rowStart'] ) ) { + $row_start = $block['attrs']['style']['layout']['rowStart']; + $child_layout_declarations['grid-row'] = "$row_start"; + } elseif ( isset( $block['attrs']['style']['layout']['rowSpan'] ) ) { $row_span = $block['attrs']['style']['layout']['rowSpan']; $child_layout_declarations['grid-row'] = "span $row_span"; } 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 fc25a5cd0a80f..596e1d1957c10 100644 --- a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useState, useEffect } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; /** @@ -127,6 +127,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { function GridVisualizerCell( { column, row } ) { const [ isDraggingOver, setIsDraggingOver ] = useState( false ); + const { getBlockAttributes } = useSelect( blockEditorStore ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); const ref = useDropZone( { @@ -138,19 +139,23 @@ function GridVisualizerCell( { column, row } ) { }, onDrop( event ) { setIsDraggingOver( false ); - const { srcClientIds } = parseDropEvent( event ); - if ( ! srcClientIds?.length ) { + const { + srcClientIds: [ srcClientId ], + } = parseDropEvent( event ); + if ( ! srcClientId ) { return; } - updateBlockAttributes( srcClientIds, { + const attributes = getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { style: { + ...attributes.style, layout: { + ...attributes.style?.layout, columnStart: column, rowStart: row, }, }, } ); - console.log( srcClientIds, column, row ); }, } ); diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index d8333e8e0e830..9aac9735bcbd6 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -17,7 +17,14 @@ function useBlockPropsChildLayoutStyles( { style } ) { return ! select( blockEditorStore ).getSettings().disableLayoutStyles; } ); const layout = style?.layout ?? {}; - const { selfStretch, flexSize, columnSpan, rowSpan } = layout; + const { + selfStretch, + flexSize, + columnStart, + rowStart, + columnSpan, + rowSpan, + } = layout; const parentLayout = useLayout() || {}; const { columnCount, minimumColumnWidth } = parentLayout; const id = useInstanceId( useBlockPropsChildLayoutStyles ); @@ -34,6 +41,14 @@ function useBlockPropsChildLayoutStyles( { style } ) { css = `${ selector } { flex-grow: 1; }`; + } else if ( columnStart && columnSpan ) { + css = `${ selector } { + grid-column: ${ columnStart } / span ${ columnSpan }; + }`; + } else if ( columnStart ) { + css = `${ selector } { + grid-column: ${ columnStart }; + }`; } else if ( columnSpan ) { css = `${ selector } { grid-column: span ${ columnSpan }; @@ -79,7 +94,15 @@ function useBlockPropsChildLayoutStyles( { style } ) { } }`; } - if ( rowSpan ) { + if ( rowStart && rowSpan ) { + css += `${ selector } { + grid-row: ${ rowStart } / span ${ rowSpan }; + }`; + } else if ( rowStart ) { + css += `${ selector } { + grid-row: ${ rowStart }; + }`; + } else if ( rowSpan ) { css += `${ selector } { grid-row: span ${ rowSpan }; }`; From 81f693644e0b108bfca61fca79ceb5680b0f3e78 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 1 Mar 2024 09:43:20 +1100 Subject: [PATCH 05/32] Output grid-start styling --- lib/block-supports/layout.php | 18 +++++++++++-- .../block-editor/src/hooks/layout-child.js | 27 +++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 48cb206fd7894..6f7f3fc1c23ba 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -581,11 +581,25 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $child_layout_declarations['flex-grow'] = '1'; } - if ( isset( $block['attrs']['style']['layout']['columnSpan'] ) ) { + if ( isset( $block['attrs']['style']['layout']['columnStart'] ) && isset( $block['attrs']['style']['layout']['columnSpan'] ) ) { + $column_start = $block['attrs']['style']['layout']['columnStart']; + $column_span = $block['attrs']['style']['layout']['columnSpan']; + $child_layout_declarations['grid-column'] = "$column_start / span $column_span"; + } elseif ( isset( $block['attrs']['style']['layout']['columnStart'] ) ) { + $column_start = $block['attrs']['style']['layout']['columnStart']; + $child_layout_declarations['grid-column'] = "$column_start"; + } elseif ( isset( $block['attrs']['style']['layout']['columnSpan'] ) ) { $column_span = $block['attrs']['style']['layout']['columnSpan']; $child_layout_declarations['grid-column'] = "span $column_span"; } - if ( isset( $block['attrs']['style']['layout']['rowSpan'] ) ) { + if ( isset( $block['attrs']['style']['layout']['rowStart'] ) && isset( $block['attrs']['style']['layout']['rowSpan'] ) ) { + $row_start = $block['attrs']['style']['layout']['rowStart']; + $row_span = $block['attrs']['style']['layout']['rowSpan']; + $child_layout_declarations['grid-row'] = "$row_start / span $row_span"; + } elseif ( isset( $block['attrs']['style']['layout']['rowStart'] ) ) { + $row_start = $block['attrs']['style']['layout']['rowStart']; + $child_layout_declarations['grid-row'] = "$row_start"; + } elseif ( isset( $block['attrs']['style']['layout']['rowSpan'] ) ) { $row_span = $block['attrs']['style']['layout']['rowSpan']; $child_layout_declarations['grid-row'] = "span $row_span"; } diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index d8333e8e0e830..9aac9735bcbd6 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -17,7 +17,14 @@ function useBlockPropsChildLayoutStyles( { style } ) { return ! select( blockEditorStore ).getSettings().disableLayoutStyles; } ); const layout = style?.layout ?? {}; - const { selfStretch, flexSize, columnSpan, rowSpan } = layout; + const { + selfStretch, + flexSize, + columnStart, + rowStart, + columnSpan, + rowSpan, + } = layout; const parentLayout = useLayout() || {}; const { columnCount, minimumColumnWidth } = parentLayout; const id = useInstanceId( useBlockPropsChildLayoutStyles ); @@ -34,6 +41,14 @@ function useBlockPropsChildLayoutStyles( { style } ) { css = `${ selector } { flex-grow: 1; }`; + } else if ( columnStart && columnSpan ) { + css = `${ selector } { + grid-column: ${ columnStart } / span ${ columnSpan }; + }`; + } else if ( columnStart ) { + css = `${ selector } { + grid-column: ${ columnStart }; + }`; } else if ( columnSpan ) { css = `${ selector } { grid-column: span ${ columnSpan }; @@ -79,7 +94,15 @@ function useBlockPropsChildLayoutStyles( { style } ) { } }`; } - if ( rowSpan ) { + if ( rowStart && rowSpan ) { + css += `${ selector } { + grid-row: ${ rowStart } / span ${ rowSpan }; + }`; + } else if ( rowStart ) { + css += `${ selector } { + grid-row: ${ rowStart }; + }`; + } else if ( rowSpan ) { css += `${ selector } { grid-row: span ${ rowSpan }; }`; From ed4483a77bd582aabedf25c535d49b63a12477b3 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 1 Mar 2024 10:38:34 +1100 Subject: [PATCH 06/32] Add Column Start and Row Start controls to block inspector --- .../components/child-layout-control/index.js | 103 +++++++++++++----- .../global-styles/dimensions-panel.js | 4 + 2 files changed, 78 insertions(+), 29 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 310d9cd2a3f15..728df01d361cd 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -40,7 +40,14 @@ export default function ChildLayoutControl( { onChange, parentLayout, } ) { - const { selfStretch, flexSize, columnSpan, rowSpan } = childLayout; + const { + selfStretch, + flexSize, + columnStart, + rowStart, + columnSpan, + rowSpan, + } = childLayout; const { type: parentType, default: { type: defaultParentType = 'default' } = {}, @@ -107,34 +114,72 @@ export default function ChildLayoutControl( { ) } { parentLayoutType === 'grid' && ( - - { - onChange( { - rowSpan, - columnSpan: value, - } ); - } } - value={ columnSpan } - min={ 1 } - /> - { - onChange( { - columnSpan, - rowSpan: value, - } ); - } } - value={ rowSpan } - min={ 1 } - /> - + <> + + { + onChange( { + columnStart: value, + rowStart, + columnSpan, + rowSpan, + } ); + } } + value={ columnStart } + min={ 1 } + /> + { + onChange( { + columnStart, + rowStart: value, + columnSpan, + rowSpan, + } ); + } } + value={ rowStart } + min={ 1 } + /> + + + { + onChange( { + columnStart, + rowStart, + columnSpan: value, + rowSpan, + } ); + } } + value={ columnSpan } + min={ 1 } + /> + { + onChange( { + columnStart, + rowStart, + columnSpan, + rowSpan: value, + } ); + } } + value={ rowSpan } + min={ 1 } + /> + + ) } ); diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js index 1386df0dfe289..0d7473be28803 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -418,6 +418,8 @@ export default function DimensionsPanel( { setChildLayout( { selfStretch: undefined, flexSize: undefined, + columnStart: undefined, + rowStart: undefined, columnSpan: undefined, rowSpan: undefined, } ); @@ -433,6 +435,8 @@ export default function DimensionsPanel( { wideSize: undefined, selfStretch: undefined, flexSize: undefined, + columnStart: undefined, + rowStart: undefined, columnSpan: undefined, rowSpan: undefined, } ), From 2fb3f1b5f245b1840ba74c53ea28d62b38a34220 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 1 Mar 2024 13:14:43 +1100 Subject: [PATCH 07/32] Rename grid-visualizer dir to grid-interactivity --- .../grid-item-resizer.js | 0 .../grid-visualizer.js | 0 .../{grid-visualizer => grid-interactivity}/index.js | 0 .../{grid-visualizer => grid-interactivity}/style.scss | 0 .../{grid-visualizer => grid-interactivity}/utils.js | 0 packages/block-editor/src/hooks/layout-child.js | 5 ++++- packages/block-editor/src/layouts/grid.js | 2 +- packages/block-editor/src/style.scss | 2 +- 8 files changed, 6 insertions(+), 3 deletions(-) rename packages/block-editor/src/components/{grid-visualizer => grid-interactivity}/grid-item-resizer.js (100%) rename packages/block-editor/src/components/{grid-visualizer => grid-interactivity}/grid-visualizer.js (100%) rename packages/block-editor/src/components/{grid-visualizer => grid-interactivity}/index.js (100%) rename packages/block-editor/src/components/{grid-visualizer => grid-interactivity}/style.scss (100%) rename packages/block-editor/src/components/{grid-visualizer => grid-interactivity}/utils.js (100%) diff --git a/packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js similarity index 100% rename from packages/block-editor/src/components/grid-visualizer/grid-item-resizer.js rename to packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js diff --git a/packages/block-editor/src/components/grid-visualizer/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js similarity index 100% rename from packages/block-editor/src/components/grid-visualizer/grid-visualizer.js rename to packages/block-editor/src/components/grid-interactivity/grid-visualizer.js diff --git a/packages/block-editor/src/components/grid-visualizer/index.js b/packages/block-editor/src/components/grid-interactivity/index.js similarity index 100% rename from packages/block-editor/src/components/grid-visualizer/index.js rename to packages/block-editor/src/components/grid-interactivity/index.js diff --git a/packages/block-editor/src/components/grid-visualizer/style.scss b/packages/block-editor/src/components/grid-interactivity/style.scss similarity index 100% rename from packages/block-editor/src/components/grid-visualizer/style.scss rename to packages/block-editor/src/components/grid-interactivity/style.scss diff --git a/packages/block-editor/src/components/grid-visualizer/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js similarity index 100% rename from packages/block-editor/src/components/grid-visualizer/utils.js rename to packages/block-editor/src/components/grid-interactivity/utils.js diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index 9aac9735bcbd6..92f995ac438f8 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -10,7 +10,10 @@ import { useSelect } from '@wordpress/data'; 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, +} from '../components/grid-interactivity'; 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 0dc72694bd568..2b993893bcc41 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 } from '../components/grid-interactivity'; const RANGE_CONTROL_MAX_VALUES = { px: 600, diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 1cbc49f58551e..c6816bdf8114f 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-interactivity/style.scss"; @import "./components/height-control/style.scss"; @import "./components/image-size-control/style.scss"; @import "./components/inserter-list-item/style.scss"; From f68c71524202ad317d3123d08b7d24fc6661bfe1 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 1 Mar 2024 13:19:38 +1100 Subject: [PATCH 08/32] Use specificity instead of !important --- .../components/grid-interactivity/style.scss | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/style.scss b/packages/block-editor/src/components/grid-interactivity/style.scss index 09130e7a64323..5d1507e066b8f 100644 --- a/packages/block-editor/src/components/grid-interactivity/style.scss +++ b/packages/block-editor/src/components/grid-interactivity/style.scss @@ -1,5 +1,5 @@ .block-editor-grid-visualizer { - // Override the z-index and pointer-events set by .components-popover using lots of specificity. + // 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"); @@ -29,17 +29,23 @@ } .block-editor-grid-item-resizer { - z-index: z-index(".block-editor-grid-visualizer") !important; -} + // 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"); -.block-editor-grid-item-resizer .components-popover__content * { - pointer-events: none !important; + .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 { - pointer-events: all !important; + // Specificity to override the pointer-events set by .components-popover. + &.components-resizable-box__handle.components-resizable-box__handle { + pointer-events: all; + } } } From 402eddc03e1333cac0d7c10b96f3e53c5c523f80 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 1 Mar 2024 13:59:14 +1100 Subject: [PATCH 09/32] Add 'Pin to grid' button --- .../grid-item-pin-toolbar-item.js | 66 +++++++++++++++++++ .../grid-interactivity/grid-item-resizer.js | 22 +------ .../components/grid-interactivity/index.js | 1 + .../components/grid-interactivity/utils.js | 20 ++++++ .../block-editor/src/hooks/layout-child.js | 32 +++++---- 5 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js new file mode 100644 index 0000000000000..449c232a21991 --- /dev/null +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { pin as pinIcon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import BlockControls from '../block-controls'; +import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; +import { getComputedCSS, getGridLines, getClosestLine } from './utils'; + +export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { + const blockElement = useBlockElement( clientId ); + if ( ! blockElement ) { + return null; + } + + const isPinned = !! layout?.columnStart && !! layout?.rowStart; + + function unpinBlock() { + onChange( { + columnStart: undefined, + rowStart: undefined, + } ); + } + + function pinBlock() { + const gridElement = blockElement.parentElement; + const columnGap = parseFloat( + getComputedCSS( gridElement, 'column-gap' ) + ); + const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); + const gridColumnLines = getGridLines( + getComputedCSS( gridElement, 'grid-template-columns' ), + columnGap + ); + const gridRowLines = getGridLines( + getComputedCSS( gridElement, 'grid-template-rows' ), + rowGap + ); + const columnStart = + getClosestLine( gridColumnLines, blockElement.offsetLeft ) + 1; + const rowStart = + getClosestLine( gridRowLines, blockElement.offsetTop ) + 1; + onChange( { + columnStart, + rowStart, + } ); + } + + return ( + + + + + + ); +} diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js index 54683e48beeea..2f0deef402926 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js @@ -8,7 +8,7 @@ import { ResizableBox } from '@wordpress/components'; */ 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, getGridLines, getClosestLine } from './utils'; export function GridItemResizer( { clientId, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -78,23 +78,3 @@ export function GridItemResizer( { clientId, onChange } ) { ); } - -function getGridLines( template, gap ) { - const lines = [ 0 ]; - for ( const size of template.split( ' ' ) ) { - const line = parseFloat( size ); - lines.push( lines[ lines.length - 1 ] + line + gap ); - } - return lines; -} - -function getClosestLine( lines, position ) { - return lines.reduce( - ( closest, line, index ) => - Math.abs( line - position ) < - Math.abs( lines[ closest ] - position ) - ? index - : closest, - 0 - ); -} diff --git a/packages/block-editor/src/components/grid-interactivity/index.js b/packages/block-editor/src/components/grid-interactivity/index.js index add845d702203..663f89aac426c 100644 --- a/packages/block-editor/src/components/grid-interactivity/index.js +++ b/packages/block-editor/src/components/grid-interactivity/index.js @@ -1,2 +1,3 @@ export { GridVisualizer } from './grid-visualizer'; export { GridItemResizer } from './grid-item-resizer'; +export { GridItemPinToolbarItem } from './grid-item-pin-toolbar-item'; diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index a100e596a4e24..6229fc7e78bed 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -3,3 +3,23 @@ export function getComputedCSS( element, property ) { .getComputedStyle( element ) .getPropertyValue( property ); } + +export function getGridLines( template, gap ) { + const lines = [ 0 ]; + for ( const size of template.split( ' ' ) ) { + const line = parseFloat( size ); + lines.push( lines[ lines.length - 1 ] + line + gap ); + } + return lines; +} + +export function getClosestLine( lines, position ) { + return lines.reduce( + ( closest, line, index ) => + Math.abs( line - position ) < + Math.abs( lines[ closest ] - position ) + ? index + : closest, + 0 + ); +} diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index 92f995ac438f8..eb51ac5dd1a69 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -13,6 +13,7 @@ import { useLayout } from '../components/block-list/layout'; import { GridVisualizer, GridItemResizer, + GridItemPinToolbarItem, } from '../components/grid-interactivity'; function useBlockPropsChildLayoutStyles( { style } ) { @@ -131,29 +132,34 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { }, [ clientId ] ); + if ( parentLayout.type !== 'grid' ) { return null; } if ( ! window.__experimentalEnableGridInteractivity ) { return null; } + + function updateLayout( layout ) { + setAttributes( { + style: { + ...style, + layout: { + ...style?.layout, + ...layout, + }, + }, + } ); + } + return ( <> - + { - setAttributes( { - style: { - ...style, - layout: { - ...style?.layout, - columnSpan, - rowSpan, - }, - }, - } ); - } } + layout={ style?.layout } + onChange={ updateLayout } /> ); From 8eaa61a62fa8bab5f3662fd46228579b52b12731 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 1 Mar 2024 15:44:47 +1100 Subject: [PATCH 10/32] Remove weird blank line --- .../src/components/grid-interactivity/grid-visualizer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 596e1d1957c10..3106dbf46c752 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -87,7 +87,6 @@ function GridVisualizerGrid( { clientId, blockElement } ) { function onGlobalDrag() { setHasCellPointerEvents( true ); } - function onGlobalDragEnd() { setHasCellPointerEvents( false ); } From bfe3042566b71ad8b3263c8350855e6dca68246f Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 15 Mar 2024 10:15:25 +1100 Subject: [PATCH 11/32] Remove pin button for now --- .../grid-item-pin-toolbar-item.js | 66 ------------------- .../components/grid-interactivity/index.js | 1 - .../block-editor/src/hooks/layout-child.js | 6 -- 3 files changed, 73 deletions(-) delete mode 100644 packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js deleted file mode 100644 index 449c232a21991..0000000000000 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; -import { pin as pinIcon } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import BlockControls from '../block-controls'; -import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import { getComputedCSS, getGridLines, getClosestLine } from './utils'; - -export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { - const blockElement = useBlockElement( clientId ); - if ( ! blockElement ) { - return null; - } - - const isPinned = !! layout?.columnStart && !! layout?.rowStart; - - function unpinBlock() { - onChange( { - columnStart: undefined, - rowStart: undefined, - } ); - } - - function pinBlock() { - const gridElement = blockElement.parentElement; - const columnGap = parseFloat( - getComputedCSS( gridElement, 'column-gap' ) - ); - const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); - const gridColumnLines = getGridLines( - getComputedCSS( gridElement, 'grid-template-columns' ), - columnGap - ); - const gridRowLines = getGridLines( - getComputedCSS( gridElement, 'grid-template-rows' ), - rowGap - ); - const columnStart = - getClosestLine( gridColumnLines, blockElement.offsetLeft ) + 1; - const rowStart = - getClosestLine( gridRowLines, blockElement.offsetTop ) + 1; - onChange( { - columnStart, - rowStart, - } ); - } - - return ( - - - - - - ); -} diff --git a/packages/block-editor/src/components/grid-interactivity/index.js b/packages/block-editor/src/components/grid-interactivity/index.js index 663f89aac426c..add845d702203 100644 --- a/packages/block-editor/src/components/grid-interactivity/index.js +++ b/packages/block-editor/src/components/grid-interactivity/index.js @@ -1,3 +1,2 @@ export { GridVisualizer } from './grid-visualizer'; export { GridItemResizer } from './grid-item-resizer'; -export { GridItemPinToolbarItem } from './grid-item-pin-toolbar-item'; diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index a5e9add7b3a46..b70d5960d6afd 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -13,7 +13,6 @@ import { useLayout } from '../components/block-list/layout'; import { GridVisualizer, GridItemResizer, - GridItemPinToolbarItem, } from '../components/grid-interactivity'; function useBlockPropsChildLayoutStyles( { style } ) { @@ -169,11 +168,6 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { <> - ); } From e62f09012cb846704c66519966c5ad145ba4f727 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 15 Mar 2024 12:05:30 +1100 Subject: [PATCH 12/32] Revert "Remove pin button for now" This reverts commit bfe3042566b71ad8b3263c8350855e6dca68246f. --- .../grid-item-pin-toolbar-item.js | 66 +++++++++++++++++++ .../components/grid-interactivity/index.js | 1 + .../block-editor/src/hooks/layout-child.js | 6 ++ 3 files changed, 73 insertions(+) create mode 100644 packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js new file mode 100644 index 0000000000000..449c232a21991 --- /dev/null +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { pin as pinIcon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import BlockControls from '../block-controls'; +import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; +import { getComputedCSS, getGridLines, getClosestLine } from './utils'; + +export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { + const blockElement = useBlockElement( clientId ); + if ( ! blockElement ) { + return null; + } + + const isPinned = !! layout?.columnStart && !! layout?.rowStart; + + function unpinBlock() { + onChange( { + columnStart: undefined, + rowStart: undefined, + } ); + } + + function pinBlock() { + const gridElement = blockElement.parentElement; + const columnGap = parseFloat( + getComputedCSS( gridElement, 'column-gap' ) + ); + const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); + const gridColumnLines = getGridLines( + getComputedCSS( gridElement, 'grid-template-columns' ), + columnGap + ); + const gridRowLines = getGridLines( + getComputedCSS( gridElement, 'grid-template-rows' ), + rowGap + ); + const columnStart = + getClosestLine( gridColumnLines, blockElement.offsetLeft ) + 1; + const rowStart = + getClosestLine( gridRowLines, blockElement.offsetTop ) + 1; + onChange( { + columnStart, + rowStart, + } ); + } + + return ( + + + + + + ); +} diff --git a/packages/block-editor/src/components/grid-interactivity/index.js b/packages/block-editor/src/components/grid-interactivity/index.js index add845d702203..663f89aac426c 100644 --- a/packages/block-editor/src/components/grid-interactivity/index.js +++ b/packages/block-editor/src/components/grid-interactivity/index.js @@ -1,2 +1,3 @@ export { GridVisualizer } from './grid-visualizer'; export { GridItemResizer } from './grid-item-resizer'; +export { GridItemPinToolbarItem } from './grid-item-pin-toolbar-item'; diff --git a/packages/block-editor/src/hooks/layout-child.js b/packages/block-editor/src/hooks/layout-child.js index b70d5960d6afd..a5e9add7b3a46 100644 --- a/packages/block-editor/src/hooks/layout-child.js +++ b/packages/block-editor/src/hooks/layout-child.js @@ -13,6 +13,7 @@ import { useLayout } from '../components/block-list/layout'; import { GridVisualizer, GridItemResizer, + GridItemPinToolbarItem, } from '../components/grid-interactivity'; function useBlockPropsChildLayoutStyles( { style } ) { @@ -168,6 +169,11 @@ function ChildLayoutControlsPure( { clientId, style, setAttributes } ) { <> + ); } From f6a9e0a699fed177cb0cec5dcacfbaf1f8d97fd8 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 15 Mar 2024 13:54:23 +1100 Subject: [PATCH 13/32] Make drop zone 8x8 at minimum --- .../grid-interactivity/grid-visualizer.js | 25 +++++++++++-------- .../components/grid-interactivity/style.scss | 17 ++++++++++--- packages/block-editor/src/layouts/grid.js | 4 ++- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 3106dbf46c752..cfac9610e8d14 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -65,7 +65,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( blockElement ) ); - const [ hasCellPointerEvents, setHasCellPointerEvents ] = useState( false ); + const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); useEffect( () => { const observers = []; @@ -85,10 +85,10 @@ function GridVisualizerGrid( { clientId, blockElement } ) { useEffect( () => { function onGlobalDrag() { - setHasCellPointerEvents( true ); + setIsDroppingAllowed( true ); } function onGlobalDragEnd() { - setHasCellPointerEvents( false ); + setIsDroppingAllowed( false ); } document.addEventListener( 'drag', onGlobalDrag ); document.addEventListener( 'dragend', onGlobalDragEnd ); @@ -101,7 +101,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { return ( +
+
+
); } diff --git a/packages/block-editor/src/components/grid-interactivity/style.scss b/packages/block-editor/src/components/grid-interactivity/style.scss index 5d1507e066b8f..a17323af21ab1 100644 --- a/packages/block-editor/src/components/grid-interactivity/style.scss +++ b/packages/block-editor/src/components/grid-interactivity/style.scss @@ -7,8 +7,8 @@ pointer-events: none; } - &.has-cell-pointer-events { - .block-editor-grid-visualizer__cell { + &.is-dropping-allowed { + .block-editor-grid-visualizer__drop-zone { pointer-events: all; } } @@ -20,8 +20,19 @@ } .block-editor-grid-visualizer__cell { + align-items: center; + display: flex; + justify-content: center; +} + +.block-editor-grid-visualizer__drop-zone { border: $border-width dashed $gray-300; - position: relative; + 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-dragging-over { background: var(--wp-admin-theme-color); diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 098a698d4191a..12f31dc08d57d 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -120,11 +120,13 @@ export default { if ( columnCount ) { rules.push( `grid-template-columns: repeat(${ columnCount }, minmax(0, 1fr))` + // 'grid-auto-rows: minmax(8px, auto)' ); } else if ( minimumColumnWidth ) { rules.push( `grid-template-columns: repeat(auto-fill, minmax(min(${ minimumColumnWidth }, 100%), 1fr))`, - `container-type: inline-size` + 'container-type: inline-size' + // 'grid-auto-rows: minmax(8px, auto)' ); } From 7dcc87447f562ae647e0eb52d222ca969cc09ea0 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 15 Mar 2024 14:54:44 +1100 Subject: [PATCH 14/32] Improve handling of resizing onto empty cells --- .../grid-item-pin-toolbar-item.js | 10 +++--- .../grid-interactivity/grid-item-resizer.js | 32 ++++++++++--------- .../components/grid-interactivity/utils.js | 22 +++++++------ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js index 449c232a21991..9a5432780b0a5 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -10,7 +10,7 @@ import { pin as pinIcon } from '@wordpress/icons'; */ import BlockControls from '../block-controls'; import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import { getComputedCSS, getGridLines, getClosestLine } from './utils'; +import { getComputedCSS, getGridTracks, getClosestTrack } from './utils'; export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -33,18 +33,18 @@ export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { getComputedCSS( gridElement, 'column-gap' ) ); const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); - const gridColumnLines = getGridLines( + const gridColumnTracks = getGridTracks( getComputedCSS( gridElement, 'grid-template-columns' ), columnGap ); - const gridRowLines = getGridLines( + const gridRowTracks = getGridTracks( getComputedCSS( gridElement, 'grid-template-rows' ), rowGap ); const columnStart = - getClosestLine( gridColumnLines, blockElement.offsetLeft ) + 1; + getClosestTrack( gridColumnTracks, blockElement.offsetLeft ) + 1; const rowStart = - getClosestLine( gridRowLines, blockElement.offsetTop ) + 1; + getClosestTrack( gridRowTracks, blockElement.offsetTop ) + 1; onChange( { columnStart, rowStart, diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js index 2f0deef402926..b663c8fd6617e 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js @@ -8,7 +8,7 @@ import { ResizableBox } from '@wordpress/components'; */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { getComputedCSS, getGridLines, getClosestLine } from './utils'; +import { getComputedCSS, getGridTracks, getClosestTrack } from './utils'; export function GridItemResizer( { clientId, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -45,33 +45,35 @@ export function GridItemResizer( { clientId, onChange } ) { const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); - const gridColumnLines = getGridLines( + const gridColumnTracks = getGridTracks( getComputedCSS( gridElement, 'grid-template-columns' ), columnGap ); - const gridRowLines = getGridLines( + const gridRowTracks = getGridTracks( getComputedCSS( gridElement, 'grid-template-rows' ), rowGap ); - const columnStart = getClosestLine( - gridColumnLines, + const columnStart = getClosestTrack( + gridColumnTracks, blockElement.offsetLeft ); - const rowStart = getClosestLine( - gridRowLines, + const rowStart = getClosestTrack( + gridRowTracks, blockElement.offsetTop ); - const columnEnd = getClosestLine( - gridColumnLines, - blockElement.offsetLeft + boxElement.offsetWidth + const columnEnd = getClosestTrack( + gridColumnTracks, + blockElement.offsetLeft + boxElement.offsetWidth, + 'end' ); - const rowEnd = getClosestLine( - gridRowLines, - blockElement.offsetTop + boxElement.offsetHeight + const rowEnd = getClosestTrack( + gridRowTracks, + blockElement.offsetTop + boxElement.offsetHeight, + 'end' ); onChange( { - columnSpan: Math.max( columnEnd - columnStart, 1 ), - rowSpan: Math.max( rowEnd - rowStart, 1 ), + columnSpan: columnEnd - columnStart + 1, + rowSpan: rowEnd - rowStart + 1, } ); } } /> diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index 6229fc7e78bed..ba7d6372f91e6 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -4,20 +4,22 @@ export function getComputedCSS( element, property ) { .getPropertyValue( property ); } -export function getGridLines( template, gap ) { - const lines = [ 0 ]; +export function getGridTracks( template, gap ) { + const tracks = []; for ( const size of template.split( ' ' ) ) { - const line = parseFloat( size ); - lines.push( lines[ lines.length - 1 ] + line + gap ); + const previousTrack = tracks[ tracks.length - 1 ]; + const start = previousTrack ? previousTrack.end + gap : 0; + const end = start + parseFloat( size ); + tracks.push( { start, end } ); } - return lines; + return tracks; } -export function getClosestLine( lines, position ) { - return lines.reduce( - ( closest, line, index ) => - Math.abs( line - position ) < - Math.abs( lines[ closest ] - position ) +export 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 From 81463b449c1eef47401cb1a7963c8cd5c24213f6 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 18 Mar 2024 15:19:37 +1100 Subject: [PATCH 15/32] Don't allow blocks with a >1 span to be dragged too close to the edge --- .../components/child-layout-control/index.js | 7 +- .../grid-interactivity/grid-visualizer.js | 121 +++++++++++++----- .../components/grid-interactivity/style.scss | 2 +- .../components/grid-interactivity/utils.js | 29 +++++ 4 files changed, 125 insertions(+), 34 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 e0af72c238ad5..12360975a1d0c 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -223,7 +223,11 @@ export default function ChildLayoutControl( { } } value={ columnStart } min={ 1 } - max={ parentLayout?.columnCount } + max={ + parentLayout?.columnCount - + columnSpan + + 1 + } /> @@ -241,7 +245,6 @@ export default function ChildLayoutControl( { } } value={ rowStart } min={ 1 } - max={ parentLayout?.columnCount } /> diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index cfac9610e8d14..76e35666b7d27 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/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 { getComputedCSS } from './utils'; +import { getComputedCSS, range, Rect } from './utils'; import { parseDropEvent } from '../use-on-block-drop'; import { store as blockEditorStore } from '../../store'; @@ -57,15 +57,15 @@ function getGridInfo( blockElement ) { }; } -function range( start, length ) { - return Array.from( { length }, ( _, i ) => start + i ); -} - function GridVisualizerGrid( { clientId, blockElement } ) { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( blockElement ) ); const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); + const [ highlightedRect, setHighlightedRect ] = useState( null ); + + const { getBlockAttributes } = useSelect( blockEditorStore ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); useEffect( () => { const observers = []; @@ -114,8 +114,73 @@ function GridVisualizerGrid( { clientId, blockElement } ) { range( 1, gridInfo.numColumns ).map( ( column ) => ( { + const attributes = + getBlockAttributes( srcClientId ); + const columnSpan = + attributes.style?.layout?.columnSpan ?? 1; + const rowSpan = + attributes.style?.layout?.rowSpan ?? 1; + return new Rect( { + width: gridInfo.numColumns, + height: gridInfo.numRows, + } ).containsRect( + new Rect( { + x: column - 1, + y: row - 1, + width: columnSpan, + height: rowSpan, + } ) + ); + } } + onDragEnter={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + const columnSpan = + attributes.style?.layout?.columnSpan ?? 1; + const rowSpan = + attributes.style?.layout?.rowSpan ?? 1; + setHighlightedRect( + new Rect( { + x: column - 1, + y: row - 1, + width: columnSpan, + height: 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?.x === column - 1 && + prevHighlightedRect?.y === row - 1 + ? null + : prevHighlightedRect + ); + } } + onDrop={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, + }, + } ); + setHighlightedRect( null ); + } } /> ) ) ) } @@ -124,40 +189,34 @@ function GridVisualizerGrid( { clientId, blockElement } ) { ); } -function GridVisualizerCell( { column, row } ) { - const [ isDraggingOver, setIsDraggingOver ] = useState( false ); - const { getBlockAttributes } = useSelect( blockEditorStore ); - const { updateBlockAttributes } = useDispatch( blockEditorStore ); - +function GridVisualizerCell( { + isHighlighted, + validateDrag, + onDragEnter, + onDragLeave, + onDrop, +} ) { const ref = useDropZone( { - onDragEnter() { - setIsDraggingOver( true ); + onDragEnter( event ) { + const { + srcClientIds: [ srcClientId ], + } = parseDropEvent( event ); + if ( validateDrag( srcClientId ) ) { + onDragEnter( srcClientId ); + } }, onDragLeave() { - setIsDraggingOver( false ); + onDragLeave(); }, onDrop( event ) { - setIsDraggingOver( false ); const { srcClientIds: [ srcClientId ], } = parseDropEvent( event ); - if ( ! srcClientId ) { - return; + if ( validateDrag( srcClientId ) ) { + onDrop( srcClientId ); } - const attributes = getBlockAttributes( srcClientId ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, - columnStart: column, - rowStart: row, - }, - }, - } ); }, } ); - return (
diff --git a/packages/block-editor/src/components/grid-interactivity/style.scss b/packages/block-editor/src/components/grid-interactivity/style.scss index a17323af21ab1..2c9cec978b3aa 100644 --- a/packages/block-editor/src/components/grid-interactivity/style.scss +++ b/packages/block-editor/src/components/grid-interactivity/style.scss @@ -34,7 +34,7 @@ min-width: $grid-unit-10; min-height: $grid-unit-10; - &.is-dragging-over { + &.is-highlighted { background: var(--wp-admin-theme-color); } } diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index ba7d6372f91e6..1943ed53dc503 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -25,3 +25,32 @@ export function getClosestTrack( tracks, position, edge = 'start' ) { 0 ); } + +export function range( start, length ) { + return Array.from( { length }, ( _, i ) => start + i ); +} + +export class Rect { + constructor( { x = 0, y = 0, width = 1, height = 1 } = {} ) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + contains( x, y ) { + return ( + x >= this.x && + x < this.x + this.width && + y >= this.y && + y < this.y + this.height + ); + } + + containsRect( rect ) { + return ( + this.contains( rect.x, rect.y ) && + this.contains( rect.x + rect.width - 1, rect.y + rect.height - 1 ) + ); + } +} From 8fb6fd68bb6b3b71634128a3b634820d3f8253cc Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 18 Mar 2024 15:21:22 +1100 Subject: [PATCH 16/32] Delete accidental comment --- packages/block-editor/src/layouts/grid.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 12f31dc08d57d..607e7e116f6c8 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -120,13 +120,11 @@ export default { if ( columnCount ) { rules.push( `grid-template-columns: repeat(${ columnCount }, minmax(0, 1fr))` - // 'grid-auto-rows: minmax(8px, auto)' ); } else if ( minimumColumnWidth ) { rules.push( `grid-template-columns: repeat(auto-fill, minmax(min(${ minimumColumnWidth }, 100%), 1fr))`, 'container-type: inline-size' - // 'grid-auto-rows: minmax(8px, auto)' ); } From c9638ecaae9a651a6565fd1ed708ce3ce1592785 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 18 Mar 2024 15:22:16 +1100 Subject: [PATCH 17/32] Treat a block as pinned if it has a column start **or** a row start --- .../components/grid-interactivity/grid-item-pin-toolbar-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js index 9a5432780b0a5..cadeae9398417 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -18,7 +18,7 @@ export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { return null; } - const isPinned = !! layout?.columnStart && !! layout?.rowStart; + const isPinned = !! layout?.columnStart || !! layout?.rowStart; function unpinBlock() { onChange( { From 5f4aaeb48de4c37e0c26a813b985f657c105e945 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 18 Mar 2024 15:23:56 +1100 Subject: [PATCH 18/32] Adjust toolbar button label if pinned --- .../grid-interactivity/grid-item-pin-toolbar-item.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js index cadeae9398417..eeb4b2315a0cf 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -56,7 +56,9 @@ export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { From 1726d593c05528b55351d4d979d5099f3e9a86b2 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 18 Mar 2024 16:19:01 +1100 Subject: [PATCH 19/32] Fix visualizer not appearing when block inspector is closed --- packages/block-editor/src/layouts/grid.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 607e7e116f6c8..44ba127d00e76 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -68,7 +68,6 @@ export default { inspectorControls: function GridLayoutInspectorControls( { layout = {}, onChange, - clientId, } ) { return ( <> @@ -87,14 +86,14 @@ export default { onChange={ onChange } /> ) } - { window.__experimentalEnableGridInteractivity && ( - - ) } ); }, - toolBarControls: function GridLayoutToolbarControls() { - return null; + toolBarControls: function GridLayoutToolbarControls( { clientId } ) { + if ( ! window.__experimentalEnableGridInteractivity ) { + return null; + } + return ; }, getLayoutStyle: function getLayoutStyle( { selector, From 049606045b253a4518ea9f45288c99393b2cb51c Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 19 Mar 2024 14:39:59 +1100 Subject: [PATCH 20/32] Fix dragging in Chrome --- .../grid-interactivity/grid-visualizer.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 76e35666b7d27..9c528cc26d483 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -16,7 +16,6 @@ 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, Rect } from './utils'; -import { parseDropEvent } from '../use-on-block-drop'; import { store as blockEditorStore } from '../../store'; export function GridVisualizer( { clientId } ) { @@ -196,27 +195,26 @@ function GridVisualizerCell( { onDragLeave, onDrop, } ) { + const { getDraggedBlockClientIds } = useSelect( blockEditorStore ); + const ref = useDropZone( { - onDragEnter( event ) { - const { - srcClientIds: [ srcClientId ], - } = parseDropEvent( event ); - if ( validateDrag( srcClientId ) ) { + onDragEnter() { + const [ srcClientId ] = getDraggedBlockClientIds(); + if ( srcClientId && validateDrag( srcClientId ) ) { onDragEnter( srcClientId ); } }, onDragLeave() { onDragLeave(); }, - onDrop( event ) { - const { - srcClientIds: [ srcClientId ], - } = parseDropEvent( event ); - if ( validateDrag( srcClientId ) ) { + onDrop() { + const [ srcClientId ] = getDraggedBlockClientIds(); + if ( srcClientId && validateDrag( srcClientId ) ) { onDrop( srcClientId ); } }, } ); + return (
Date: Thu, 11 Apr 2024 16:41:21 +1000 Subject: [PATCH 21/32] Unpin when re-ordering blocks in the block list --- .../src/components/use-on-block-drop/index.js | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index 58ce615436d8e..0e9ad67204199 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -336,14 +336,40 @@ export default function useOnBlockDrop( const moveBlocks = useCallback( ( sourceClientIds, sourceRootClientId, insertIndex ) => { + const sourceBlocks = getBlocksByClientId( sourceClientIds ); + + // TODO: Figure out a better place for this. useOnBlockDrop shouldn't care about layout styles. + const unpinBlock = () => { + updateBlockAttributes( + sourceClientIds, + sourceBlocks.reduce( ( accumulator, sourceBlock ) => { + if ( + sourceBlock.attributes.style?.layout?.columnStart || + sourceBlock.attributes.style?.layout?.rowStart + ) { + const { columnStart, rowStart, ...layout } = + sourceBlock.attributes.style.layout; + accumulator[ sourceBlock.clientId ] = { + style: { + ...sourceBlock.attributes.style, + layout, + }, + }; + } + return accumulator; + }, {} ), + /* uniqueByBlock: */ true + ); + }; + if ( operation === 'replace' ) { - const sourceBlocks = getBlocksByClientId( sourceClientIds ); const targetBlockClientIds = getBlockOrder( targetRootClientId ); const targetBlockClientId = targetBlockClientIds[ targetBlockIndex ]; registry.batch( () => { + unpinBlock(); // Remove the source blocks. removeBlocks( sourceClientIds, false ); // Replace the target block with the source blocks. @@ -355,12 +381,15 @@ export default function useOnBlockDrop( ); } ); } else { - moveBlocksToPosition( - sourceClientIds, - sourceRootClientId, - targetRootClientId, - insertIndex - ); + registry.batch( () => { + unpinBlock(); + moveBlocksToPosition( + sourceClientIds, + sourceRootClientId, + targetRootClientId, + insertIndex + ); + } ); } }, [ From c3d1c1f8c6bd53ac5784ce9139df99a84a8ebe71 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 15 Apr 2024 16:07:23 +1000 Subject: [PATCH 22/32] Disable dragging a block onto an occupied cell --- .../grid-item-pin-toolbar-item.js | 25 +--- .../grid-interactivity/grid-item-resizer.js | 46 ++----- .../grid-interactivity/grid-visualizer.js | 71 ++++++----- .../components/grid-interactivity/utils.js | 116 +++++++++++++----- 4 files changed, 147 insertions(+), 111 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js index eeb4b2315a0cf..d91cc85cf895d 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -10,7 +10,7 @@ import { pin as pinIcon } from '@wordpress/icons'; */ import BlockControls from '../block-controls'; import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import { getComputedCSS, getGridTracks, getClosestTrack } from './utils'; +import { calculateGridRect } from './utils'; export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -28,26 +28,13 @@ export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { } function pinBlock() { - const gridElement = blockElement.parentElement; - const columnGap = parseFloat( - getComputedCSS( gridElement, 'column-gap' ) + const rect = calculateGridRect( + blockElement.parentElement, + blockElement ); - 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, blockElement.offsetLeft ) + 1; - const rowStart = - getClosestTrack( gridRowTracks, blockElement.offsetTop ) + 1; onChange( { - columnStart, - rowStart, + columnStart: rect.columnStart, + rowStart: rect.rowStart, } ); } diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js index b663c8fd6617e..fc0ea67a8e241 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js @@ -8,7 +8,7 @@ import { ResizableBox } from '@wordpress/components'; */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { getComputedCSS, getGridTracks, getClosestTrack } from './utils'; +import { calculateGridRect } from './utils'; export function GridItemResizer( { clientId, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -38,42 +38,18 @@ export function GridItemResizer( { clientId, onChange } ) { topRight: false, } } onResizeStop={ ( event, direction, boxElement ) => { - const gridElement = blockElement.parentElement; - 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, - blockElement.offsetLeft - ); - const rowStart = getClosestTrack( - gridRowTracks, - blockElement.offsetTop - ); - const columnEnd = getClosestTrack( - gridColumnTracks, - blockElement.offsetLeft + boxElement.offsetWidth, - 'end' - ); - const rowEnd = getClosestTrack( - gridRowTracks, - blockElement.offsetTop + boxElement.offsetHeight, - 'end' + const rect = calculateGridRect( + blockElement.parentElement, + new window.DOMRect( + blockElement.offsetLeft, + blockElement.offsetTop, + boxElement.offsetWidth, + boxElement.offsetHeight + ) ); onChange( { - columnSpan: columnEnd - columnStart + 1, - rowSpan: rowEnd - rowStart + 1, + columnSpan: rect.columnSpan, + rowSpan: rect.rowSpan, } ); } } /> diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 9c528cc26d483..c628ef85a297a 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/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 { getComputedCSS, range, Rect } from './utils'; +import { getComputedCSS, range, GridRect, calculateGridRect } from './utils'; import { store as blockEditorStore } from '../../store'; export function GridVisualizer( { clientId } ) { @@ -114,43 +114,57 @@ function GridVisualizerGrid( { clientId, blockElement } ) { { + const isCellOccupied = Array.from( + blockElement.children + ).some( ( childElement ) => { + const rect = calculateGridRect( + blockElement, + childElement + ); + return rect.contains( column, row ); + } ); + if ( isCellOccupied ) { + return false; + } + const attributes = getBlockAttributes( srcClientId ); - const columnSpan = - attributes.style?.layout?.columnSpan ?? 1; - const rowSpan = - attributes.style?.layout?.rowSpan ?? 1; - return new Rect( { - width: gridInfo.numColumns, - height: gridInfo.numRows, + const isInBounds = new GridRect( { + columnSpan: gridInfo.numColumns, + rowSpan: gridInfo.numRows, } ).containsRect( - new Rect( { - x: column - 1, - y: row - 1, - width: columnSpan, - height: rowSpan, + new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout?.rowSpan, } ) ); + if ( ! isInBounds ) { + return false; + } + + return true; } } onDragEnter={ ( srcClientId ) => { const attributes = getBlockAttributes( srcClientId ); - const columnSpan = - attributes.style?.layout?.columnSpan ?? 1; - const rowSpan = - attributes.style?.layout?.rowSpan ?? 1; setHighlightedRect( - new Rect( { - x: column - 1, - y: row - 1, - width: columnSpan, - height: rowSpan, + new GridRect( { + columnStart: column, + rowStart: row, + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout?.rowSpan, } ) ); } } @@ -159,8 +173,9 @@ function GridVisualizerGrid( { clientId, blockElement } ) { // their mouse quickly, so only clear the highlight if it was set // by this cell. setHighlightedRect( ( prevHighlightedRect ) => - prevHighlightedRect?.x === column - 1 && - prevHighlightedRect?.y === row - 1 + prevHighlightedRect?.columnStart === + column && + prevHighlightedRect?.rowStart === row ? null : prevHighlightedRect ); diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index 1943ed53dc503..3daecfed15c22 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -1,10 +1,62 @@ +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 ) + ); + } +} + export function getComputedCSS( element, property ) { return element.ownerDocument.defaultView .getComputedStyle( element ) .getPropertyValue( property ); } -export function getGridTracks( template, gap ) { +function getGridTracks( template, gap ) { const tracks = []; for ( const size of template.split( ' ' ) ) { const previousTrack = tracks[ tracks.length - 1 ]; @@ -15,7 +67,7 @@ export function getGridTracks( template, gap ) { return tracks; } -export function getClosestTrack( tracks, position, edge = 'start' ) { +function getClosestTrack( tracks, position, edge = 'start' ) { return tracks.reduce( ( closest, track, index ) => Math.abs( track[ edge ] - position ) < @@ -26,31 +78,37 @@ export function getClosestTrack( tracks, position, edge = 'start' ) { ); } -export function range( start, length ) { - return Array.from( { length }, ( _, i ) => start + i ); -} - -export class Rect { - constructor( { x = 0, y = 0, width = 1, height = 1 } = {} ) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - contains( x, y ) { - return ( - x >= this.x && - x < this.x + this.width && - y >= this.y && - y < this.y + this.height - ); - } - - containsRect( rect ) { - return ( - this.contains( rect.x, rect.y ) && - this.contains( rect.x + rect.width - 1, rect.y + rect.height - 1 ) - ); - } +export function calculateGridRect( gridElement, target ) { + 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 offsetRect = + target instanceof window.DOMRect + ? target + : new window.DOMRect( + target.offsetLeft, + target.offsetTop, + target.offsetWidth, + target.offsetHeight + ); + const columnStart = + getClosestTrack( gridColumnTracks, offsetRect.left ) + 1; + const rowStart = getClosestTrack( gridRowTracks, offsetRect.top ) + 1; + const columnEnd = + getClosestTrack( gridColumnTracks, offsetRect.right, 'end' ) + 1; + const rowEnd = + getClosestTrack( gridRowTracks, offsetRect.bottom, 'end' ) + 1; + return new GridRect( { + columnStart, + columnEnd, + rowStart, + rowEnd, + } ); } From ff98a89c59fd9602d88824083f6b9371091fed6c Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 16 Apr 2024 13:56:03 +1000 Subject: [PATCH 23/32] Split calculateGridRect --- .../grid-item-pin-toolbar-item.js | 7 ++-- .../grid-interactivity/grid-item-resizer.js | 4 +-- .../grid-interactivity/grid-visualizer.js | 8 ++--- .../components/grid-interactivity/utils.js | 33 ++++++++++--------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js index d91cc85cf895d..b1726d3c55c76 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -10,7 +10,7 @@ import { pin as pinIcon } from '@wordpress/icons'; */ import BlockControls from '../block-controls'; import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; -import { calculateGridRect } from './utils'; +import { getGridItemRect } from './utils'; export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -28,10 +28,7 @@ export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { } function pinBlock() { - const rect = calculateGridRect( - blockElement.parentElement, - blockElement - ); + const rect = getGridItemRect( blockElement ); onChange( { columnStart: rect.columnStart, rowStart: rect.rowStart, diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js index fc0ea67a8e241..f628f081286f7 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-resizer.js @@ -8,7 +8,7 @@ import { ResizableBox } from '@wordpress/components'; */ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import BlockPopoverCover from '../block-popover/cover'; -import { calculateGridRect } from './utils'; +import { getGridRect } from './utils'; export function GridItemResizer( { clientId, onChange } ) { const blockElement = useBlockElement( clientId ); @@ -38,7 +38,7 @@ export function GridItemResizer( { clientId, onChange } ) { topRight: false, } } onResizeStop={ ( event, direction, boxElement ) => { - const rect = calculateGridRect( + const rect = getGridRect( blockElement.parentElement, new window.DOMRect( blockElement.offsetLeft, diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index c628ef85a297a..7b481088d8ffe 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/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 { getComputedCSS, range, GridRect, calculateGridRect } from './utils'; +import { getComputedCSS, range, GridRect, getGridItemRect } from './utils'; import { store as blockEditorStore } from '../../store'; export function GridVisualizer( { clientId } ) { @@ -121,10 +121,8 @@ function GridVisualizerGrid( { clientId, blockElement } ) { const isCellOccupied = Array.from( blockElement.children ).some( ( childElement ) => { - const rect = calculateGridRect( - blockElement, - childElement - ); + const rect = + getGridItemRect( childElement ); return rect.contains( column, row ); } ); if ( isCellOccupied ) { diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index 3daecfed15c22..558337df4ce6e 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -78,7 +78,7 @@ function getClosestTrack( tracks, position, edge = 'start' ) { ); } -export function calculateGridRect( gridElement, target ) { +export function getGridRect( gridElement, rect ) { const columnGap = parseFloat( getComputedCSS( gridElement, 'column-gap' ) ); const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); const gridColumnTracks = getGridTracks( @@ -89,22 +89,11 @@ export function calculateGridRect( gridElement, target ) { getComputedCSS( gridElement, 'grid-template-rows' ), rowGap ); - const offsetRect = - target instanceof window.DOMRect - ? target - : new window.DOMRect( - target.offsetLeft, - target.offsetTop, - target.offsetWidth, - target.offsetHeight - ); - const columnStart = - getClosestTrack( gridColumnTracks, offsetRect.left ) + 1; - const rowStart = getClosestTrack( gridRowTracks, offsetRect.top ) + 1; + const columnStart = getClosestTrack( gridColumnTracks, rect.left ) + 1; + const rowStart = getClosestTrack( gridRowTracks, rect.top ) + 1; const columnEnd = - getClosestTrack( gridColumnTracks, offsetRect.right, 'end' ) + 1; - const rowEnd = - getClosestTrack( gridRowTracks, offsetRect.bottom, 'end' ) + 1; + getClosestTrack( gridColumnTracks, rect.right, 'end' ) + 1; + const rowEnd = getClosestTrack( gridRowTracks, rect.bottom, 'end' ) + 1; return new GridRect( { columnStart, columnEnd, @@ -112,3 +101,15 @@ export function calculateGridRect( gridElement, target ) { rowEnd, } ); } + +export function getGridItemRect( gridItemElement ) { + return getGridRect( + gridItemElement.parentElement, + new window.DOMRect( + gridItemElement.offsetLeft, + gridItemElement.offsetTop, + gridItemElement.offsetWidth, + gridItemElement.offsetHeight + ) + ); +} From 665684b8ef063acff7e5c6f7c8324dcb72d14428 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 16 Apr 2024 14:33:41 +1000 Subject: [PATCH 24/32] Move the block instead of setting a row/column start when possible --- .../grid-interactivity/grid-visualizer.js | 86 +++++++++++++++---- packages/block-editor/src/store/reducer.js | 2 +- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 7b481088d8ffe..00bacc3464be5 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -63,8 +63,9 @@ function GridVisualizerGrid( { clientId, blockElement } ) { const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); - const { getBlockAttributes } = useSelect( blockEditorStore ); - const { updateBlockAttributes } = useDispatch( blockEditorStore ); + const { getBlockAttributes, getBlockIndex } = useSelect( blockEditorStore ); + const { moveBlockToPosition, updateBlockAttributes } = + useDispatch( blockEditorStore ); useEffect( () => { const observers = []; @@ -120,10 +121,11 @@ function GridVisualizerGrid( { clientId, blockElement } ) { validateDrag={ ( srcClientId ) => { const isCellOccupied = Array.from( blockElement.children - ).some( ( childElement ) => { - const rect = - getGridItemRect( childElement ); - return rect.contains( column, row ); + ).some( ( child ) => { + return getGridItemRect( child ).contains( + column, + row + ); } ); if ( isCellOccupied ) { return false; @@ -179,18 +181,68 @@ function GridVisualizerGrid( { clientId, blockElement } ) { ); } } onDrop={ ( srcClientId ) => { - const attributes = - getBlockAttributes( srcClientId ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, - columnStart: column, - rowStart: row, - }, - }, + const leadingCellColumn = + column > 1 + ? column - 1 + : gridInfo.numColumns; + const leadingCellRow = + column > 1 ? row : row - 1; + const leadingGridItem = Array.from( + blockElement.children + ).find( ( child ) => { + return getGridItemRect( child ).contains( + leadingCellColumn, + leadingCellRow + ); } ); + const leadingBlockClientId = + leadingGridItem?.dataset.block; + + if ( + leadingBlockClientId && + leadingBlockClientId !== srcClientId + ) { + moveBlockToPosition( + srcClientId, + clientId, + clientId, + getBlockIndex( leadingBlockClientId ) + + 1 + ); + const attributes = + getBlockAttributes( srcClientId ); + if ( + attributes.style?.layout?.columnStart || + attributes.style?.layout?.rowStart + ) { + const { + columnStart, + rowStart, + ...layout + } = attributes.style.layout; + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout, + }, + } ); + } + } else { + // TODO: call moveBlockToPosition() here. + const attributes = + getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, + }, + } ); + } + setHighlightedRect( null ); } } /> diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 13024d4d2e8fa..5766c096faf53 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -884,7 +884,7 @@ export const blocks = pipe( for ( const clientId of action.clientIds ) { const updatedAttributeEntries = Object.entries( action.uniqueByBlock - ? action.attributes[ clientId ] + ? action.attributes[ clientId ] ?? {} : action.attributes ?? {} ); if ( updatedAttributeEntries.length === 0 ) { From 2a3e5888152177b64a5c77e28e8e99f4864cc8dd Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Apr 2024 10:07:32 +1000 Subject: [PATCH 25/32] Rename blockElement -> gridElement --- .../grid-interactivity/grid-visualizer.js | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 00bacc3464be5..90ad18045f9bf 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -19,25 +19,22 @@ import { getComputedCSS, range, GridRect, getGridItemRect } from './utils'; import { store as blockEditorStore } from '../../store'; export function GridVisualizer( { clientId } ) { - const blockElement = useBlockElement( clientId ); - if ( ! blockElement ) { + const gridElement = useBlockElement( clientId ); + if ( ! gridElement ) { return null; } return ( - + ); } -function getGridInfo( blockElement ) { +function getGridInfo( gridElement ) { const gridTemplateColumns = getComputedCSS( - blockElement, + gridElement, 'grid-template-columns' ); const gridTemplateRows = getComputedCSS( - blockElement, + gridElement, 'grid-template-rows' ); const numColumns = gridTemplateColumns.split( ' ' ).length; @@ -50,15 +47,15 @@ function getGridInfo( blockElement ) { style: { gridTemplateColumns, gridTemplateRows, - gap: getComputedCSS( blockElement, 'gap' ), - padding: getComputedCSS( blockElement, 'padding' ), + gap: getComputedCSS( gridElement, 'gap' ), + padding: getComputedCSS( gridElement, 'padding' ), }, }; } -function GridVisualizerGrid( { clientId, blockElement } ) { +function GridVisualizerGrid( { clientId, gridElement } ) { const [ gridInfo, setGridInfo ] = useState( () => - getGridInfo( blockElement ) + getGridInfo( gridElement ) ); const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); @@ -69,9 +66,9 @@ function GridVisualizerGrid( { clientId, blockElement } ) { useEffect( () => { const observers = []; - for ( const element of [ blockElement, ...blockElement.children ] ) { + for ( const element of [ gridElement, ...gridElement.children ] ) { const observer = new window.ResizeObserver( () => { - setGridInfo( getGridInfo( blockElement ) ); + setGridInfo( getGridInfo( gridElement ) ); } ); observer.observe( element ); observers.push( observer ); @@ -81,7 +78,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { observer.disconnect(); } }; - }, [ blockElement ] ); + }, [ gridElement ] ); useEffect( () => { function onGlobalDrag() { @@ -120,7 +117,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { } validateDrag={ ( srcClientId ) => { const isCellOccupied = Array.from( - blockElement.children + gridElement.children ).some( ( child ) => { return getGridItemRect( child ).contains( column, @@ -188,7 +185,7 @@ function GridVisualizerGrid( { clientId, blockElement } ) { const leadingCellRow = column > 1 ? row : row - 1; const leadingGridItem = Array.from( - blockElement.children + gridElement.children ).find( ( child ) => { return getGridItemRect( child ).contains( leadingCellColumn, From e675646baeff16ec4e20eb8e8cfb11deb03d94cc Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Apr 2024 10:20:09 +1000 Subject: [PATCH 26/32] Don't moveBlockToPosition() for now. The algorithm needs to be robust --- .../grid-interactivity/grid-visualizer.js | 77 ++++--------------- 1 file changed, 13 insertions(+), 64 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 90ad18045f9bf..a379bcc3d00e3 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -60,9 +60,8 @@ function GridVisualizerGrid( { clientId, gridElement } ) { const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); - const { getBlockAttributes, getBlockIndex } = useSelect( blockEditorStore ); - const { moveBlockToPosition, updateBlockAttributes } = - useDispatch( blockEditorStore ); + const { getBlockAttributes } = useSelect( blockEditorStore ); + const { updateBlockAttributes } = useDispatch( blockEditorStore ); useEffect( () => { const observers = []; @@ -178,68 +177,18 @@ function GridVisualizerGrid( { clientId, gridElement } ) { ); } } onDrop={ ( srcClientId ) => { - const leadingCellColumn = - column > 1 - ? column - 1 - : gridInfo.numColumns; - const leadingCellRow = - column > 1 ? row : row - 1; - const leadingGridItem = Array.from( - gridElement.children - ).find( ( child ) => { - return getGridItemRect( child ).contains( - leadingCellColumn, - leadingCellRow - ); - } ); - const leadingBlockClientId = - leadingGridItem?.dataset.block; - - if ( - leadingBlockClientId && - leadingBlockClientId !== srcClientId - ) { - moveBlockToPosition( - srcClientId, - clientId, - clientId, - getBlockIndex( leadingBlockClientId ) + - 1 - ); - const attributes = - getBlockAttributes( srcClientId ); - if ( - attributes.style?.layout?.columnStart || - attributes.style?.layout?.rowStart - ) { - const { - columnStart, - rowStart, - ...layout - } = attributes.style.layout; - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout, - }, - } ); - } - } else { - // TODO: call moveBlockToPosition() here. - const attributes = - getBlockAttributes( srcClientId ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, - columnStart: column, - rowStart: row, - }, + const attributes = + getBlockAttributes( srcClientId ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, }, - } ); - } - + }, + } ); setHighlightedRect( null ); } } /> From d63b85be4b794884f26e25ac54b9b7f03f05f962 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Apr 2024 10:28:57 +1000 Subject: [PATCH 27/32] Take span into account when validating drag --- .../grid-interactivity/grid-visualizer.js | 45 +++++++++---------- .../components/grid-interactivity/utils.js | 9 ++++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index a379bcc3d00e3..bbe68c0b29a04 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -115,38 +115,37 @@ function GridVisualizerGrid( { clientId, gridElement } ) { false } validateDrag={ ( srcClientId ) => { - const isCellOccupied = Array.from( - gridElement.children - ).some( ( child ) => { - return getGridItemRect( child ).contains( - column, - row - ); - } ); - if ( isCellOccupied ) { - return false; - } - 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( - new GridRect( { - columnStart: column, - rowStart: row, - columnSpan: - attributes.style?.layout - ?.columnSpan, - rowSpan: - attributes.style?.layout?.rowSpan, - } ) - ); + } ).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 ) => { diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index 558337df4ce6e..b377ce079084b 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -48,6 +48,15 @@ export class GridRect { 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 ) { From 073c281117ea9cf0f19e1ada4ecbd96fbb64c806 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Apr 2024 10:39:43 +1000 Subject: [PATCH 28/32] Don't put pin toolbar button in a group --- .../grid-item-pin-toolbar-item.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js index b1726d3c55c76..0f786738b06ff 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-item-pin-toolbar-item.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; +import { ToolbarButton } from '@wordpress/components'; import { pin as pinIcon } from '@wordpress/icons'; /** @@ -37,16 +37,14 @@ export function GridItemPinToolbarItem( { clientId, layout, onChange } ) { return ( - - - + ); } From ac5f050ebd98a1a841dc13222b9b1e78f6bcf419 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Apr 2024 10:45:37 +1000 Subject: [PATCH 29/32] Add translucent background to grid cells --- .../block-editor/src/components/grid-interactivity/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid-interactivity/style.scss b/packages/block-editor/src/components/grid-interactivity/style.scss index 2c9cec978b3aa..dfb3e57f84ae0 100644 --- a/packages/block-editor/src/components/grid-interactivity/style.scss +++ b/packages/block-editor/src/components/grid-interactivity/style.scss @@ -26,7 +26,8 @@ } .block-editor-grid-visualizer__drop-zone { - border: $border-width dashed $gray-300; + background: rgba($gray-400, 0.1); + border: $border-width dotted $gray-300; width: 100%; height: 100%; From 05b0392265f11692f23ae045d4e78fc736d73dd2 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Apr 2024 11:07:02 +1000 Subject: [PATCH 30/32] Fix input max --- .../src/components/child-layout-control/index.js | 15 ++++++++++++--- 1 file changed, 12 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 12360975a1d0c..eb2a02e5095d7 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -224,9 +224,11 @@ export default function ChildLayoutControl( { value={ columnStart } min={ 1 } max={ - parentLayout?.columnCount - - columnSpan + - 1 + parentLayout?.columnCount + ? parentLayout.columnCount - + ( columnSpan ?? 1 ) + + 1 + : undefined } /> @@ -245,6 +247,13 @@ export default function ChildLayoutControl( { } } value={ rowStart } min={ 1 } + max={ + parentLayout?.rowCount + ? parentLayout.rowCount - + ( rowSpan ?? 1 ) + + 1 + : undefined + } /> From b70baeeea1effadce65ed41f37fab7a50a7af4ab Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 19 Apr 2024 11:01:03 +1000 Subject: [PATCH 31/32] (Not fully working:) Call moveToPosition() when moving a block to its 'natural position' --- .../grid-interactivity/grid-visualizer.js | 169 ++++++++++++++++-- 1 file changed, 155 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index bbe68c0b29a04..84d23f1c5dea4 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -24,7 +24,10 @@ export function GridVisualizer( { clientId } ) { return null; } return ( - + ); } @@ -53,15 +56,90 @@ function getGridInfo( gridElement ) { }; } -function GridVisualizerGrid( { clientId, gridElement } ) { +// TODO: clean up these two funcs +// TODO: these two funcs don't properly handle the case where an item only has a row or a column set +// what we're supposed to do in that case is quite complicated: https://www.w3.org/TR/css-grid-1/#auto-placement-algo + +function getNaturalPosition( { + rects, + numColumns, + numRows, + columnStart, // if specified, constrain to this column + rowStart, // if specified, constrain to this row + columnSpan = 1, + rowSpan = 1, +} ) { + for ( let row = 1; row <= numRows; row++ ) { + for ( let column = 1; column <= numColumns; column++ ) { + const rect = new GridRect( { + columnStart: columnStart ?? column, + rowStart: rowStart ?? row, + columnSpan, + rowSpan, + } ); + if ( + ! rects.some( ( otherRect ) => + rect.intersectsRect( otherRect ) + ) + ) { + return { column, row }; + } + } + } + return null; +} + +function getRects( { innerBlocks, numColumns, numRows } ) { + const rects = []; + + for ( const block of innerBlocks ) { + const layout = block.attributes.style?.layout; + if ( layout.columnStart && layout.rowStart ) { + const rect = new GridRect( { + columnStart: layout.columnStart, + rowStart: layout.rowStart, + columnSpan: layout.columnSpan ?? 1, + rowSpan: layout.rowSpan ?? 1, + } ); + rects.push( rect ); + } + } + + for ( const block of innerBlocks ) { + const layout = block.attributes.style?.layout; + if ( ! layout.columnStart || ! layout.rowStart ) { + const naturalPosition = getNaturalPosition( { + rects, + numColumns, + numRows, + columnStart: layout.columnStart, + rowStart: layout.rowStart, + columnSpan: layout.columnSpan ?? 1, + rowSpan: layout.rowSpan ?? 1, + } ); + const rect = new GridRect( { + columnStart: naturalPosition.column, + rowStart: naturalPosition.row, + columnSpan: layout.columnSpan ?? 1, + rowSpan: layout.rowSpan ?? 1, + } ); + rects.push( rect ); + } + } + + return rects; +} + +function GridVisualizerGrid( { gridClientId, 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 ); + const { getBlockAttributes, getBlocks } = useSelect( blockEditorStore ); + const { updateBlockAttributes, moveBlockToPosition } = + useDispatch( blockEditorStore ); useEffect( () => { const observers = []; @@ -99,7 +177,7 @@ function GridVisualizerGrid( { clientId, gridElement } ) { className={ classnames( 'block-editor-grid-visualizer', { 'is-dropping-allowed': isDroppingAllowed, } ) } - clientId={ clientId } + clientId={ gridClientId } __unstablePopoverSlot="block-toolbar" >
{ + // TODO: this is messy + const attributes = getBlockAttributes( srcClientId ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, - columnStart: column, - rowStart: row, - }, - }, + + const blocks = getBlocks( gridClientId ).filter( + ( { clientId } ) => clientId !== srcClientId + ); + const rects = getRects( { + innerBlocks: blocks, + numColumns: gridInfo.numColumns, + numRows: gridInfo.numRows, + } ); + console.log( 'rects', rects ); + const naturalPosition = getNaturalPosition( { + rects, + numColumns: gridInfo.numColumns, + numRows: gridInfo.numRows, + columnSpan: + attributes.style?.layout?.columnSpan, + rowSpan: attributes.style?.layout?.rowSpan, } ); + console.log( + 'naturalPosition', + naturalPosition + ); + + if ( + column === naturalPosition?.column && + row === naturalPosition?.row + ) { + console.log( + 'moveBlockToPosition', + blocks.length + ); + moveBlockToPosition( + srcClientId, + gridClientId, + gridClientId, + blocks.length + ); + if ( + attributes.style?.layout?.columnStart || + attributes.style?.layout?.rowStart + ) { + const { + columnStart, + rowStart, + ...layout + } = attributes.style.layout; + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout, + }, + } ); + } + } else { + console.log( + 'updateBlockAttributes', + column, + row + ); + updateBlockAttributes( srcClientId, { + style: { + ...attributes.style, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, + }, + } ); + } + setHighlightedRect( null ); } } /> From 05900ce0c733506c006b0395f9dc834ed1a9a4d3 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 19 Apr 2024 14:09:46 +1000 Subject: [PATCH 32/32] Very very rough crack at drag to insert --- .../grid-interactivity/grid-visualizer.js | 428 ++++++++++++------ .../components/grid-interactivity/utils.js | 43 +- packages/block-library/src/group/edit.js | 2 +- 3 files changed, 330 insertions(+), 143 deletions(-) diff --git a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js index 84d23f1c5dea4..3ab9cc0c84515 100644 --- a/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js +++ b/packages/block-editor/src/components/grid-interactivity/grid-visualizer.js @@ -9,14 +9,23 @@ import classnames from 'classnames'; import { useState, useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; +import { Popover } 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 { getComputedCSS, range, GridRect, getGridItemRect } from './utils'; +import { + getComputedCSS, + range, + GridRect, + getGridItemRect, + getGridCell, + getClosestGridCell, +} from './utils'; import { store as blockEditorStore } from '../../store'; +import QuickInserter from '../inserter/quick-inserter'; export function GridVisualizer( { clientId } ) { const gridElement = useBlockElement( clientId ); @@ -136,6 +145,7 @@ function GridVisualizerGrid( { gridClientId, gridElement } ) { ); const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); const [ highlightedRect, setHighlightedRect ] = useState( null ); + const [ insertion, setInsertion ] = useState( false ); const { getBlockAttributes, getBlocks } = useSelect( blockEditorStore ); const { updateBlockAttributes, moveBlockToPosition } = @@ -150,10 +160,113 @@ function GridVisualizerGrid( { gridClientId, gridElement } ) { observer.observe( element ); observers.push( observer ); } + + let dragStartCell = null; + + function onMouseDown( event ) { + if ( event.target !== gridElement ) { + return; + } + const { column, row } = getGridCell( + gridElement, + event.offsetX, + event.offsetY + ); + if ( column && row ) { + dragStartCell = { + column, + row, + }; + } + } + function onMouseUp( event ) { + if ( event.target !== gridElement ) { + return; + } + const { column, row } = getGridCell( + gridElement, + event.offsetX, + event.offsetY + ); + if ( dragStartCell && column && row ) { + const rect = new GridRect( { + columnStart: Math.min( dragStartCell.column, column ), + rowStart: Math.min( dragStartCell.row, row ), + columnSpan: Math.abs( dragStartCell.column - column ) + 1, + rowSpan: Math.abs( dragStartCell.row - row ) + 1, + } ); + const anchor = { + getBoundingClientRect() { + return new window.DOMRect( + event.clientX, + event.clientY, + 0, + 0 + ); + }, + ownerDocument: event.target.ownerDocument, + }; + setInsertion( { rect, anchor } ); + } + dragStartCell = null; + } + function onMouseMove( event ) { + if ( event.target !== gridElement ) { + return; + } + if ( dragStartCell ) { + const { column, row } = getClosestGridCell( + gridElement, + event.offsetX, + event.offsetY + ); + setHighlightedRect( + column && row + ? new GridRect( { + columnStart: Math.min( + dragStartCell.column, + column + ), + rowStart: Math.min( dragStartCell.row, row ), + columnSpan: + Math.abs( dragStartCell.column - column ) + + 1, + rowSpan: + Math.abs( dragStartCell.row - row ) + 1, + } ) + : new GridRect( { + columnStart: dragStartCell.column, + rowStart: dragStartCell.row, + } ) + ); + } else { + const { column, row } = getGridCell( + gridElement, + event.offsetX, + event.offsetY + ); + setHighlightedRect( + column && row + ? new GridRect( { + columnStart: column, + rowStart: row, + } ) + : null + ); + } + } + gridElement.addEventListener( 'mousedown', onMouseDown ); + gridElement.addEventListener( 'mouseup', onMouseUp ); + gridElement.addEventListener( 'mousemove', onMouseMove ); + return () => { for ( const observer of observers ) { observer.disconnect(); } + + gridElement.removeEventListener( 'mousedown', onMouseDown ); + gridElement.removeEventListener( 'mouseup', onMouseUp ); + gridElement.removeEventListener( 'mousemove', onMouseMove ); }; }, [ gridElement ] ); @@ -173,64 +286,57 @@ function GridVisualizerGrid( { gridClientId, gridElement } ) { }, [] ); return ( - -
+ { insertion && ( + + { + updateBlockAttributes( block.clientId, { + style: { + ...block.attributes.style, + layout: { + ...block.attributes.style?.layout, + columnStart: insertion.rect.columnStart, + rowStart: insertion.rect.rowStart, + columnSpan: insertion.rect.columnSpan, + rowSpan: insertion.rect.rowSpan, + }, + }, + } ); + setInsertion( null ); + } } + /> + + ) } + - { 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.numRows ).map( ( row ) => + range( 1, gridInfo.numColumns ).map( ( column ) => ( + { - const attributes = - getBlockAttributes( srcClientId ); - setHighlightedRect( - new GridRect( { + validateDrag={ ( srcClientId ) => { + const attributes = + getBlockAttributes( srcClientId ); + const rect = new GridRect( { columnStart: column, rowStart: row, columnSpan: @@ -238,104 +344,144 @@ function GridVisualizerGrid( { gridClientId, gridElement } ) { ?.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 ) => { - // TODO: this is messy + } ); - const attributes = - getBlockAttributes( srcClientId ); + const isInBounds = new GridRect( { + columnSpan: gridInfo.numColumns, + rowSpan: gridInfo.numRows, + } ).containsRect( rect ); + if ( ! isInBounds ) { + return false; + } - const blocks = getBlocks( gridClientId ).filter( - ( { clientId } ) => clientId !== srcClientId - ); - const rects = getRects( { - innerBlocks: blocks, - numColumns: gridInfo.numColumns, - numRows: gridInfo.numRows, - } ); - console.log( 'rects', rects ); - const naturalPosition = getNaturalPosition( { - rects, - numColumns: gridInfo.numColumns, - numRows: gridInfo.numRows, - columnSpan: - attributes.style?.layout?.columnSpan, - rowSpan: attributes.style?.layout?.rowSpan, - } ); - console.log( - 'naturalPosition', - naturalPosition - ); + const isOverlapping = Array.from( + gridElement.children + ).some( + ( child ) => + child.dataset.block !== + srcClientId && + rect.intersectsRect( + getGridItemRect( child ) + ) + ); + if ( isOverlapping ) { + return false; + } - if ( - column === naturalPosition?.column && - row === naturalPosition?.row - ) { - console.log( - 'moveBlockToPosition', - blocks.length + 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, + } ) ); - moveBlockToPosition( - srcClientId, - gridClientId, - gridClientId, - blocks.length + } } + 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 ) => { + // TODO: this is messy + + const attributes = + getBlockAttributes( srcClientId ); + + const blocks = getBlocks( + gridClientId + ).filter( + ( { clientId } ) => + clientId !== srcClientId + ); + const rects = getRects( { + innerBlocks: blocks, + numColumns: gridInfo.numColumns, + numRows: gridInfo.numRows, + } ); + const naturalPosition = getNaturalPosition( + { + rects, + numColumns: gridInfo.numColumns, + numRows: gridInfo.numRows, + columnSpan: + attributes.style?.layout + ?.columnSpan, + rowSpan: + attributes.style?.layout + ?.rowSpan, + } + ); + if ( - attributes.style?.layout?.columnStart || - attributes.style?.layout?.rowStart + column === naturalPosition?.column && + row === naturalPosition?.row ) { - const { - columnStart, - rowStart, - ...layout - } = attributes.style.layout; + moveBlockToPosition( + srcClientId, + gridClientId, + gridClientId, + blocks.length + ); + if ( + attributes.style?.layout + ?.columnStart || + attributes.style?.layout?.rowStart + ) { + const { + columnStart, + rowStart, + ...layout + } = attributes.style.layout; + updateBlockAttributes( + srcClientId, + { + style: { + ...attributes.style, + layout, + }, + } + ); + } + } else { updateBlockAttributes( srcClientId, { style: { ...attributes.style, - layout, + layout: { + ...attributes.style?.layout, + columnStart: column, + rowStart: row, + }, }, } ); } - } else { - console.log( - 'updateBlockAttributes', - column, - row - ); - updateBlockAttributes( srcClientId, { - style: { - ...attributes.style, - layout: { - ...attributes.style?.layout, - columnStart: column, - rowStart: row, - }, - }, - } ); - } - setHighlightedRect( null ); - } } - /> - ) ) - ) } -
-
+ setHighlightedRect( null ); + } } + /> + ) ) + ) } +
+
+ ); } diff --git a/packages/block-editor/src/components/grid-interactivity/utils.js b/packages/block-editor/src/components/grid-interactivity/utils.js index b377ce079084b..09d6cbe3fb45a 100644 --- a/packages/block-editor/src/components/grid-interactivity/utils.js +++ b/packages/block-editor/src/components/grid-interactivity/utils.js @@ -71,7 +71,8 @@ function getGridTracks( template, gap ) { const previousTrack = tracks[ tracks.length - 1 ]; const start = previousTrack ? previousTrack.end + gap : 0; const end = start + parseFloat( size ); - tracks.push( { start, end } ); + const center = ( start + end ) / 2; + tracks.push( { start, end, center } ); } return tracks; } @@ -87,6 +88,46 @@ function getClosestTrack( tracks, position, edge = 'start' ) { ); } +export function getGridCell( gridElement, x, y ) { + 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 columnIndex = gridColumnTracks.findIndex( + ( track ) => x >= track.start && x < track.end + ); + const rowIndex = gridRowTracks.findIndex( + ( track ) => y >= track.start && y < track.end + ); + const column = columnIndex === -1 ? null : columnIndex + 1; + const row = rowIndex === -1 ? null : rowIndex + 1; + return { column, row }; +} + +export function getClosestGridCell( gridElement, x, y ) { + 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 columnIndex = getClosestTrack( gridColumnTracks, x, 'center' ); + const rowIndex = getClosestTrack( gridRowTracks, y, 'center' ); + const column = columnIndex === -1 ? null : columnIndex + 1; + const row = rowIndex === -1 ? null : rowIndex + 1; + return { column, row }; +} + export function getGridRect( gridElement, rect ) { const columnGap = parseFloat( getComputedCSS( gridElement, 'column-gap' ) ); const rowGap = parseFloat( getComputedCSS( gridElement, 'row-gap' ) ); diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index a763bc95e60d7..2b4db947da068 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -109,7 +109,7 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { // Default to the regular appender being rendered. let renderAppender; - if ( showPlaceholder ) { + if ( showPlaceholder || type === 'grid' ) { // 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