UNPKG

insert-text-at-cursor

Version:

Cross-browser lib for inserting text at selection in a textarea / input

116 lines (99 loc) 3.83 kB
let browserSupportsTextareaTextNodes; /** * @param {HTMLElement} input * @return {boolean} */ function canManipulateViaTextNodes(input) { if (input.nodeName !== "TEXTAREA") { return false; } if (typeof browserSupportsTextareaTextNodes === "undefined") { const textarea = document.createElement("textarea"); textarea.value = 1; browserSupportsTextareaTextNodes = !!textarea.firstChild; } return browserSupportsTextareaTextNodes; } /** * @param {HTMLTextAreaElement|HTMLInputElement} input * @param {string} text * @returns {void} */ export default function(input, text) { // Most of the used APIs only work with the field selected input.focus(); // IE 8-10 if (document.selection) { const ieRange = document.selection.createRange(); ieRange.text = text; // Move cursor after the inserted text ieRange.collapse(false /* to the end */); ieRange.select(); return; } // Webkit + Edge const isSuccess = document.execCommand("insertText", false, text); if (!isSuccess) { const start = input.selectionStart; const end = input.selectionEnd; // Firefox (non-standard method) if (typeof input.setRangeText === "function") { input.setRangeText(text); } else { // To make a change we just need a Range, not a Selection const range = document.createRange(); const textNode = document.createTextNode(text); if (canManipulateViaTextNodes(input)) { let node = input.firstChild; // If textarea is empty, just insert the text if (!node) { input.appendChild(textNode); } else { // Otherwise we need to find a nodes for start and end let offset = 0; let startNode = null; let endNode = null; while (node && (startNode === null || endNode === null)) { const nodeLength = node.nodeValue.length; // if start of the selection falls into current node if (start >= offset && start <= offset + nodeLength) { range.setStart((startNode = node), start - offset); } // if end of the selection falls into current node if (end >= offset && end <= offset + nodeLength) { range.setEnd((endNode = node), end - offset); } offset += nodeLength; node = node.nextSibling; } // If there is some text selected, remove it as we should replace it if (start !== end) { range.deleteContents(); } } } // If the node is a textarea and the range doesn't span outside the element // // Get the commonAncestorContainer of the selected range and test its type // If the node is of type `#text` it means that we're still working with text nodes within our textarea element // otherwise, if it's of type `#document` for example it means our selection spans outside the textarea. if ( canManipulateViaTextNodes(input) && range.commonAncestorContainer.nodeName === "#text" ) { // Finally insert a new node. The browser will automatically split start and end nodes into two if necessary range.insertNode(textNode); } else { // If the node is not a textarea or the range spans outside a textarea the only way is to replace the whole value const value = input.value; input.value = value.slice(0, start) + text + value.slice(end); } } // Correct the cursor position to be at the end of the insertion input.setSelectionRange(start + text.length, start + text.length); // Notify any possible listeners of the change const e = document.createEvent("UIEvent"); e.initEvent("input", true, false); input.dispatchEvent(e); } }