@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
236 lines (235 loc) • 7.2 kB
JavaScript
// 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