UNPKG

@wordpress/editor

Version:
236 lines (235 loc) 7.2 kB
// packages/editor/src/components/collaborators-overlay/cursor-dom-utils.ts import { privateApis as blockEditorPrivateApis } from "@wordpress/block-editor"; import { unlock } from "../../lock-unlock.mjs"; var { isElementVisible } = unlock(blockEditorPrivateApis); var getNearestVisibleBlockAncestor = (element) => { let current = element.closest("[data-block]"); while (current) { if (isElementVisible(current)) { return current; } current = current.parentElement?.closest("[data-block]") ?? null; } return null; }; var MAX_NODE_OFFSET_COUNT = 500; var getCursorPosition = (absolutePositionIndex, blockElement, editorDocument, overlayRect) => { if (absolutePositionIndex === null || !blockElement) { return null; } return getOffsetPositionInBlock( blockElement, absolutePositionIndex, editorDocument, overlayRect ) ?? null; }; var getOffsetPositionInBlock = (blockElement, charOffset, editorDocument, overlayRect) => { if (!isElementVisible(blockElement)) { return null; } const { node, offset } = findInnerBlockOffset( blockElement, charOffset, editorDocument ); const cursorRange = editorDocument.createRange(); try { cursorRange.setStart(node, offset); } catch { return null; } cursorRange.collapse(true); const cursorRect = cursorRange.getBoundingClientRect(); const blockRect = blockElement.getBoundingClientRect(); let cursorX = 0; let cursorY = 0; if (cursorRect.x === 0 && cursorRect.y === 0 && cursorRect.width === 0 && cursorRect.height === 0) { cursorX = blockRect.left - overlayRect.left; cursorY = blockRect.top - overlayRect.top; } else { cursorX = cursorRect.left - overlayRect.left; cursorY = cursorRect.top - overlayRect.top; } let cursorHeight = cursorRect.height; if (cursorHeight === 0) { const view = editorDocument.defaultView ?? window; cursorHeight = parseInt(view.getComputedStyle(blockElement).lineHeight, 10) || blockRect.height; } return { x: cursorX, y: cursorY, height: cursorHeight }; }; var getSelectionRects = (blockElement, startOffset, endOffset, editorDocument, overlayRect) => { if (!isElementVisible(blockElement)) { return null; } let normalizedStart = startOffset; let normalizedEnd = endOffset; if (normalizedStart > normalizedEnd) { [normalizedStart, normalizedEnd] = [normalizedEnd, normalizedStart]; } const startPos = findInnerBlockOffset( blockElement, normalizedStart, editorDocument ); const endPos = findInnerBlockOffset( blockElement, normalizedEnd, editorDocument ); const range = editorDocument.createRange(); try { range.setStart(startPos.node, startPos.offset); range.setEnd(endPos.node, endPos.offset); } catch { return null; } const clientRects = range.getClientRects(); const rects = []; for (const rect of clientRects) { if (rect.width === 0 && rect.height === 0) { continue; } const x = rect.left - overlayRect.left; const y = rect.top - overlayRect.top; const isDuplicate = rects.some( (r) => r.x === x && r.y === y && r.width === rect.width && r.height === rect.height ); if (isDuplicate) { continue; } rects.push({ x, y, width: rect.width, height: rect.height }); } return rects.length > 0 ? rects : null; }; var blockContainerOf = (el) => { const parent = el.parentElement; return parent?.hasAttribute("data-block") ? parent : el; }; var getBlocksBetween = (startBlockId, endBlockId, editorDocument) => { const allBlocks = editorDocument.querySelectorAll("[data-block]"); let startIndex = -1; let endIndex = -1; for (let i = 0; i < allBlocks.length; i++) { const blockId = allBlocks[i].getAttribute("data-block"); if (blockId === startBlockId) { startIndex = i; } if (blockId === endBlockId) { endIndex = i; } } if (startIndex === -1 || endIndex === -1) { return []; } if (startIndex > endIndex) { [startIndex, endIndex] = [endIndex, startIndex]; } const result = []; for (let i = startIndex + 1; i < endIndex; i++) { const block = allBlocks[i]; if (!result.some((r) => r.contains(block))) { result.push(block); } } return result; }; var getOrderedBlockRange = (startId, endId, doc) => { const startEl = doc.querySelector( `[data-block="${startId}"]` ); const endEl = doc.querySelector( `[data-block="${endId}"]` ); if (!startEl || !endEl) { return null; } const rawFirstEl = isNodeBefore(endEl, startEl) ? endEl : startEl; const rawLastEl = isNodeBefore(endEl, startEl) ? startEl : endEl; const firstEl = blockContainerOf(rawFirstEl); const lastEl = blockContainerOf(rawLastEl); const firstId = firstEl.getAttribute("data-block"); const lastId = lastEl.getAttribute("data-block"); const sameContainer = firstId === lastId; const middleEls = sameContainer ? [] : getBlocksBetween(firstId, lastId, doc).filter( (el) => !firstEl.contains(el) && !lastEl.contains(el) ); return { firstEl, firstId, lastEl, lastId, middleEls, sameContainer }; }; var findInnerBlockOffset = (blockElement, offset, editorDocument) => { const treeWalker = editorDocument.createTreeWalker( blockElement, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT // eslint-disable-line no-bitwise ); let currentOffset = 0; let lastTextNode = null; let node = null; let nodeCount = 1; while (node = treeWalker.nextNode()) { nodeCount++; if (nodeCount > MAX_NODE_OFFSET_COUNT) { if (lastTextNode) { return { node: lastTextNode, offset: 0 }; } return { node: blockElement, offset: 0 }; } const nodeLength = node.nodeValue?.length ?? 0; if (node.nodeType === Node.ELEMENT_NODE) { if (node.nodeName === "BR") { if (currentOffset + 1 >= offset) { const nodeAfterBr = treeWalker.nextNode(); if (nodeAfterBr?.nodeType === Node.TEXT_NODE) { return { node: nodeAfterBr, offset: 0 }; } else if (lastTextNode) { return { node: lastTextNode, offset: lastTextNode.nodeValue?.length ?? 0 }; } return { node: blockElement, offset: 0 }; } currentOffset += 1; continue; } else { continue; } } if (nodeLength === 0) { continue; } if (currentOffset + nodeLength >= offset) { return { node, offset: offset - currentOffset }; } currentOffset += nodeLength; if (node.nodeType === Node.TEXT_NODE) { lastTextNode = node; } } if (lastTextNode && lastTextNode.nodeValue?.length) { return { node: lastTextNode, offset: lastTextNode.nodeValue.length }; } return { node: blockElement, offset: 0 }; }; var isNodeBefore = (a, b) => ( // eslint-disable-next-line no-bitwise !!(a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) ); export { blockContainerOf, findInnerBlockOffset, getCursorPosition, getNearestVisibleBlockAncestor, getOrderedBlockRange, getSelectionRects }; //# sourceMappingURL=cursor-dom-utils.mjs.map