@chayns-components/emoji-input
Version:
Input field that supports HTML elements and emojis
281 lines (272 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.setChildIndex = exports.saveSelection = exports.restoreSelection = exports.moveSelectionOffset = exports.insertInvisibleCursorMarker = exports.getCharCodeThatWillBeDeleted = exports.findAndSelectText = void 0;
var _number = require("./number");
var _text = require("./text");
let childIndex = -1;
let endOffset = -1;
let startOffset = -1;
const saveSelection = (element, {
shouldIgnoreEmptyTextNodes
} = {}) => {
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(({
nodeType,
nodeValue
}) => nodeType !== Node.TEXT_NODE || nodeValue !== '' && nodeValue !== '\u200B');
}
childIndex = childNodesArray.indexOf(anchorNode);
endOffset = range.endOffset;
startOffset = range.startOffset;
};
exports.saveSelection = saveSelection;
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 = (0, _text.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 {
var _childNode$parentNode;
const textNode = document.createTextNode('\u200B');
(_childNode$parentNode = childNode.parentNode) === null || _childNode$parentNode === void 0 || _childNode$parentNode.insertBefore(textNode, childNode.nextSibling);
childNode = textNode;
endOffset = textNode.length;
startOffset = textNode.length;
}
} else {
var _childNode$parentNode2;
const textNode = document.createTextNode('\u200B');
(_childNode$parentNode2 = childNode.parentNode) === null || _childNode$parentNode2 === void 0 || _childNode$parentNode2.insertBefore(textNode, childNode.nextSibling);
childNode = textNode;
endOffset = textNode.length;
startOffset = textNode.length;
}
} else if (childNode.nodeValue && endOffset > childNode.nodeValue.length) {
var _childNode$nextSiblin;
if ((_childNode$nextSiblin = childNode.nextSibling) !== null && _childNode$nextSiblin !== void 0 && _childNode$nextSiblin.nodeValue) {
let elementTextLength = childNode.nodeValue.length;
childNode = childNode.nextSibling;
// noinspection SuspiciousTypeOfGuard
if (typeof childNode.nodeValue !== 'string') {
var _childNode$nextSiblin2;
elementTextLength += (0, _text.getElementTextLength)(childNode);
if ((_childNode$nextSiblin2 = childNode.nextSibling) !== null && _childNode$nextSiblin2 !== void 0 && _childNode$nextSiblin2.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 {
var _childNode$parentNode3;
const textNode = document.createTextNode('\u200B');
(_childNode$parentNode3 = childNode.parentNode) === null || _childNode$parentNode3 === void 0 || _childNode$parentNode3.insertBefore(textNode, childNode.nextSibling);
childNode = textNode;
endOffset = textNode.length;
startOffset = textNode.length;
}
} else {
var _childNode$parentNode4;
const textNode = document.createTextNode('\u200B');
(_childNode$parentNode4 = childNode.parentNode) === null || _childNode$parentNode4 === void 0 || _childNode$parentNode4.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 = (0, _number.clamp)(startOffset, 0, childNode.nodeValue.length);
endOffset = (0, _number.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);
};
exports.restoreSelection = restoreSelection;
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);
}
}
};
exports.insertInvisibleCursorMarker = insertInvisibleCursorMarker;
const moveSelectionOffset = distance => {
endOffset += distance;
startOffset += distance;
};
exports.moveSelectionOffset = moveSelectionOffset;
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"
*/
exports.setChildIndex = setChildIndex;
const getCharCodeThatWillBeDeleted = event => {
var _window$getSelection, _nextSibling$nodeValu;
const range = (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 ? void 0 : _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') {
var _previousSibling$node;
const {
nodeValue,
previousSibling
} = range.startContainer;
if (range.startOffset > 0) {
return nodeValue === null || nodeValue === void 0 ? void 0 : nodeValue.charCodeAt(range.startOffset - 1);
}
return previousSibling === null || previousSibling === void 0 || (_previousSibling$node = previousSibling.nodeValue) === null || _previousSibling$node === void 0 ? void 0 : _previousSibling$node.charCodeAt(previousSibling.nodeValue.length - 1);
}
const {
nextSibling,
nodeValue
} = range.endContainer;
if (range.endOffset < ((nodeValue === null || nodeValue === void 0 ? void 0 : nodeValue.length) ?? 0)) {
return nodeValue === null || nodeValue === void 0 ? void 0 : nodeValue.charCodeAt(range.endOffset);
}
return nextSibling === null || nextSibling === void 0 || (_nextSibling$nodeValu = nextSibling.nodeValue) === null || _nextSibling$nodeValu === void 0 ? void 0 : _nextSibling$nodeValu.charCodeAt(0);
};
exports.getCharCodeThatWillBeDeleted = getCharCodeThatWillBeDeleted;
const findAndSelectText = ({
editorElement,
searchText
}) => {
var _editorElement$textCo;
if (!((_editorElement$textCo = editorElement.textContent) !== null && _editorElement$textCo !== void 0 && _editorElement$textCo.includes(searchText))) {
return null;
}
const range = document.createRange();
let startNode = null;
let offset = -1;
const searchNodesForText = node => {
if (node.nodeType === Node.TEXT_NODE) {
var _node$textContent;
const index = (_node$textContent = node.textContent) === null || _node$textContent === void 0 ? void 0 : _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;
};
exports.findAndSelectText = findAndSelectText;
//# sourceMappingURL=selection.js.map