Skip to content

Commit

Permalink
Block Selection Toolbar: Support fixed and sticky blocks by re-render…
Browse files Browse the repository at this point in the history
…ing on scroll and flipping based on available space at top of viewport
  • Loading branch information
andrewserong committed Dec 14, 2022
1 parent a3db417 commit fa6ce11
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 6 deletions.
26 changes: 26 additions & 0 deletions packages/block-editor/src/components/block-popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { useMergeRefs } from '@wordpress/compose';
import { getScrollContainer } from '@wordpress/dom';
import { Popover } from '@wordpress/components';
import {
forwardRef,
Expand Down Expand Up @@ -75,6 +76,31 @@ function BlockPopover(
};
}, [ selectedElement ] );

const scrollContainer = useMemo( () => {
if ( ! __unstableContentRef?.current ) {
return;
}
return getScrollContainer( __unstableContentRef?.current );
}, [ __unstableContentRef?.current ] );

useLayoutEffect( () => {
if ( ! scrollContainer ) {
return;
}

scrollContainer?.addEventListener?.(
'scroll',
forceRecomputePopoverDimensions
);

return () => {
scrollContainer?.removeEventHandler?.(
'scroll',
forceRecomputePopoverDimensions
);
};
}, [ scrollContainer ] );

const style = useMemo( () => {
if (
// popoverDimensionsRecomputeCounter is by definition always equal or greater
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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 ),
Expand All @@ -98,7 +131,12 @@ export default function useBlockToolbarPopoverProps( {
const updateProps = useCallback(
() =>
setProps(
getProps( contentElement, selectedBlockElement, toolbarHeight )
getProps(
contentElement,
selectedBlockElement,
scrollContainer,
toolbarHeight
)
),
[ contentElement, selectedBlockElement, toolbarHeight ]
);
Expand Down

0 comments on commit fa6ce11

Please sign in to comment.