diff --git a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js index d218b1104139cf..08f71989df9a1c 100644 --- a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js +++ b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js @@ -3,7 +3,13 @@ */ import { useRefEffect } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; -import { useCallback, useLayoutEffect, useState } from '@wordpress/element'; +import { getScrollContainer } from '@wordpress/dom'; +import { + useCallback, + useLayoutEffect, + useMemo, + useState, +} from '@wordpress/element'; /** * Internal dependencies @@ -40,24 +46,40 @@ const RESTRICTED_HEIGHT_PROPS = { * * @param {Element} contentElement The DOM element that represents the editor content or canvas. * @param {Element} selectedBlockElement The outer DOM element of the first selected block. + * @param {Element} scrollContainer The scrollable container for the contentElement. * @param {number} toolbarHeight The height of the toolbar in pixels. * * @return {Object} The popover props used to determine the position of the toolbar. */ -function getProps( contentElement, selectedBlockElement, toolbarHeight ) { +function getProps( + contentElement, + selectedBlockElement, + scrollContainer, + toolbarHeight +) { if ( ! contentElement || ! selectedBlockElement ) { return DEFAULT_PROPS; } + // Get how far the content area has been scrolled. + const scrollTop = scrollContainer?.scrollTop || 0; + const blockRect = selectedBlockElement.getBoundingClientRect(); const contentRect = contentElement.getBoundingClientRect(); + // Get the vertical position of top of the visible content area. + const topOfContentElementInViewport = scrollTop + contentRect.top; + // The document element's clientHeight represents the viewport height. const viewportHeight = contentElement.ownerDocument.documentElement.clientHeight; - const hasSpaceForToolbarAbove = - blockRect.top - contentRect.top > toolbarHeight; + // The restricted height area is calculated as the sum of the + // vertical position of the visible content area, plus the height + // of the block toolbar. + const restrictedTopArea = topOfContentElementInViewport + toolbarHeight; + const hasSpaceForToolbarAbove = blockRect.top > restrictedTopArea; + const isBlockTallerThanViewport = blockRect.height > viewportHeight - toolbarHeight; @@ -83,8 +105,19 @@ export default function useBlockToolbarPopoverProps( { } ) { const selectedBlockElement = useBlockElement( clientId ); const [ toolbarHeight, setToolbarHeight ] = useState( 0 ); + const scrollContainer = useMemo( () => { + if ( ! contentElement ) { + return; + } + return getScrollContainer( contentElement ); + }, [ contentElement ] ); const [ props, setProps ] = useState( () => - getProps( contentElement, selectedBlockElement, toolbarHeight ) + getProps( + contentElement, + selectedBlockElement, + scrollContainer, + toolbarHeight + ) ); const blockIndex = useSelect( ( select ) => select( blockEditorStore ).getBlockIndex( clientId ), @@ -98,9 +131,14 @@ export default function useBlockToolbarPopoverProps( { const updateProps = useCallback( () => setProps( - getProps( contentElement, selectedBlockElement, toolbarHeight ) + getProps( + contentElement, + selectedBlockElement, + scrollContainer, + toolbarHeight + ) ), - [ contentElement, selectedBlockElement, toolbarHeight ] + [ contentElement, selectedBlockElement, scrollContainer, toolbarHeight ] ); // Update props when the block is moved. This also ensures the props are