/
githubmirror
/
gutenberg
Обзор
Документация
Войти
/
githubmirror
/
gutenberg
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
packages/block-editor/src/utils/dom.ts
234 строки
6 KB
Ella
Add an `editableRoot` block support for native cross-block selection (#79105)
14 июл 2026, 09:17
Не верифицирован
14 июл 2026, 09:17
3944b0e
Код
Авторство
О чём код?
const BLOCK_SELECTOR = '.block-editor-block-list__block'; const APPENDER_SELECTOR = '.block-list-appender'; const BLOCK_APPENDER_CLASS = '.block-editor-button-block-appender'; /** * Returns true if two elements are contained within the same block. * * @param a First element. * @param b Second element. * * @return Whether elements are in the same block. */ export function isInSameBlock( a: Element, b: Element ) { return a.closest( BLOCK_SELECTOR ) === b.closest( BLOCK_SELECTOR ); } /** * Returns true if an element is considered part of the block and not its inner * blocks or appender. * * @param blockElement Block container element. * @param element Element. * * @return Whether an element is considered part of the block and not * its inner blocks or appender. */ export function isInsideRootBlock( blockElement: Element, element: Element ) { const parentBlock = element.closest( [ BLOCK_SELECTOR, APPENDER_SELECTOR, BLOCK_APPENDER_CLASS ].join( ',' ) ); return parentBlock === blockElement; } /** * Returns the editable element containing the selection, if the selection is * fully contained within one. The root element (an editable wrapper) does not * count as a containing editable element. * * @param selection The selection. * @param root The editable wrapper element. * * @return The editable element containing the selection. */ export function getSelectionEditableElement( selection: Selection, root: Element ) { const { anchorNode, focusNode } = selection; if ( ! anchorNode || ! focusNode ) { return; } const element = anchorNode.nodeType === anchorNode.ELEMENT_NODE ? ( anchorNode as Element ) : anchorNode.parentElement; const editable = element?.closest( '[contenteditable="true"]' ); if ( ! editable || editable === root || ! editable.contains( focusNode ) ) { return; } return editable; } /** * Finds the block client ID given any DOM node inside the block. * * @param node DOM node. * * @return Client ID or undefined if the node is not part of * a block. */ export function getBlockClientId( node: Node | null ) { while ( node && node.nodeType !== node.ELEMENT_NODE ) { node = node.parentNode; } if ( ! node ) { return; } const elementNode = node as Element; const blockNode = elementNode.closest( BLOCK_SELECTOR ); if ( ! blockNode ) { return; } return blockNode.id.slice( 'block-'.length ); } /** * Calculates the union of two rectangles. * * @param rect1 First rectangle. * @param rect2 Second rectangle. * @return Union of the two rectangles. */ export function rectUnion( rect1: DOMRect, rect2: DOMRect ) { const left = Math.min( rect1.left, rect2.left ); const right = Math.max( rect1.right, rect2.right ); const bottom = Math.max( rect1.bottom, rect2.bottom ); const top = Math.min( rect1.top, rect2.top ); return new window.DOMRectReadOnly( left, top, right - left, bottom - top ); } /** * Returns whether an element is visible. * * @param element Element. * @return Whether the element is visible. */ export function isElementVisible( element: Element ) { const viewport = element.ownerDocument.defaultView; if ( ! viewport ) { return false; } // Check for <VisuallyHidden> components. if ( element.hasAttribute( 'data-visually-hidden' ) ) { return false; } const bounds = element.getBoundingClientRect(); if ( bounds.width === 0 || bounds.height === 0 ) { return false; } // Older browsers, e.g. Safari < 17.4 may not support the `checkVisibility` method. if ( element.checkVisibility ) { return element.checkVisibility?.( { opacityProperty: true, contentVisibilityAuto: true, visibilityProperty: true, } ); } const style = viewport.getComputedStyle( element ); if ( style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0' ) { return false; } return true; } /** * Checks if the element is scrollable. * * @param element Element. * @return True if the element is scrollable. */ function isScrollable( element: Element ) { const style = window.getComputedStyle( element ); return ( style.overflowX === 'auto' || style.overflowX === 'scroll' || style.overflowY === 'auto' || style.overflowY === 'scroll' ); } export const WITH_OVERFLOW_ELEMENT_BLOCKS = [ 'core/navigation' ]; /** * Returns the bounding rectangle of an element, with special handling for blocks * that have visible overflowing children (defined in WITH_OVERFLOW_ELEMENT_BLOCKS). * * For blocks like Navigation that can have overflowing elements (e.g. submenus), * this function calculates the combined bounds of both the parent and its visible * children. The returned rect may extend beyond the viewport. * The returned rect represents the full extent of the element and its visible * children, which may extend beyond the viewport. * * @param element Element. * @return Bounding client rect of the element and its visible children. */ export function getElementBounds( element: Element ) { const viewport = element.ownerDocument.defaultView; if ( ! viewport ) { return new window.DOMRectReadOnly(); } let bounds = element.getBoundingClientRect(); const dataType = element.getAttribute( 'data-type' ); /* * For blocks with overflowing elements (like Navigation), include the bounds * of visible children that extend beyond the parent container. */ if ( dataType && WITH_OVERFLOW_ELEMENT_BLOCKS.includes( dataType ) ) { const stack = [ element ]; let currentElement; while ( ( currentElement = stack.pop() ) ) { // Children won’t affect bounds unless the element is not scrollable. if ( ! isScrollable( currentElement ) ) { for ( const child of currentElement.children ) { if ( isElementVisible( child ) ) { const childBounds = child.getBoundingClientRect(); bounds = rectUnion( bounds, childBounds ); stack.push( child ); } } } } } /* * Take into account the outer horizontal limits of the container in which * an element is supposed to be "visible". For example, if an element is * positioned -10px to the left of the window x value (0), this function * discounts the negative overhang because it's not visible and therefore * not to be counted in the visibility calculations. Top and bottom values * are not accounted for to accommodate vertical scroll. */ const left = Math.max( bounds.left, 0 ); const right = Math.min( bounds.right, viewport.innerWidth ); bounds = new window.DOMRectReadOnly( left, bounds.top, right - left, bounds.height ); return bounds; }