Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0bd9683
Use DropZones in GridVisualizer to set positions of grid items
noisysocks Feb 29, 2024
4be75af
Remove text and icon from drop zone
noisysocks Feb 29, 2024
8d0160a
Don't obscure the in-between block drop zone
noisysocks Feb 29, 2024
59c3994
Output grid-start styling
noisysocks Feb 29, 2024
81f6936
Output grid-start styling
noisysocks Feb 29, 2024
ed4483a
Add Column Start and Row Start controls to block inspector
noisysocks Feb 29, 2024
b8e9a33
Merge branch 'add/grid-column-and-row-start' into add/grid-interactiv…
noisysocks Feb 29, 2024
2fb3f1b
Rename grid-visualizer dir to grid-interactivity
noisysocks Mar 1, 2024
f68c715
Use specificity instead of !important
noisysocks Mar 1, 2024
402eddc
Add 'Pin to grid' button
noisysocks Mar 1, 2024
8eaa61a
Remove weird blank line
noisysocks Mar 1, 2024
577a7eb
Merge remote-tracking branch 'origin/trunk' into add/grid-interactivi…
noisysocks Mar 14, 2024
bfe3042
Remove pin button for now
noisysocks Mar 14, 2024
e62f090
Revert "Remove pin button for now"
noisysocks Mar 15, 2024
f6a9e0a
Make drop zone 8x8 at minimum
noisysocks Mar 15, 2024
7dcc874
Improve handling of resizing onto empty cells
noisysocks Mar 15, 2024
81463b4
Don't allow blocks with a >1 span to be dragged too close to the edge
noisysocks Mar 18, 2024
8fb6fd6
Delete accidental comment
noisysocks Mar 18, 2024
c9638ec
Treat a block as pinned if it has a column start **or** a row start
noisysocks Mar 18, 2024
5f4aaeb
Adjust toolbar button label if pinned
noisysocks Mar 18, 2024
1726d59
Fix visualizer not appearing when block inspector is closed
noisysocks Mar 18, 2024
0496060
Fix dragging in Chrome
noisysocks Mar 19, 2024
3ff1f86
Merge remote-tracking branch 'origin/trunk' into add/grid-interactivi…
noisysocks Apr 11, 2024
8c1e9ee
Unpin when re-ordering blocks in the block list
noisysocks Apr 11, 2024
c3d1c1f
Disable dragging a block onto an occupied cell
noisysocks Apr 15, 2024
ff98a89
Split calculateGridRect
noisysocks Apr 16, 2024
665684b
Move the block instead of setting a row/column start when possible
noisysocks Apr 16, 2024
9d5940f
Merge remote-tracking branch 'origin/trunk' into add/grid-interactivi…
noisysocks Apr 16, 2024
2a3e588
Rename blockElement -> gridElement
noisysocks Apr 17, 2024
e675646
Don't moveBlockToPosition() for now. The algorithm needs to be robust
noisysocks Apr 17, 2024
d63b85b
Take span into account when validating drag
noisysocks Apr 17, 2024
073c281
Don't put pin toolbar button in a group
noisysocks Apr 17, 2024
ac5f050
Add translucent background to grid cells
noisysocks Apr 17, 2024
05b0392
Fix input max
noisysocks Apr 17, 2024
b70baee
(Not fully working:) Call moveToPosition() when moving a block to its…
noisysocks Apr 19, 2024
05900ce
Very very rough crack at drag to insert
noisysocks Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 (
<BlockControls group="parent">
<ToolbarGroup>
<ToolbarButton
icon={ pinIcon }
label={ __( 'Pin to grid' ) }
isPressed={ isPinned }
onClick={ isPinned ? unpinBlock : pinBlock }
/>
</ToolbarGroup>
</BlockControls>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -78,23 +78,3 @@ export function GridItemResizer( { clientId, onChange } ) {
</BlockPopoverCover>
);
}

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
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useState, useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose';

/**
* Internal dependencies
*/
import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
import BlockPopoverCover from '../block-popover/cover';
import { getComputedCSS } from './utils';
import { parseDropEvent } from '../use-on-block-drop';
import { store as blockEditorStore } from '../../store';

export function GridVisualizer( { clientId } ) {
const blockElement = useBlockElement( clientId );
if ( ! blockElement ) {
return null;
}
return (
<GridVisualizerGrid
clientId={ clientId }
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( { clientId, blockElement } ) {
const [ gridInfo, setGridInfo ] = useState( () =>
getGridInfo( blockElement )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the new kid: this is so the visualizer matches the underlying grid when resizing the window? Works really well. Nice 🤟🏻

Feel free to ignore this opinion: "Info" sounds to me like something I'd ask for at a truck stop on my way to an obscure holiday township with no mobile reception.

Just jokes. What about 'properties'? E.g., getBlockElementGridProperties

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does handle window resizing but that's secondary. The primary purpose is so that we recalculate the grid lines when the grid or its children change size e.g. if the user changes the number of columns in the inspector.

I didn't want to use the words attributes or properties because I didn't want to imply that we're returning HTML attributes, React props, or CSS properties. I think a generic word like data or info is right. But idk happy to do what you say here 🤷‍♂️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good! Just a passing comment with as much value as it cost in the first place = $0 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to ask clarifying questions, it means they're answered for future readers too 🙂

);
const [ hasCellPointerEvents, setHasCellPointerEvents ] = useState( false );

useEffect( () => {
const observers = [];
for ( const element of [ blockElement, ...blockElement.children ] ) {
const observer = new window.ResizeObserver( () => {
setGridInfo( getGridInfo( blockElement ) );
} );
observer.observe( element );
observers.push( observer );
}
return () => {
for ( const observer of observers ) {
observer.disconnect();
}
};
}, [ blockElement ] );

useEffect( () => {
function onGlobalDrag() {
setHasCellPointerEvents( true );
}
function onGlobalDragEnd() {
setHasCellPointerEvents( false );
}
document.addEventListener( 'drag', onGlobalDrag );
document.addEventListener( 'dragend', onGlobalDragEnd );
return () => {
document.removeEventListener( 'drag', onGlobalDrag );
document.removeEventListener( 'dragend', onGlobalDragEnd );
};
}, [] );

return (
<BlockPopoverCover
className={ classnames( 'block-editor-grid-visualizer', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere a little z-index jig in mobile view is required as it sits over the sidebar

2024-03-15.12.09.46.mp4

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Why is this happening? When I look in the inspector the visualiser has z-index = 30 and the list view has z-index = 10000 😕

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect they're in different stacking contexts? Or a new one needs to be created around the grid visualizer.

This tool is pretty handy to work it out: https://github.com/gwwar/z-context

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah they're in different stacking contexts but what's causing the issue is the popover content moving to the bottom of the DOM when the viewport is smaller than 782px:

Screenshot 2024-03-18 at 5 07 37 PM

I'm not sure what the role of components-popover__fallback-container is in popover logic, but it appears after the sidebar and is a sibling of #wpwrap so I suspect the only way to fix this with CSS would be to assign a non-negative z-index to #wpwrap. I doubt that would be free of consequences though 😅

'has-cell-pointer-events': hasCellPointerEvents,
} ) }
clientId={ clientId }
__unstablePopoverSlot="block-toolbar"
>
<div
className="block-editor-grid-visualizer__grid"
style={ gridInfo.style }
>
{ range( 1, gridInfo.numRows ).map( ( row ) =>
range( 1, gridInfo.numColumns ).map( ( column ) => (
<GridVisualizerCell
key={ `${ row }-${ column }` }
column={ column }
row={ row }
/>
) )
) }
</div>
</BlockPopoverCover>
);
}

function GridVisualizerCell( { column, row } ) {
const [ isDraggingOver, setIsDraggingOver ] = useState( false );
const { getBlockAttributes } = useSelect( blockEditorStore );
const { updateBlockAttributes } = useDispatch( blockEditorStore );

const ref = useDropZone( {
onDragEnter() {
setIsDraggingOver( true );
},
onDragLeave() {
setIsDraggingOver( false );
},
onDrop( event ) {
setIsDraggingOver( false );
const {
srcClientIds: [ srcClientId ],
} = parseDropEvent( event );
if ( ! srcClientId ) {
return;
}
const attributes = getBlockAttributes( srcClientId );
updateBlockAttributes( srcClientId, {
style: {
...attributes.style,
layout: {
...attributes.style?.layout,
columnStart: column,
rowStart: row,
},
},
} );
},
} );

return (
<div
ref={ ref }
className={ classnames( 'block-editor-grid-visualizer__cell', {
'is-dragging-over': isDraggingOver,
} ) }
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { GridVisualizer } from './grid-visualizer';
export { GridItemResizer } from './grid-item-resizer';
export { GridItemPinToolbarItem } from './grid-item-pin-toolbar-item';
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.block-editor-grid-visualizer {
// Specificity to override the z-index and pointer-events set by .components-popover.
&.block-editor-grid-visualizer.block-editor-grid-visualizer {
z-index: z-index(".block-editor-grid-visualizer");

.components-popover__content * {
pointer-events: none;
}

&.has-cell-pointer-events {
.block-editor-grid-visualizer__cell {
pointer-events: all;
}
}
}
}

.block-editor-grid-visualizer__grid {
display: grid;
}

.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 {
// Specificity to override the z-index and pointer-events set by .components-popover.
&.block-editor-grid-item-resizer.block-editor-grid-item-resizer {
z-index: z-index(".block-editor-grid-visualizer");

.components-popover__content * {
pointer-events: none;
}
}
}

.block-editor-grid-item-resizer__box {
border: $border-width solid var(--wp-admin-theme-color);

.components-resizable-box__handle {
// Specificity to override the pointer-events set by .components-popover.
&.components-resizable-box__handle.components-resizable-box__handle {
pointer-events: all;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export function getComputedCSS( element, property ) {
return element.ownerDocument.defaultView
.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
);
}
Loading