UNPKG

@wordpress/block-editor

Version:
251 lines (238 loc) 10.6 kB
/** * External dependencies */ import clsx from 'clsx'; /** * WordPress dependencies */ import { __experimentalHStack as HStack, __experimentalTruncate as Truncate, Popover } from '@wordpress/components'; import { getScrollContainer } from '@wordpress/dom'; import { useCallback, useMemo } from '@wordpress/element'; import { isRTL } from '@wordpress/i18n'; /** * Internal dependencies */ import BlockIcon from '../block-icon'; import useBlockDisplayInformation from '../use-block-display-information'; import useBlockDisplayTitle from '../block-title/use-block-display-title'; import ListViewExpander from './expander'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export default function ListViewDropIndicatorPreview({ draggedBlockClientId, listViewRef, blockDropTarget }) { const blockInformation = useBlockDisplayInformation(draggedBlockClientId); const blockTitle = useBlockDisplayTitle({ clientId: draggedBlockClientId, context: 'list-view' }); const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; const [rootBlockElement, blockElement] = useMemo(() => { if (!listViewRef.current) { return []; } // The rootClientId will be defined whenever dropping into inner // block lists, but is undefined when dropping at the root level. const _rootBlockElement = rootClientId ? listViewRef.current.querySelector(`[data-block="${rootClientId}"]`) : undefined; // The clientId represents the sibling block, the dragged block will // usually be inserted adjacent to it. It will be undefined when // dropping a block into an empty block list. const _blockElement = clientId ? listViewRef.current.querySelector(`[data-block="${clientId}"]`) : undefined; return [_rootBlockElement, _blockElement]; }, [listViewRef, rootClientId, clientId]); // The targetElement is the element that the drop indicator will appear // before or after. When dropping into an empty block list, blockElement // is undefined, so the indicator will appear after the rootBlockElement. const targetElement = blockElement || rootBlockElement; const rtl = isRTL(); const getDropIndicatorWidth = useCallback((targetElementRect, indent) => { if (!targetElement) { return 0; } // Default to assuming that the width of the drop indicator // should be the same as the target element. let width = targetElement.offsetWidth; // In deeply nested lists, where a scrollbar is present, // the width of the drop indicator should be the width of // the scroll container, minus the distance from the left // edge of the scroll container to the left edge of the // target element. const scrollContainer = getScrollContainer(targetElement, 'horizontal'); const ownerDocument = targetElement.ownerDocument; const windowScroll = scrollContainer === ownerDocument.body || scrollContainer === ownerDocument.documentElement; if (scrollContainer && !windowScroll) { const scrollContainerRect = scrollContainer.getBoundingClientRect(); const distanceBetweenContainerAndTarget = isRTL() ? scrollContainerRect.right - targetElementRect.right : targetElementRect.left - scrollContainerRect.left; const scrollContainerWidth = scrollContainer.clientWidth; if (scrollContainerWidth < width + distanceBetweenContainerAndTarget) { width = scrollContainerWidth - distanceBetweenContainerAndTarget; } // LTR logic for ensuring the drop indicator does not extend // beyond the right edge of the scroll container. if (!rtl && targetElementRect.left + indent < scrollContainerRect.left) { width -= scrollContainerRect.left - targetElementRect.left; return width; } // RTL logic for ensuring the drop indicator does not extend // beyond the right edge of the scroll container. if (rtl && targetElementRect.right - indent > scrollContainerRect.right) { width -= targetElementRect.right - scrollContainerRect.right; return width; } } // Subtract the indent from the final width of the indicator. return width - indent; }, [rtl, targetElement]); const style = useMemo(() => { if (!targetElement) { return {}; } const targetElementRect = targetElement.getBoundingClientRect(); return { width: getDropIndicatorWidth(targetElementRect, 0) }; }, [getDropIndicatorWidth, targetElement]); const horizontalScrollOffsetStyle = useMemo(() => { if (!targetElement) { return {}; } const scrollContainer = getScrollContainer(targetElement); const ownerDocument = targetElement.ownerDocument; const windowScroll = scrollContainer === ownerDocument.body || scrollContainer === ownerDocument.documentElement; if (scrollContainer && !windowScroll) { const scrollContainerRect = scrollContainer.getBoundingClientRect(); const targetElementRect = targetElement.getBoundingClientRect(); const distanceBetweenContainerAndTarget = rtl ? scrollContainerRect.right - targetElementRect.right : targetElementRect.left - scrollContainerRect.left; if (!rtl && scrollContainerRect.left > targetElementRect.left) { return { transform: `translateX( ${distanceBetweenContainerAndTarget}px )` }; } if (rtl && scrollContainerRect.right < targetElementRect.right) { return { transform: `translateX( ${distanceBetweenContainerAndTarget * -1}px )` }; } } return {}; }, [rtl, targetElement]); const ariaLevel = useMemo(() => { if (!rootBlockElement) { return 1; } const _ariaLevel = parseInt(rootBlockElement.getAttribute('aria-level'), 10); return _ariaLevel ? _ariaLevel + 1 : 1; }, [rootBlockElement]); const hasAdjacentSelectedBranch = useMemo(() => { if (!targetElement) { return false; } return targetElement.classList.contains('is-branch-selected'); }, [targetElement]); const popoverAnchor = useMemo(() => { const isValidDropPosition = dropPosition === 'top' || dropPosition === 'bottom' || dropPosition === 'inside'; if (!targetElement || !isValidDropPosition) { return undefined; } return { contextElement: targetElement, getBoundingClientRect() { const rect = targetElement.getBoundingClientRect(); // In RTL languages, the drop indicator should be positioned // to the left of the target element, with the width of the // indicator determining the indent at the right edge of the // target element. In LTR languages, the drop indicator should // end at the right edge of the target element, with the indent // added to the position of the left edge of the target element. // let left = rtl ? rect.left : rect.left + indent; let left = rect.left; let top = 0; // In deeply nested lists, where a scrollbar is present, // the width of the drop indicator should be the width of // the visible area of the scroll container. Additionally, // the left edge of the drop indicator line needs to be // offset by the distance the left edge of the target element // and the left edge of the scroll container. The ensures // that the drop indicator position never breaks out of the // visible area of the scroll container. const scrollContainer = getScrollContainer(targetElement, 'horizontal'); const doc = targetElement.ownerDocument; const windowScroll = scrollContainer === doc.body || scrollContainer === doc.documentElement; // If the scroll container is not the window, offset the left position, if need be. if (scrollContainer && !windowScroll) { const scrollContainerRect = scrollContainer.getBoundingClientRect(); // In RTL languages, a vertical scrollbar is present on the // left edge of the scroll container. The width of the // scrollbar needs to be accounted for when positioning the // drop indicator. const scrollbarWidth = rtl ? scrollContainer.offsetWidth - scrollContainer.clientWidth : 0; if (left < scrollContainerRect.left + scrollbarWidth) { left = scrollContainerRect.left + scrollbarWidth; } } if (dropPosition === 'top') { top = rect.top - rect.height * 2; } else { // `dropPosition` is either `bottom` or `inside` top = rect.top; } const width = getDropIndicatorWidth(rect, 0); const height = rect.height; return new window.DOMRect(left, top, width, height); } }; }, [targetElement, dropPosition, getDropIndicatorWidth, rtl]); if (!targetElement) { return null; } return /*#__PURE__*/_jsx(Popover, { animate: false, anchor: popoverAnchor, focusOnMount: false, className: "block-editor-list-view-drop-indicator--preview", variant: "unstyled", flip: false, resize: true, children: /*#__PURE__*/_jsx("div", { style: style, className: clsx('block-editor-list-view-drop-indicator__line', { 'block-editor-list-view-drop-indicator__line--darker': hasAdjacentSelectedBranch }), children: /*#__PURE__*/_jsxs("div", { className: "block-editor-list-view-leaf", "aria-level": ariaLevel, children: [/*#__PURE__*/_jsxs("div", { className: clsx('block-editor-list-view-block-select-button', 'block-editor-list-view-block-contents'), style: horizontalScrollOffsetStyle, children: [/*#__PURE__*/_jsx(ListViewExpander, { onClick: () => {} }), /*#__PURE__*/_jsx(BlockIcon, { icon: blockInformation?.icon, showColors: true, context: "list-view" }), /*#__PURE__*/_jsx(HStack, { alignment: "center", className: "block-editor-list-view-block-select-button__label-wrapper", justify: "flex-start", spacing: 1, children: /*#__PURE__*/_jsx("span", { className: "block-editor-list-view-block-select-button__title", children: /*#__PURE__*/_jsx(Truncate, { ellipsizeMode: "auto", children: blockTitle }) }) })] }), /*#__PURE__*/_jsx("div", { className: "block-editor-list-view-block__menu-cell" })] }) }) }); } //# sourceMappingURL=drop-indicator.js.map