From dc2ba4b739fc0cf9271f0efea74b5d4ff606e6b9 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 23 Aug 2022 20:10:28 +0800 Subject: [PATCH] Ensure block toolbar doesn't overlap block by modifying forcePosition and shift popover props (#42887) * Flip popover placement in SelectedBlockPopover component * Adjust shift/forcePosition props instead of placement * Remount popover when attached to a different block * Remove key prop * Update hook to use an effect and handle window resize event * Fix props not changing when elements are changed. Tidy up code and add comments. * Remove any unrelated changes * Handle content ref being undefined - fixes widget editor errors * Update toolbar popover props on block movement * Use dynamic toolbar height in hook * Improve comments * Add resize observer for block * Refactor into seperate hooks * Revert "Refactor into seperate hooks" This reverts commit 59b192fb6c3f2d68c650671bdec9070e353c2509. * Ensure the resize observer is using the correct view * Refactor update on block move to a different effect to reduce event binding * Fix doc block description * Add missing hook dependency * Switch to useLayoutEffect * Use lazy initial state --- .../src/components/block-popover/index.js | 35 +++-- .../block-tools/selected-block-popover.js | 7 + .../use-block-toolbar-popover-props.js | 123 ++++++++++++++++++ 3 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js diff --git a/packages/block-editor/src/components/block-popover/index.js b/packages/block-editor/src/components/block-popover/index.js index 370959d1b145ce..597f61eb949106 100644 --- a/packages/block-editor/src/components/block-popover/index.js +++ b/packages/block-editor/src/components/block-popover/index.js @@ -6,8 +6,9 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { useMergeRefs } from '@wordpress/compose'; import { Popover } from '@wordpress/components'; -import { useMemo } from '@wordpress/element'; +import { forwardRef, useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -15,19 +16,25 @@ import { useMemo } from '@wordpress/element'; import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs'; import usePopoverScroll from './use-popover-scroll'; -export default function BlockPopover( { - clientId, - bottomClientId, - children, - __unstableRefreshSize, - __unstableCoverTarget = false, - __unstablePopoverSlot, - __unstableContentRef, - ...props -} ) { +function BlockPopover( + { + clientId, + bottomClientId, + children, + __unstableRefreshSize, + __unstableCoverTarget = false, + __unstablePopoverSlot, + __unstableContentRef, + ...props + }, + ref +) { const selectedElement = useBlockElement( clientId ); const lastSelectedElement = useBlockElement( bottomClientId ?? clientId ); - const popoverScrollRef = usePopoverScroll( __unstableContentRef ); + const mergedRefs = useMergeRefs( [ + ref, + usePopoverScroll( __unstableContentRef ), + ] ); const style = useMemo( () => { if ( ! selectedElement || lastSelectedElement !== selectedElement ) { return {}; @@ -51,7 +58,7 @@ export default function BlockPopover( { return ( ); } + +export default forwardRef( BlockPopover ); diff --git a/packages/block-editor/src/components/block-tools/selected-block-popover.js b/packages/block-editor/src/components/block-tools/selected-block-popover.js index 962063ba284b6f..652c04efe6aed5 100644 --- a/packages/block-editor/src/components/block-tools/selected-block-popover.js +++ b/packages/block-editor/src/components/block-tools/selected-block-popover.js @@ -20,6 +20,7 @@ import BlockSelectionButton from './block-selection-button'; import BlockContextualToolbar from './block-contextual-toolbar'; import { store as blockEditorStore } from '../../store'; import BlockPopover from '../block-popover'; +import useBlockToolbarPopoverProps from './use-block-toolbar-popover-props'; function selector( select ) { const { @@ -113,6 +114,11 @@ function SelectedBlockPopover( { // to it when re-mounting. const initialToolbarItemIndexRef = useRef(); + const popoverProps = useBlockToolbarPopoverProps( { + contentElement: __unstableContentRef?.current, + clientId, + } ); + if ( ! shouldShowBreadcrumb && ! shouldShowContextualToolbar ) { return null; } @@ -126,6 +132,7 @@ function SelectedBlockPopover( { } ) } __unstablePopoverSlot={ __unstablePopoverSlot } __unstableContentRef={ __unstableContentRef } + { ...popoverProps } > { shouldShowContextualToolbar && ( toolbarHeight ) { + return DEFAULT_PROPS; + } + + return RESTRICTED_HEIGHT_PROPS; +} + +/** + * Determines the desired popover positioning behavior, returning a set of appropriate props. + * + * @param {Object} elements + * @param {Element} elements.contentElement The DOM element that represents the editor content or canvas. + * @param {string} elements.clientId The clientId of the first selected block. + * + * @return {Object} The popover props used to determine the position of the toolbar. + */ +export default function useBlockToolbarPopoverProps( { + contentElement, + clientId, +} ) { + const selectedBlockElement = useBlockElement( clientId ); + const [ toolbarHeight, setToolbarHeight ] = useState( 0 ); + const [ props, setProps ] = useState( () => + getProps( contentElement, selectedBlockElement, toolbarHeight ) + ); + const blockIndex = useSelect( + ( select ) => select( blockEditorStore ).getBlockIndex( clientId ), + [ clientId ] + ); + + const popoverRef = useRefEffect( ( popoverNode ) => { + setToolbarHeight( popoverNode.offsetHeight ); + }, [] ); + + const updateProps = useCallback( + () => + setProps( + getProps( contentElement, selectedBlockElement, toolbarHeight ) + ), + [ contentElement, selectedBlockElement, toolbarHeight ] + ); + + // Update props when the block is moved. This also ensures the props are + // correct on initial mount, and when the selected block or content element + // changes (since the callback ref will update). + useLayoutEffect( updateProps, [ blockIndex, updateProps ] ); + + // Update props when the viewport is resized or the block is resized. + useLayoutEffect( () => { + if ( ! contentElement || ! selectedBlockElement ) { + return; + } + + // Update the toolbar props on viewport resize. + const contentView = contentElement?.ownerDocument?.defaultView; + contentView?.addEventHandler?.( 'resize', updateProps ); + + // Update the toolbar props on block resize. + let resizeObserver; + const blockView = selectedBlockElement?.ownerDocument?.defaultView; + if ( blockView.ResizeObserver ) { + resizeObserver = new blockView.ResizeObserver( updateProps ); + resizeObserver.observe( selectedBlockElement ); + } + + return () => { + contentView?.removeEventHandler?.( 'resize', updateProps ); + + if ( resizeObserver ) { + resizeObserver.disconnect(); + } + }; + }, [ updateProps, contentElement, selectedBlockElement ] ); + + return { + ...props, + ref: popoverRef, + }; +}