diff --git a/packages/grid/src/grid-item.tsx b/packages/grid/src/grid-item.tsx index 735b472a705e2a..53707aa8596d3c 100644 --- a/packages/grid/src/grid-item.tsx +++ b/packages/grid/src/grid-item.tsx @@ -17,6 +17,21 @@ import ResizeHandle from './resize-handle'; import type { DashboardGridLayoutItem } from './types'; import styles from './grid-item.module.css'; +function getItemCursor( + disabled: boolean, + interacting: boolean +): React.CSSProperties[ 'cursor' ] { + if ( disabled ) { + return 'default'; + } + + if ( interacting ) { + return undefined; + } + + return 'grab'; +} + type GridItemProps = { /** * The layout item containing grid positioning information. @@ -132,7 +147,12 @@ export function GridItem( { ) }`, gridRowEnd: `span ${ item.height || 1 }`, - cursor: disabled ? 'default' : 'grab', + + // Suppress the grab hint while any gesture is active so the + // inline `cursor` on the tile doesn't override the gesture's + // document-level cursor (e.g. the resize lock). Setting + // `undefined` leaves the property off the DOM. + cursor: getItemCursor( disabled, interacting ), }; const itemClassName = clsx( diff --git a/packages/grid/src/resize-handle.tsx b/packages/grid/src/resize-handle.tsx index 49f8d0060acf09..508fa703452d58 100644 --- a/packages/grid/src/resize-handle.tsx +++ b/packages/grid/src/resize-handle.tsx @@ -8,7 +8,8 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { useThrottle } from '@wordpress/compose'; +import { useEffect, useRef } from '@wordpress/element'; +import { useMergeRefs, useThrottle } from '@wordpress/compose'; /** * Internal dependencies @@ -57,14 +58,38 @@ function ResizeHandle( { itemId, verticalResizable = true, }: ResizeHandleProps ) { - const { attributes, listeners, setNodeRef } = useDraggable( { + const { attributes, listeners, setNodeRef, isDragging } = useDraggable( { id: 'draggable', data: { itemId }, } ); + // Track the rendered node so we can resolve `ownerDocument` for the + // cursor lock — needed when the grid lives inside an iframe (e.g. + // the block-editor canvas). + const nodeRef = useRef< HTMLElement | null >( null ); + const mergedRef = useMergeRefs( [ nodeRef, setNodeRef ] ); + + // Lock the document cursor while the gesture is active. Without + // this, the OS pointer reverts to the default arrow as soon as it + // leaves the handle's hit area, even though the resize is still + // in progress. + useEffect( () => { + if ( ! isDragging ) { + return; + } + const cursor = verticalResizable ? 'nwse-resize' : 'ew-resize'; + const root = ( nodeRef.current?.ownerDocument ?? document ) + .documentElement; + const previous = root.style.cursor; + root.style.cursor = cursor; + return () => { + root.style.cursor = previous; + }; + }, [ isDragging, verticalResizable ] ); + return (