Skip to content

Commit

Permalink
Fix resizing items to top and left with GridItemResizer (#60986)
Browse files Browse the repository at this point in the history
Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
Co-authored-by: noisysocks <noisysocks@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
  • Loading branch information
4 people authored May 10, 2024
1 parent 4d1c083 commit 927ed76
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 31 deletions.
23 changes: 19 additions & 4 deletions packages/block-editor/src/components/block-popover/cover.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-
import BlockPopover from '.';

function BlockPopoverCover(
{ clientId, bottomClientId, children, shift = false, ...props },
{
clientId,
bottomClientId,
children,
shift = false,
additionalStyles,
...props
},
ref
) {
bottomClientId ??= clientId;
Expand All @@ -26,7 +33,10 @@ function BlockPopoverCover(
{ ...props }
>
{ selectedElement && clientId === bottomClientId ? (
<CoverContainer selectedElement={ selectedElement }>
<CoverContainer
selectedElement={ selectedElement }
additionalStyles={ additionalStyles }
>
{ children }
</CoverContainer>
) : (
Expand All @@ -36,7 +46,11 @@ function BlockPopoverCover(
);
}

function CoverContainer( { selectedElement, children } ) {
function CoverContainer( {
selectedElement,
additionalStyles = {},
children,
} ) {
const [ width, setWidth ] = useState( selectedElement.offsetWidth );
const [ height, setHeight ] = useState( selectedElement.offsetHeight );

Expand All @@ -54,8 +68,9 @@ function CoverContainer( { selectedElement, children } ) {
position: 'absolute',
width,
height,
...additionalStyles,
};
}, [ width, height ] );
}, [ width, height, additionalStyles ] );

return <div style={ style }>{ children }</div>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { ResizableBox } from '@wordpress/components';
import { useState, useRef, useEffect } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -12,14 +13,125 @@ import { getComputedCSS } from './utils';

export function GridItemResizer( { clientId, onChange } ) {
const blockElement = useBlockElement( clientId );
const rootBlockElement = blockElement?.parentElement;

if ( ! blockElement || ! rootBlockElement ) {
return null;
}

return (
<GridItemResizerInner
clientId={ clientId }
blockElement={ blockElement }
rootBlockElement={ rootBlockElement }
onChange={ onChange }
/>
);
}

function GridItemResizerInner( {
clientId,
blockElement,
rootBlockElement,
onChange,
} ) {
const [ resizeDirection, setResizeDirection ] = useState( null );
const [ enableSide, setEnableSide ] = useState( {
top: false,
bottom: false,
left: false,
right: false,
} );

useEffect( () => {
const observer = new window.ResizeObserver( () => {
const blockClientRect = blockElement.getBoundingClientRect();
const rootBlockClientRect =
rootBlockElement.getBoundingClientRect();
setEnableSide( {
top: blockClientRect.top > rootBlockClientRect.top,
bottom: blockClientRect.bottom < rootBlockClientRect.bottom,
left: blockClientRect.left > rootBlockClientRect.left,
right: blockClientRect.right < rootBlockClientRect.right,
} );
} );
observer.observe( blockElement );
return () => observer.disconnect();
}, [ blockElement, rootBlockElement ] );

/*
* This ref is necessary get the bounding client rect of the resizer,
* because it exists outside of the iframe, so its bounding client
* rect isn't the same as the block element's.
*/
const resizerRef = useRef( null );

if ( ! blockElement ) {
return null;
}

const justification = {
right: 'flex-start',
left: 'flex-end',
};

const alignment = {
top: 'flex-end',
bottom: 'flex-start',
};

const styles = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
...( justification[ resizeDirection ] && {
justifyContent: justification[ resizeDirection ],
} ),
...( alignment[ resizeDirection ] && {
alignItems: alignment[ resizeDirection ],
} ),
};

/*
* The bounding element is equivalent to the root block element, but
* its bounding client rect is modified to account for the resizer
* being outside of the editor iframe.
*/
const boundingElement = {
offsetWidth: rootBlockElement.offsetWidth,
offsetHeight: rootBlockElement.offsetHeight,
getBoundingClientRect: () => {
const blockClientRect = blockElement.getBoundingClientRect();
const rootBlockClientRect =
rootBlockElement.getBoundingClientRect();
const resizerTop = resizerRef.current?.getBoundingClientRect()?.top;
// Fallback value of 60 to account for editor top bar height.
const heightDifference = resizerTop
? resizerTop - blockClientRect.top
: 60;
return {
bottom: rootBlockClientRect.bottom + heightDifference,
height: rootBlockElement.offsetHeight,
left: rootBlockClientRect.left,
right: rootBlockClientRect.right,
top: rootBlockClientRect.top + heightDifference,
width: rootBlockClientRect.width,
x: rootBlockClientRect.x,
y: rootBlockClientRect.y + heightDifference,
};
},
};

// Controller to remove event listener on resize stop.
const controller = new AbortController();

return (
<BlockPopoverCover
className="block-editor-grid-item-resizer"
clientId={ clientId }
__unstablePopoverSlot="block-toolbar"
additionalStyles={ styles }
__unstableContentRef={ resizerRef }
>
<ResizableBox
className="block-editor-grid-item-resizer__box"
Expand All @@ -28,57 +140,85 @@ export function GridItemResizer( { clientId, onChange } ) {
height: '100%',
} }
enable={ {
bottom: true,
bottom: enableSide.bottom,
bottomLeft: false,
bottomRight: false,
left: false,
right: true,
top: false,
left: enableSide.left,
right: enableSide.right,
top: enableSide.top,
topLeft: false,
topRight: false,
} }
bounds={ boundingElement }
boundsByDirection
onResizeStart={ ( event, direction ) => {
/*
* The container justification and alignment need to be set
* according to the direction the resizer is being dragged in,
* so that it resizes in the right direction.
*/
setResizeDirection( direction );

/*
* The mouseup event on the resize handle doesn't trigger if the mouse
* isn't directly above the handle, so we try to detect if it happens
* outside the grid and dispatch a mouseup event on the handle.
*/
const rootElementParent =
rootBlockElement.closest( 'body' );
rootElementParent.addEventListener(
'mouseup',
() => {
event.target.dispatchEvent(
new Event( 'mouseup', { bubbles: true } )
);
},
{ signal: controller.signal, capture: true }
);
} }
onResizeStop={ ( event, direction, boxElement ) => {
const gridElement = blockElement.parentElement;
const columnGap = parseFloat(
getComputedCSS( gridElement, 'column-gap' )
getComputedCSS( rootBlockElement, 'column-gap' )
);
const rowGap = parseFloat(
getComputedCSS( gridElement, 'row-gap' )
getComputedCSS( rootBlockElement, 'row-gap' )
);
const gridColumnTracks = getGridTracks(
getComputedCSS( gridElement, 'grid-template-columns' ),
getComputedCSS(
rootBlockElement,
'grid-template-columns'
),
columnGap
);
const gridRowTracks = getGridTracks(
getComputedCSS( gridElement, 'grid-template-rows' ),
getComputedCSS(
rootBlockElement,
'grid-template-rows'
),
rowGap
);
const rect = new window.DOMRect(
blockElement.offsetLeft + boxElement.offsetLeft,
blockElement.offsetTop + boxElement.offsetTop,
boxElement.offsetWidth,
boxElement.offsetHeight
);
const columnStart =
getClosestTrack(
gridColumnTracks,
blockElement.offsetLeft
) + 1;
getClosestTrack( gridColumnTracks, rect.left ) + 1;
const rowStart =
getClosestTrack(
gridRowTracks,
blockElement.offsetTop
) + 1;
getClosestTrack( gridRowTracks, rect.top ) + 1;
const columnEnd =
getClosestTrack(
gridColumnTracks,
blockElement.offsetLeft + boxElement.offsetWidth,
'end'
) + 1;
getClosestTrack( gridColumnTracks, rect.right, 'end' ) +
1;
const rowEnd =
getClosestTrack(
gridRowTracks,
blockElement.offsetTop + boxElement.offsetHeight,
'end'
) + 1;
getClosestTrack( gridRowTracks, rect.bottom, 'end' ) +
1;
onChange( {
columnSpan: columnEnd - columnStart + 1,
rowSpan: rowEnd - rowStart + 1,
} );
// Removes event listener added in onResizeStart.
controller.abort();
} }
/>
</BlockPopoverCover>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
pointer-events: all !important;
}
}

0 comments on commit 927ed76

Please sign in to comment.