Skip to content

Commit

Permalink
Add Grid interactivity (#59052)
Browse files Browse the repository at this point in the history
* Add grid visualization

* Show grid visualization when child is selected

* Allow dragging to set column and row span

* Don't need this ref

* Just disable left/top resizing for now

* Clean up CSS

* Add shift=false to popovers

* Accommodate variably sized columns/rows by using grid-template to calculate span

* BlockPopover: Use ResizeObserver to match size of covered block

* Fix error due to undefined selectedElement

* Update GridVisualizer when grid or its children resize

* Add experimental flag

* Fix formatting

* Go away spaces

* BlockPopover: Remove __unstableRefreshSize prop and improve how __unstableCoverTarget works

* BlockPopover: Remove __unstableCoverTarget in favour of BlockPopoverCover

* Use BlockPopoverCover

Co-authored-by: noisysocks <noisysocks@git.wordpress.org>
Co-authored-by: hanneslsm <hanneslsm@git.wordpress.org>
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
  • Loading branch information
5 people authored Feb 23, 2024
1 parent 671fbf9 commit ba535da
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/experimental/editor-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ function gutenberg_enable_experiments() {
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-color-randomizer', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableColorRandomizer = true', 'before' );
}
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-grid-interactivity', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGridInteractivity = true', 'before' );
}
if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) {
wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' );
}
Expand Down
14 changes: 14 additions & 0 deletions lib/experiments-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function gutenberg_initialize_experiments_settings() {
'id' => 'gutenberg-color-randomizer',
)
);

add_settings_field(
'gutenberg-form-blocks',
__( 'Form and input blocks ', 'gutenberg' ),
Expand All @@ -101,6 +102,19 @@ function gutenberg_initialize_experiments_settings() {
'id' => 'gutenberg-form-blocks',
)
);

add_settings_field(
'gutenberg-grid-interactivity',
__( 'Grid interactivty ', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
'label' => __( 'Test enhancements to the Grid block that let you move and resize items in the editor canvas.', 'gutenberg' ),
'id' => 'gutenberg-grid-interactivity',
)
);

add_settings_field(
'gutenberg-no-tinymce',
__( 'Disable TinyMCE and Classic block', 'gutenberg' ),
Expand Down
3 changes: 3 additions & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* WordPress dependencies
*/
import { ResizableBox } from '@wordpress/components';

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

export function GridItemResizer( { clientId, onChange } ) {
const blockElement = useBlockElement( clientId );
if ( ! blockElement ) {
return null;
}
return (
<BlockPopoverCover
className="block-editor-grid-item-resizer"
clientId={ clientId }
__unstablePopoverSlot="block-toolbar"
>
<ResizableBox
className="block-editor-grid-item-resizer__box"
size={ {
width: '100%',
height: '100%',
} }
enable={ {
bottom: true,
bottomLeft: false,
bottomRight: false,
left: false,
right: true,
top: false,
topLeft: false,
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 gridColumnLines = getGridLines(
getComputedCSS( gridElement, 'grid-template-columns' ),
columnGap
);
const gridRowLines = getGridLines(
getComputedCSS( gridElement, 'grid-template-rows' ),
rowGap
);
const columnStart = getClosestLine(
gridColumnLines,
blockElement.offsetLeft
);
const rowStart = getClosestLine(
gridRowLines,
blockElement.offsetTop
);
const columnEnd = getClosestLine(
gridColumnLines,
blockElement.offsetLeft + boxElement.offsetWidth
);
const rowEnd = getClosestLine(
gridRowLines,
blockElement.offsetTop + boxElement.offsetHeight
);
onChange( {
columnSpan: Math.max( columnEnd - columnStart, 1 ),
rowSpan: Math.max( rowEnd - rowStart, 1 ),
} );
} }
/>
</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,81 @@
/**
* WordPress dependencies
*/
import { useState, useEffect } from '@wordpress/element';

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

export function GridVisualizer( { clientId } ) {
const blockElement = useBlockElement( clientId );
if ( ! blockElement ) {
return null;
}
return (
<BlockPopoverCover
className="block-editor-grid-visualizer"
clientId={ clientId }
__unstablePopoverSlot="block-toolbar"
>
<GridVisualizerGrid blockElement={ blockElement } />
</BlockPopoverCover>
);
}

function GridVisualizerGrid( { blockElement } ) {
const [ gridInfo, setGridInfo ] = useState( () =>
getGridInfo( blockElement )
);
useEffect( () => {
const observers = [];
for ( const element of [ blockElement, ...blockElement.children ] ) {
const observer = new window.ResizeObserver( () => {
setGridInfo( getGridInfo( blockElement ) );
} );
observer.observe( element );
observers.push( observer );
}
return () => {
for ( const observer of observers ) {
observer.disconnect();
}
};
}, [ blockElement ] );
return (
<div
className="block-editor-grid-visualizer__grid"
style={ gridInfo.style }
>
{ Array.from( { length: gridInfo.numItems }, ( _, i ) => (
<div key={ i } className="block-editor-grid-visualizer__item" />
) ) }
</div>
);
}

function getGridInfo( blockElement ) {
const gridTemplateColumns = getComputedCSS(
blockElement,
'grid-template-columns'
);
const gridTemplateRows = getComputedCSS(
blockElement,
'grid-template-rows'
);
const numColumns = gridTemplateColumns.split( ' ' ).length;
const numRows = gridTemplateRows.split( ' ' ).length;
const numItems = numColumns * numRows;
return {
numItems,
style: {
gridTemplateColumns,
gridTemplateRows,
gap: getComputedCSS( blockElement, 'gap' ),
padding: getComputedCSS( blockElement, 'padding' ),
},
};
}
2 changes: 2 additions & 0 deletions packages/block-editor/src/components/grid-visualizer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { GridVisualizer } from './grid-visualizer';
export { GridItemResizer } from './grid-item-resizer';
33 changes: 33 additions & 0 deletions packages/block-editor/src/components/grid-visualizer/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 {
z-index: z-index(".block-editor-grid-visualizer") !important;
}

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

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

.components-resizable-box__handle {
pointer-events: all !important;
}
}
5 changes: 5 additions & 0 deletions packages/block-editor/src/components/grid-visualizer/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function getComputedCSS( element, property ) {
return element.ownerDocument.defaultView
.getComputedStyle( element )
.getPropertyValue( property );
}
1 change: 1 addition & 0 deletions packages/block-editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ createBlockEditFilter(
contentLockUI,
blockHooks,
blockRenaming,
childLayout,
].filter( Boolean )
);
createBlockListBlockFilter( [
Expand Down
38 changes: 38 additions & 0 deletions packages/block-editor/src/hooks/layout-child.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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 @@ -96,8 +97,45 @@ 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;
}
if ( ! window.__experimentalEnableGridInteractivity ) {
return null;
}
return (
<>
<GridVisualizer clientId={ rootClientId } />
<GridItemResizer
clientId={ clientId }
onChange={ ( { columnSpan, rowSpan } ) => {
setAttributes( {
style: {
...style,
layout: {
...style?.layout,
columnSpan,
rowSpan,
},
},
} );
} }
/>
</>
);
}

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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit ba535da

Please sign in to comment.