diff --git a/packages/caret/package.json b/packages/caret/package.json index fd2ce5f..5f9edc3 100644 --- a/packages/caret/package.json +++ b/packages/caret/package.json @@ -3,7 +3,7 @@ "description": "Utils useful for work with caret for Editor.js tools development", "repository": "https://github.com/editor-js/utils/tree/main/packages/caret", "link": "https://github.com/editor-js/utils/tree/main/packages/caret", - "version": "1.0.0", + "version": "1.0.1", "main": "dist/index.js", "license": "MIT", "scripts": { diff --git a/packages/caret/src/focus/focus.ts b/packages/caret/src/focus/focus.ts index 066e9b0..01783a1 100644 --- a/packages/caret/src/focus/focus.ts +++ b/packages/caret/src/focus/focus.ts @@ -20,8 +20,61 @@ export function focus(element: HTMLElement, atStart: boolean = true): void { return; } - range.selectNodeContents(element); - range.collapse(atStart); + /** + * Helper function to create a new text node and set the caret + * @param parent - parent element to append the text node + */ + const createAndFocusTextNode = (parent: HTMLElement): void => { + const textNode = document.createTextNode(''); + + parent.appendChild(textNode); + range.setStart(textNode, 0); + range.setEnd(textNode, 0); + }; + + /** + * Helper for checking for null and undefined + * @param v - value to check + */ + const isDefinedAndNotNull = (v: T): v is T => v !== undefined && v !== null; + + /** + * We need to set focus at start/end to the text node inside an element + */ + + let childNodes = element.childNodes; + let nodeToFocus = atStart ? childNodes[0] : childNodes[childNodes.length - 1]; + + if (isDefinedAndNotNull(nodeToFocus)) { + /** + * Ensure the nodeToFocus is a text node, + * if it's not, drill down to find a text node + */ + while (isDefinedAndNotNull(nodeToFocus) && nodeToFocus.nodeType !== Node.TEXT_NODE) { + nodeToFocus = atStart ? nodeToFocus.firstChild : nodeToFocus.lastChild; + } + + /** + * If a text node is found, place the caret + */ + if (isDefinedAndNotNull(nodeToFocus) && nodeToFocus.nodeType === Node.TEXT_NODE) { + const length = nodeToFocus.textContent?.length ?? 0; + const position = atStart ? 0 : length; + + range.setStart(nodeToFocus, position); + range.setEnd(nodeToFocus, position); + } else { + /** + * If no text node is found, create one and set focus + */ + createAndFocusTextNode(element); + } + } else { + /** + * If the element is empty, create a text node and place the caret at the start + */ + createAndFocusTextNode(element); + } selection.removeAllRanges(); selection.addRange(range);