UNPKG

@wordpress/block-editor

Version:
168 lines (167 loc) 6.15 kB
// 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