Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Grid interactivity #59052

Merged
merged 20 commits into from Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
956b676
Add grid visualization
noisysocks Feb 11, 2024
c7f6986
Show grid visualization when child is selected
noisysocks Feb 14, 2024
185dc1a
Allow dragging to set column and row span
noisysocks Feb 14, 2024
3ce2c39
Don't need this ref
noisysocks Feb 14, 2024
895b33d
Just disable left/top resizing for now
noisysocks Feb 15, 2024
088cf0f
Clean up CSS
noisysocks Feb 15, 2024
a8deab4
Merge remote-tracking branch 'origin/trunk' into try/interactive-grid…
noisysocks Feb 19, 2024
0a22979
Add shift=false to popovers
noisysocks Feb 19, 2024
0bd1e2c
Accommodate variably sized columns/rows by using grid-template to cal…
noisysocks Feb 19, 2024
4db7ab0
BlockPopover: Use ResizeObserver to match size of covered block
noisysocks Feb 19, 2024
e6567ab
Fix error due to undefined selectedElement
noisysocks Feb 20, 2024
a036614
Update GridVisualizer when grid or its children resize
noisysocks Feb 20, 2024
df65b36
Add experimental flag
noisysocks Feb 20, 2024
cbc5b50
Fix formatting
noisysocks Feb 21, 2024
e1c2c92
Go away spaces
noisysocks Feb 21, 2024
f0c111a
BlockPopover: Remove __unstableRefreshSize prop and improve how __uns…
noisysocks Feb 21, 2024
4e50848
BlockPopover: Remove __unstableCoverTarget in favour of BlockPopoverC…
noisysocks Feb 21, 2024
e48fe99
Merge branch 'update/remove-__unstableRefreshSize' into try/interacti…
noisysocks Feb 21, 2024
fad46c9
Merge remote-tracking branch 'origin/trunk' into try/interactive-grid…
noisysocks Feb 22, 2024
fcdbc90
Use BlockPopoverCover
noisysocks Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/base-styles/_z-index.scss
Expand Up @@ -108,6 +108,9 @@ $z-layers: (
// Above the block list and the header.
".block-editor-block-popover": 31,

// Below the block toolbar.
".block-editor-grid-visualizer": 30,

// Show snackbars above everything (similar to popovers)
".components-snackbar-list": 100000,

Expand Down
@@ -0,0 +1,101 @@
/**
* WordPress dependencies
*/
import { ResizableBox } from '@wordpress/components';
import { useRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
import BlockPopover from '../block-popover';
import { getComputedCSS } from './utils';

export function GridItemResizer( { clientId, onChange } ) {
const resizableRef = useRef();
const blockElement = useBlockElement( clientId );
if ( ! blockElement ) {
return null;
}
return (
<BlockPopover
className="block-editor-grid-item-resizer"
clientId={ clientId }
__unstableCoverTarget
__unstablePopoverSlot="block-toolbar"
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
>
<ResizableBox
ref={ resizableRef }
className="block-editor-grid-item-resizer__box"
defaultSize={ {
width: '100%',
height: '100%',
} }
enable={ {
bottom: true,
bottomLeft: false,
bottomRight: false,
left: false,
right: true,
top: false,
topLeft: false,
topRight: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

So what happens if we enable "left" and "top"? I'm guessing it breaks somehow? 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

I think there's a bug in ResizableBox or something because it just goes crazy 🤪 We can look at adding it later if it feels like we need it.

} }
onResizeStop={ ( event, direction, element ) => {
const boxWidth = element.offsetWidth;
const boxHeight = element.offsetHeight;
const { columnGap, rowGap, itemWidth, itemHeight } =
getGridDimensions( blockElement.parentElement );
const columnSpan = Math.round(
( columnGap + boxWidth ) / ( columnGap + itemWidth )
);
const rowSpan = Math.round(
( rowGap + boxHeight ) / ( rowGap + itemHeight )
);
noisysocks marked this conversation as resolved.
Show resolved Hide resolved
const newBoxWidth =
columnSpan * itemWidth + ( columnSpan - 1 ) * columnGap;
const newBoxHeight =
rowSpan * itemHeight + ( rowSpan - 1 ) * rowGap;
onChange( {
columnSpan,
rowSpan,
} );
resizableRef.current.updateSize( {
width: newBoxWidth,
height: newBoxHeight,
} );
} }
/>
</BlockPopover>
);
}

function getGridDimensions( element ) {
const width = element.clientWidth;
const height = element.clientHeight;
const paddingX =
parseFloat( getComputedCSS( element, 'padding-left' ) ) +
parseFloat( getComputedCSS( element, 'padding-right' ) );
const paddingY =
parseFloat( getComputedCSS( element, 'padding-top' ) ) +
parseFloat( getComputedCSS( element, 'padding-bottom' ) );
const gridTemplateColumns = getComputedCSS(
element,
'grid-template-columns'
);
const gridTemplateRows = getComputedCSS( element, 'grid-template-rows' );
const numColumns = gridTemplateColumns.split( ' ' ).length;
const numRows = gridTemplateRows.split( ' ' ).length;
const columnGap = parseFloat( getComputedCSS( element, 'column-gap' ) );
const rowGap = parseFloat( getComputedCSS( element, 'row-gap' ) );
const totalColumnGap = columnGap * ( numColumns - 1 );
const totalRowGap = rowGap * ( numRows - 1 );
const itemWidth = ( width - paddingX - totalColumnGap ) / numColumns;
const itemHeight = ( height - paddingY - totalRowGap ) / numRows;
return {
columnGap,
rowGap,
itemWidth,
itemHeight,
};
}
@@ -0,0 +1,49 @@
/**
* Internal dependencies
*/
import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
import BlockPopover from '../block-popover';
import { getComputedCSS } from './utils';

export function GridVisualizer( { clientId } ) {
const blockElement = useBlockElement( clientId );
if ( ! blockElement ) {
return null;
}
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 (
<BlockPopover
className="block-editor-grid-visualizer"
clientId={ clientId }
__unstableCoverTarget
__unstablePopoverSlot="block-toolbar"
>
<div
className="block-editor-grid-visualizer__grid"
style={ {
gridTemplateColumns,
gridTemplateRows,
gap: getComputedCSS( blockElement, 'gap' ),
padding: getComputedCSS( blockElement, 'padding' ),
} }
>
{ Array.from( { length: numItems }, ( _, i ) => (
<div
key={ i }
className="block-editor-grid-visualizer__item"
/>
) ) }
</div>
</BlockPopover>
);
}
@@ -0,0 +1,2 @@
export { GridVisualizer } from './grid-visualizer';
export { GridItemResizer } from './grid-item-resizer';
29 changes: 29 additions & 0 deletions packages/block-editor/src/components/grid-visualizer/style.scss
@@ -0,0 +1,29 @@
// TODO: Specificity hacks to get rid of all these darn !importants.

.block-editor-grid-visualizer {
z-index: z-index(".block-editor-grid-visualizer") !important;
}

.block-editor-grid-visualizer .components-popover__content * {
pointer-events: none !important;
}

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

.block-editor-grid-visualizer__item {
border: $border-width dashed $gray-300;
}

.block-editor-grid-item-resizer .components-popover__content * {
pointer-events: none !important;
}

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

.components-resizable-box__handle {
pointer-events: all !important;
}
}
@@ -0,0 +1,5 @@
export function getComputedCSS( element, property ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the third instance of this function in the block-editor package, might be worth consolidating (as a separate task)

Copy link
Member Author

Choose a reason for hiding this comment

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

The refactor in #59227 brings it down to 2! Getting close... 😂

return element.ownerDocument.defaultView
.getComputedStyle( element )
.getPropertyValue( property );
}
1 change: 1 addition & 0 deletions packages/block-editor/src/hooks/index.js
Expand Up @@ -42,6 +42,7 @@ createBlockEditFilter(
contentLockUI,
blockHooks,
blockRenaming,
childLayout,
].filter( Boolean )
);
createBlockListBlockFilter( [
Expand Down
36 changes: 36 additions & 0 deletions packages/block-editor/src/hooks/layout-child.js
Expand Up @@ -9,6 +9,8 @@ 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';

function useBlockPropsChildLayoutStyles( { style } ) {
const shouldRenderChildLayoutStyles = useSelect( ( select ) => {
Expand Down Expand Up @@ -93,8 +95,42 @@ function useBlockPropsChildLayoutStyles( { style } ) {
return { className: `wp-container-content-${ id }` };
}

function ChildLayoutControlsPure( { clientId, style, setAttributes } ) {
const parentLayout = useLayout() || {};
const rootClientId = useSelect(
( select ) => {
return select( blockEditorStore ).getBlockRootClientId( clientId );
},
[ clientId ]
);
if ( parentLayout.type !== 'grid' ) {
return null;
}
return (
<>
<GridVisualizer clientId={ rootClientId } />
<GridItemResizer
clientId={ clientId }
onChange={ ( { rowSpan, columnSpan } ) => {
setAttributes( {
style: {
...style,
layout: {
...style?.layout,
rowSpan,
columnSpan,
},
},
} );
} }
/>
</>
);
}

export default {
useBlockProps: useBlockPropsChildLayoutStyles,
edit: ChildLayoutControlsPure,
attributeKeys: [ 'style' ],
hasSupport() {
return true;
Expand Down
13 changes: 12 additions & 1 deletion packages/block-editor/src/hooks/layout.js
Expand Up @@ -135,7 +135,12 @@ export function useLayoutStyles( blockAttributes = {}, blockName, selector ) {
return css;
}

function LayoutPanelPure( { layout, setAttributes, name: blockName } ) {
function LayoutPanelPure( {
layout,
setAttributes,
name: blockName,
clientId,
} ) {
const settings = useBlockSettings( blockName );
// Block settings come from theme.json under settings.[blockName].
const { layout: layoutSettings } = settings;
Expand Down Expand Up @@ -266,13 +271,17 @@ function LayoutPanelPure( { layout, setAttributes, name: blockName } ) {
layout={ usedLayout }
onChange={ onChangeLayout }
layoutBlockSupport={ blockSupportAndThemeSettings }
name={ blockName }
clientId={ clientId }
/>
) }
{ constrainedType && displayControlsForLegacyLayouts && (
<constrainedType.inspectorControls
layout={ usedLayout }
onChange={ onChangeLayout }
layoutBlockSupport={ blockSupportAndThemeSettings }
name={ blockName }
clientId={ clientId }
/>
) }
</PanelBody>
Expand All @@ -282,6 +291,8 @@ function LayoutPanelPure( { layout, setAttributes, name: blockName } ) {
layout={ usedLayout }
onChange={ onChangeLayout }
layoutBlockSupport={ layoutBlockSupport }
name={ blockName }
clientId={ clientId }
/>
) }
</>
Expand Down
26 changes: 18 additions & 8 deletions packages/block-editor/src/layouts/grid.js
Expand Up @@ -19,6 +19,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';

const RANGE_CONTROL_MAX_VALUES = {
px: 600,
Expand Down Expand Up @@ -63,17 +64,26 @@ export default {
inspectorControls: function GridLayoutInspectorControls( {
layout = {},
onChange,
clientId,
} ) {
return layout?.columnCount ? (
<GridLayoutColumnsControl layout={ layout } onChange={ onChange } />
) : (
<GridLayoutMinimumWidthControl
layout={ layout }
onChange={ onChange }
/>
return (
<>
{ layout?.columnCount ? (
<GridLayoutColumnsControl
layout={ layout }
onChange={ onChange }
/>
) : (
<GridLayoutMinimumWidthControl
layout={ layout }
onChange={ onChange }
/>
) }
<GridVisualizer clientId={ clientId } />
</>
);
},
toolBarControls: function DefaultLayoutToolbarControls() {
toolBarControls: function GridLayoutToolbarControls() {
return null;
},
getLayoutStyle: function getLayoutStyle( {
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/style.scss
Expand Up @@ -28,6 +28,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/height-control/style.scss";
@import "./components/image-size-control/style.scss";
@import "./components/inserter-list-item/style.scss";
Expand Down