@wordpress/block-editor
Version:
162 lines (146 loc) • 6.28 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useBlockToolbarPopoverProps;
var _compose = require("@wordpress/compose");
var _data = require("@wordpress/data");
var _dom = require("@wordpress/dom");
var _element = require("@wordpress/element");
var _store = require("../../store");
var _useBlockRefs = require("../block-list/use-block-props/use-block-refs");
var _position = require("../../hooks/position");
var _dom2 = require("../../utils/dom");
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const COMMON_PROPS = {
placement: 'top-start'
};
// By default the toolbar sets the `shift` prop. If the user scrolls the page
// down the toolbar will stay on screen by adopting a sticky position at the
// top of the viewport.
const DEFAULT_PROPS = {
...COMMON_PROPS,
flip: false,
shift: true
};
// When there isn't enough height between the top of the block and the editor
// canvas, the `shift` prop is set to `false`, as it will cause the block to be
// obscured. The `flip` behavior is enabled, which positions the toolbar below
// the block. This only happens if the block is smaller than the viewport, as
// otherwise the toolbar will be off-screen.
const RESTRICTED_HEIGHT_PROPS = {
...COMMON_PROPS,
flip: true,
shift: false
};
/**
* Get the popover props for the block toolbar, determined by the space at the top of the canvas and the toolbar height.
*
* @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.
* @param {boolean} isSticky Whether or not the selected block is sticky or fixed.
*
* @return {Object} The popover props used to determine the position of the toolbar.
*/
function getProps(contentElement, selectedBlockElement, scrollContainer, toolbarHeight, isSticky) {
if (!contentElement || !selectedBlockElement) {
return DEFAULT_PROPS;
}
// Get how far the content area has been scrolled.
const scrollTop = scrollContainer?.scrollTop || 0;
const blockRect = (0, _dom2.getElementBounds)(selectedBlockElement);
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;
// 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;
// Sticky blocks are treated as if they will never have enough space for the toolbar above.
if (!isSticky && (hasSpaceForToolbarAbove || isBlockTallerThanViewport)) {
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.
*/
function useBlockToolbarPopoverProps({
contentElement,
clientId
}) {
const selectedBlockElement = (0, _useBlockRefs.useBlockElement)(clientId);
const [toolbarHeight, setToolbarHeight] = (0, _element.useState)(0);
const {
blockIndex,
isSticky
} = (0, _data.useSelect)(select => {
const {
getBlockIndex,
getBlockAttributes
} = select(_store.store);
return {
blockIndex: getBlockIndex(clientId),
isSticky: (0, _position.hasStickyOrFixedPositionValue)(getBlockAttributes(clientId))
};
}, [clientId]);
const scrollContainer = (0, _element.useMemo)(() => {
if (!contentElement) {
return;
}
return (0, _dom.getScrollContainer)(contentElement);
}, [contentElement]);
const [props, setProps] = (0, _element.useState)(() => getProps(contentElement, selectedBlockElement, scrollContainer, toolbarHeight, isSticky));
const popoverRef = (0, _compose.useRefEffect)(popoverNode => {
setToolbarHeight(popoverNode.offsetHeight);
}, []);
const updateProps = (0, _element.useCallback)(() => setProps(getProps(contentElement, selectedBlockElement, scrollContainer, toolbarHeight, isSticky)), [contentElement, selectedBlockElement, scrollContainer, 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).
(0, _element.useLayoutEffect)(updateProps, [blockIndex, updateProps]);
// Update props when the viewport is resized or the block is resized.
(0, _element.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
};
}
//# sourceMappingURL=use-block-toolbar-popover-props.js.map
;