UNPKG

@gechiui/block-editor

Version:
157 lines (126 loc) 4.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useMultiSelection; var _lodash = require("lodash"); var _compose = require("@gechiui/compose"); var _data = require("@gechiui/data"); var _store = require("../../store"); var _useBlockRefs = require("../block-list/use-block-props/use-block-refs"); /** * External dependencies */ /** * GeChiUI dependencies */ /** * Internal dependencies */ function toggleRichText(container, toggle) { Array.from(container.querySelectorAll('.rich-text')).forEach(node => { if (toggle) { node.setAttribute('contenteditable', true); } else { node.removeAttribute('contenteditable'); } }); } /** * Returns for the deepest node at the start or end of a container node. Ignores * any text nodes that only contain HTML formatting whitespace. * * @param {Element} node Container to search. * @param {string} type 'start' or 'end'. */ function getDeepestNode(node, type) { const child = type === 'start' ? 'firstChild' : 'lastChild'; const sibling = type === 'start' ? 'nextSibling' : 'previousSibling'; while (node[child]) { node = node[child]; while (node.nodeType === node.TEXT_NODE && /^[ \t\n]*$/.test(node.data) && node[sibling]) { node = node[sibling]; } } return node; } function selector(select) { const { isMultiSelecting, getMultiSelectedBlockClientIds, hasMultiSelection, getSelectedBlockClientId } = select(_store.store); return { isMultiSelecting: isMultiSelecting(), multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), hasMultiSelection: hasMultiSelection(), selectedBlockClientId: getSelectedBlockClientId() }; } function useMultiSelection() { const { isMultiSelecting, multiSelectedBlockClientIds, hasMultiSelection, selectedBlockClientId } = (0, _data.useSelect)(selector, []); const selectedRef = (0, _useBlockRefs.__unstableUseBlockRef)(selectedBlockClientId); // These must be in the right DOM order. const startRef = (0, _useBlockRefs.__unstableUseBlockRef)((0, _lodash.first)(multiSelectedBlockClientIds)); const endRef = (0, _useBlockRefs.__unstableUseBlockRef)((0, _lodash.last)(multiSelectedBlockClientIds)); /** * When the component updates, and there is multi selection, we need to * select the entire block contents. */ return (0, _compose.useRefEffect)(node => { const { ownerDocument } = node; const { defaultView } = ownerDocument; if (!hasMultiSelection || isMultiSelecting) { if (!selectedBlockClientId || isMultiSelecting) { return; } const selection = defaultView.getSelection(); if (selection.rangeCount && !selection.isCollapsed) { const blockNode = selectedRef.current; const { startContainer, endContainer } = selection.getRangeAt(0); if (!!blockNode && (!blockNode.contains(startContainer) || !blockNode.contains(endContainer))) { selection.removeAllRanges(); } } return; } const { length } = multiSelectedBlockClientIds; if (length < 2) { return; } // The block refs might not be immediately available // when dragging blocks into another block. if (!startRef.current || !endRef.current) { return; } // For some browsers, like Safari, it is important that focus happens // BEFORE selection. node.focus(); const selection = defaultView.getSelection(); const range = ownerDocument.createRange(); // These must be in the right DOM order. // The most stable way to select the whole block contents is to start // and end at the deepest points. const startNode = getDeepestNode(startRef.current, 'start'); const endNode = getDeepestNode(endRef.current, 'end'); // While rich text will be disabled with a delay when there is a multi // selection, we must do it immediately because it's not possible to set // selection across editable hosts. toggleRichText(node, false); range.setStartBefore(startNode); range.setEndAfter(endNode); selection.removeAllRanges(); selection.addRange(range); }, [hasMultiSelection, isMultiSelecting, multiSelectedBlockClientIds, selectedBlockClientId]); } //# sourceMappingURL=use-multi-selection.js.map