UNPKG

@chayns-components/emoji-input

Version:
263 lines (255 loc) • 9.11 kB
import { clamp } from './number'; import { getElementTextLength } from './text'; let childIndex = -1; let endOffset = -1; let startOffset = -1; export const saveSelection = function (element) { let { shouldIgnoreEmptyTextNodes } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const selection = window.getSelection(); if (!selection) { return; } const { anchorNode } = selection; if (!anchorNode) { return; } const range = selection.getRangeAt(0); let childNodesArray = Array.from(element.childNodes); if (shouldIgnoreEmptyTextNodes) { childNodesArray = childNodesArray.filter(_ref => { let { nodeType, nodeValue } = _ref; return nodeType !== Node.TEXT_NODE || nodeValue !== '' && nodeValue !== '\u200B'; }); } childIndex = childNodesArray.indexOf(anchorNode); endOffset = range.endOffset; startOffset = range.startOffset; }; export const restoreSelection = element => { // Search for \u200C in child nodes. If found, set the childIndex, startOffset, and endOffset to the // position of the \u200C character. Also remove the \u200C character from the child node. If not found, // the childIndex, startOffset, and endOffset will be like before. const childNodesArray = Array.from(element.childNodes); let hasFoundNoJoiner = false; childNodesArray.forEach(node => { if (!hasFoundNoJoiner && node.nodeType === Node.TEXT_NODE && typeof node.nodeValue === 'string') { const noJoinerIndex = node.nodeValue.indexOf('\u200C'); if (noJoinerIndex !== -1) { hasFoundNoJoiner = true; childIndex = childNodesArray.indexOf(node); startOffset = noJoinerIndex; endOffset = noJoinerIndex; } } }); // Remove all no joiner characters from the child nodes if no joiner was found if (hasFoundNoJoiner) { childNodesArray.forEach(node => { if (node.nodeType === Node.TEXT_NODE && typeof node.nodeValue === 'string') { // eslint-disable-next-line no-param-reassign node.nodeValue = node.nodeValue.replace(/\u200C/g, ''); } }); } let childNode = element.childNodes[childIndex]; const selection = window.getSelection(); if (!childNode || !element || !selection) { return; } // noinspection SuspiciousTypeOfGuard if (typeof childNode.nodeValue !== 'string') { const elementTextLength = getElementTextLength(childNode); if (childNode.nextSibling) { childNode = childNode.nextSibling; if (childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue) { endOffset -= elementTextLength; startOffset -= elementTextLength; if (childNode.nodeValue.charCodeAt(endOffset) === 8203) { endOffset += 1; startOffset += 1; } } else { const textNode = document.createTextNode('\u200B'); childNode.parentNode?.insertBefore(textNode, childNode.nextSibling); childNode = textNode; endOffset = textNode.length; startOffset = textNode.length; } } else { const textNode = document.createTextNode('\u200B'); childNode.parentNode?.insertBefore(textNode, childNode.nextSibling); childNode = textNode; endOffset = textNode.length; startOffset = textNode.length; } } else if (childNode.nodeValue && endOffset > childNode.nodeValue.length) { if (childNode.nextSibling?.nodeValue) { let elementTextLength = childNode.nodeValue.length; childNode = childNode.nextSibling; // noinspection SuspiciousTypeOfGuard if (typeof childNode.nodeValue !== 'string') { elementTextLength += getElementTextLength(childNode); if (childNode.nextSibling?.nodeValue) { childNode = childNode.nextSibling; if (childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue) { endOffset -= elementTextLength; startOffset -= elementTextLength; if (childNode.nodeValue.charCodeAt(endOffset) === 8203) { endOffset += 1; startOffset += 1; } } else { const textNode = document.createTextNode('\u200B'); childNode.parentNode?.insertBefore(textNode, childNode.nextSibling); childNode = textNode; endOffset = textNode.length; startOffset = textNode.length; } } else { const textNode = document.createTextNode('\u200B'); childNode.parentNode?.insertBefore(textNode, childNode.nextSibling); childNode = textNode; endOffset = textNode.length; startOffset = textNode.length; } } } else { endOffset = childNode.nodeValue.length; startOffset = childNode.nodeValue.length; } } const range = document.createRange(); // noinspection SuspiciousTypeOfGuard if (typeof childNode.nodeValue === 'string') { startOffset = clamp(startOffset, 0, childNode.nodeValue.length); endOffset = clamp(endOffset, 0, childNode.nodeValue.length); } try { range.setStart(childNode, startOffset); range.setEnd(childNode, endOffset); } catch (error) { // Do nothing } selection.removeAllRanges(); selection.addRange(range); range.collapse(true); }; export const insertInvisibleCursorMarker = () => { const selection = window.getSelection(); if (!selection || selection.rangeCount === 0) { return; } const range = selection.getRangeAt(0); const textNode = range.startContainer; const offset = range.startOffset; // noinspection JSDeprecatedSymbols document.execCommand('delete', false); const span = document.createElement('span'); span.style.display = 'inline-block'; span.style.width = '0'; span.style.height = '0'; span.className = 'invisible-cursor-marker'; const parent = textNode.parentNode; if (parent) { if (textNode.nodeType === Node.TEXT_NODE) { const textContent = textNode.textContent || ''; const beforeText = textContent.slice(0, offset); const afterText = textContent.slice(offset); textNode.textContent = beforeText; parent.insertBefore(span, textNode.nextSibling); const afterTextNode = document.createTextNode(afterText); parent.insertBefore(afterTextNode, span.nextSibling); setTimeout(() => { // Set cursor to cursor element const newRange = document.createRange(); newRange.setStartAfter(span); newRange.setEndAfter(span); selection.removeAllRanges(); selection.addRange(newRange); // Remove cursor element span.remove(); }, 10); } } }; export const moveSelectionOffset = distance => { endOffset += distance; startOffset += distance; }; export const setChildIndex = index => { childIndex = index; }; /** * This function returns the code of the character that will be removed by the KeyDown event in the * next step, if the "Backspace" or "Delete" key was pressed and there is no selection of multiple * characters. * * @param event - Keyboard event from "onKeyDown" */ export const getCharCodeThatWillBeDeleted = event => { const range = window.getSelection()?.getRangeAt(0); /** * At this point the function is aborted if there is no selection range, several characters have * been selected and therefore no single letter is removed or neither the "Backspace" nor the * "Delete" key has been pressed. */ if (!range || range.endOffset !== range.startOffset || event.key !== 'Backspace' && event.key !== 'Delete' && event.key !== 'Unidentified') { return null; } if (event.key === 'Backspace' || event.key === 'Unidentified') { const { nodeValue, previousSibling } = range.startContainer; if (range.startOffset > 0) { return nodeValue?.charCodeAt(range.startOffset - 1); } return previousSibling?.nodeValue?.charCodeAt(previousSibling.nodeValue.length - 1); } const { nextSibling, nodeValue } = range.endContainer; if (range.endOffset < (nodeValue?.length ?? 0)) { return nodeValue?.charCodeAt(range.endOffset); } return nextSibling?.nodeValue?.charCodeAt(0); }; export const findAndSelectText = _ref2 => { let { editorElement, searchText } = _ref2; if (!editorElement.textContent?.includes(searchText)) { return null; } const range = document.createRange(); let startNode = null; let offset = -1; const searchNodesForText = node => { if (node.nodeType === Node.TEXT_NODE) { const index = node.textContent?.indexOf(searchText); if (typeof index === 'number' && index !== -1) { startNode = node; offset = index; range.setStart(node, index); range.setEnd(node, index + searchText.length); return true; } } else if (node.nodeName !== 'LC_MENTION') { return Array.from(node.childNodes).some(searchNodesForText); } return false; }; searchNodesForText(editorElement); if (startNode && offset !== -1) return range; return null; }; //# sourceMappingURL=selection.js.map