@gechiui/block-editor
Version:
157 lines (126 loc) • 4.39 kB
JavaScript
"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