diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index bea16b719a463d..ac932366772d9c 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { useDrag } from '@use-gesture/react'; + /** * Internal dependencies */ @@ -19,4 +24,5 @@ lock( privateApis, { Theme, Menu, kebabCase, + useDrag, } ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index b50e17054fd3ef..4daca07e535760 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -45,11 +45,11 @@ import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; import { Icon, - ResizableBox, SlotFillProvider, Tooltip, VisuallyHidden, __unstableUseNavigateRegions as useNavigateRegions, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { useMediaQuery, @@ -78,6 +78,7 @@ import useNavigateToEntityRecord from '../../hooks/use-navigate-to-entity-record const { getLayoutStyles } = unlock( blockEditorPrivateApis ); const { useCommands } = unlock( coreCommandsPrivateApis ); const { useCommandContext } = unlock( commandsPrivateApis ); +const { useDrag } = unlock( componentsPrivateApis ); const { Editor, FullscreenMode, NavigableRegion } = unlock( editorPrivateApis ); const { BlockKeyboardShortcuts } = unlock( blockLibraryPrivateApis ); const DESIGN_POST_TYPES = [ @@ -202,9 +203,10 @@ function MetaBoxesMain( { isLegacy } ) { const separatorRef = useRef(); const separatorHelpId = useId(); - const [ isUntouched, setIsUntouched ] = useState( true ); - const applyHeight = ( candidateHeight, isPersistent, isInstant ) => { + const heightRef = useRef(); + const applyHeight = ( candidateHeight, isPersistent ) => { const nextHeight = Math.min( max, Math.max( min, candidateHeight ) ); + heightRef.current = nextHeight; if ( isPersistent ) { setPreference( 'core/edit-post', @@ -212,19 +214,43 @@ function MetaBoxesMain( { isLegacy } ) { nextHeight ); } else { + metaBoxesMainRef.current.style.height = `${ nextHeight }px`; separatorRef.current.ariaValueNow = getAriaValueNow( nextHeight ); } - if ( isInstant ) { - metaBoxesMainRef.current.updateSize( { - height: nextHeight, - // Oddly, when the event that triggered this was not from the mouse (e.g. keydown), - // if `width` is left unspecified a subsequent drag gesture applies a fixed - // width and the pane fails to widen/narrow with parent width changes from - // sidebars opening/closing or window resizes. - width: 'auto', - } ); - } }; + const [ isDragging, setIsDragging ] = useState( false ); + // useDrag includes basic keyboard support with arrow keys emulating a drag. + // TODO: Support more/all keyboard interactions from the window splitter pattern: + // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/ + const bindDragGesture = useDrag( ( state ) => { + const { movement, first, last, memo, swipe } = state; + const [ , yMovement ] = movement; + if ( first ) { + setIsDragging( true ); + let fromHeight = heightRef.current; + if ( fromHeight === undefined ) { + fromHeight = metaBoxesMainRef.current.offsetHeight; + } else if ( heightRef.current > max ) { + // Starts from max in case shortening the window has imposed it. + fromHeight = max; + } + if ( yMovement !== 0 ) { + applyHeight( fromHeight - yMovement ); + } + return { fromHeight: fromHeight ?? heightRef.current }; + } + if ( ! first && ! last ) { + applyHeight( memo.fromHeight - yMovement ); + return memo; + } + setIsDragging( false ); + const [ , swipeY ] = swipe; + if ( swipeY ) { + applyHeight( swipeY === -1 ? max : min, true ); + } else if ( yMovement !== 0 ) { + applyHeight( heightRef.current, true ); + } + } ); if ( ! hasAnyVisible ) { return; @@ -252,7 +278,7 @@ function MetaBoxesMain( { isLegacy } ) { let usedMax = '50%'; // Approximation before max has a value. if ( max !== undefined ) { // Halves the available max height until a user height is set. - usedMax = isAutoHeight && isUntouched ? max / 2 : max; + usedMax = isAutoHeight && ! isDragging ? max / 2 : max; } const getAriaValueNow = ( height ) => @@ -263,104 +289,59 @@ function MetaBoxesMain( { isLegacy } ) { const toggle = () => setPreference( 'core/edit-post', 'metaBoxesMainIsOpen', ! isOpen ); - // TODO: Support more/all keyboard interactions from the window splitter pattern: - // https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/ - const onSeparatorKeyDown = ( event ) => { - const delta = { ArrowUp: 20, ArrowDown: -20 }[ event.key ]; - if ( delta ) { - const pane = metaBoxesMainRef.current.resizable; - const fromHeight = isAutoHeight ? pane.offsetHeight : openHeight; - const nextHeight = delta + fromHeight; - applyHeight( nextHeight, true, true ); - event.preventDefault(); - } - }; const className = 'edit-post-meta-boxes-main'; const paneLabel = __( 'Meta Boxes' ); - let Pane, paneProps; + let paneProps, paneButtonProps; if ( isShort ) { - Pane = NavigableRegion; paneProps = { className: clsx( className, 'is-toggle-only' ), }; + paneButtonProps = { + 'aria-expanded': isOpen, + onClick: toggle, + children: ( + <> + { paneLabel } + + + ), + }; } else { - Pane = ResizableBox; - paneProps = /** @type {Parameters[0]} */ ( { - as: NavigableRegion, + paneProps = { ref: metaBoxesMainRef, className: clsx( className, 'is-resizable' ), - defaultSize: { height: openHeight }, - minHeight: min, - maxHeight: usedMax, - enable: { - top: true, - right: false, - bottom: false, - left: false, - topLeft: false, - topRight: false, - bottomRight: false, - bottomLeft: false, - }, - handleClasses: { top: 'edit-post-meta-boxes-main__presenter' }, - handleComponent: { - top: ( - <> - - - ) : ( - - ) } + + { ! isShort && } +