@wordpress/block-editor
Version:
168 lines (167 loc) • 6.15 kB
JavaScript
// packages/block-editor/src/components/writing-flow/use-selection-observer.js
import { useSelect, useDispatch } from "@wordpress/data";
import { useRefEffect } from "@wordpress/compose";
import { create } from "@wordpress/rich-text";
import { isSelectionForward } from "@wordpress/dom";
import { store as blockEditorStore } from "../../store";
import { getBlockClientId } from "../../utils/dom";
function extractSelectionStartNode(selection) {
const { anchorNode, anchorOffset } = selection;
if (anchorNode.nodeType === anchorNode.TEXT_NODE) {
return anchorNode;
}
if (anchorOffset === 0) {
return anchorNode;
}
return anchorNode.childNodes[anchorOffset - 1];
}
function extractSelectionEndNode(selection) {
const { focusNode, focusOffset } = selection;
if (focusNode.nodeType === focusNode.TEXT_NODE) {
return focusNode;
}
if (focusOffset === focusNode.childNodes.length) {
return focusNode;
}
if (focusOffset === 0 && isSelectionForward(selection)) {
return focusNode.previousSibling ?? focusNode.parentElement;
}
return focusNode.childNodes[focusOffset];
}
function findDepth(a, b) {
let depth = 0;
while (a[depth] === b[depth]) {
depth++;
}
return depth;
}
function setContentEditableWrapper(node, value) {
if (node.contentEditable !== String(value)) {
node.contentEditable = value;
if (value) {
node.focus();
}
}
}
function getRichTextElement(node) {
const element = node.nodeType === node.ELEMENT_NODE ? node : node.parentElement;
return element?.closest("[data-wp-block-attribute-key]");
}
function useSelectionObserver() {
const { multiSelect, selectBlock, selectionChange } = useDispatch(blockEditorStore);
const { getBlockParents, getBlockSelectionStart, isMultiSelecting } = useSelect(blockEditorStore);
return useRefEffect(
(node) => {
const { ownerDocument } = node;
const { defaultView } = ownerDocument;
function onSelectionChange(event) {
const selection = defaultView.getSelection();
if (!selection.rangeCount) {
return;
}
const startNode = extractSelectionStartNode(selection);
const endNode = extractSelectionEndNode(selection);
if (!node.contains(startNode) || !node.contains(endNode)) {
return;
}
const isClickShift = event.shiftKey && event.type === "mouseup";
if (selection.isCollapsed && !isClickShift) {
if (node.contentEditable === "true" && !isMultiSelecting()) {
setContentEditableWrapper(node, false);
let element = startNode.nodeType === startNode.ELEMENT_NODE ? startNode : startNode.parentElement;
element = element?.closest("[contenteditable]");
element?.focus();
}
return;
}
let startClientId = getBlockClientId(startNode);
let endClientId = getBlockClientId(endNode);
if (isClickShift) {
const selectedClientId = getBlockSelectionStart();
const clickedClientId = getBlockClientId(event.target);
const focusNodeIsNonSelectable = clickedClientId !== endClientId;
if (startClientId === endClientId && selection.isCollapsed || !endClientId || focusNodeIsNonSelectable) {
endClientId = clickedClientId;
}
if (startClientId !== selectedClientId) {
startClientId = selectedClientId;
}
}
if (startClientId === void 0 && endClientId === void 0) {
setContentEditableWrapper(node, false);
return;
}
const isSingularSelection = startClientId === endClientId;
if (isSingularSelection) {
if (!isMultiSelecting()) {
selectBlock(startClientId);
} else {
multiSelect(startClientId, startClientId);
}
} else {
const startPath = [
...getBlockParents(startClientId),
startClientId
];
const endPath = [
...getBlockParents(endClientId),
endClientId
];
const depth = findDepth(startPath, endPath);
if (startPath[depth] !== startClientId || endPath[depth] !== endClientId) {
multiSelect(startPath[depth], endPath[depth]);
return;
}
const richTextElementStart = getRichTextElement(startNode);
const richTextElementEnd = getRichTextElement(endNode);
if (richTextElementStart && richTextElementEnd) {
const range = selection.getRangeAt(0);
const richTextDataStart = create({
element: richTextElementStart,
range,
__unstableIsEditableTree: true
});
const richTextDataEnd = create({
element: richTextElementEnd,
range,
__unstableIsEditableTree: true
});
const startOffset = richTextDataStart.start ?? richTextDataStart.end;
const endOffset = richTextDataEnd.start ?? richTextDataEnd.end;
selectionChange({
start: {
clientId: startClientId,
attributeKey: richTextElementStart.dataset.wpBlockAttributeKey,
offset: startOffset
},
end: {
clientId: endClientId,
attributeKey: richTextElementEnd.dataset.wpBlockAttributeKey,
offset: endOffset
}
});
} else {
multiSelect(startClientId, endClientId);
}
}
}
ownerDocument.addEventListener(
"selectionchange",
onSelectionChange
);
defaultView.addEventListener("mouseup", onSelectionChange);
return () => {
ownerDocument.removeEventListener(
"selectionchange",
onSelectionChange
);
defaultView.removeEventListener("mouseup", onSelectionChange);
};
},
[multiSelect, selectBlock, selectionChange, getBlockParents]
);
}
export {
useSelectionObserver as default
};
//# sourceMappingURL=use-selection-observer.js.map