UNPKG

arkit

Version:

Visualises JavaScript, TypeScript and Flow codebases as meaningful and committable architecture diagrams

203 lines (180 loc) 5.9 kB
/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import getNodeForCharacterOffset from './getNodeForCharacterOffset'; import {TEXT_NODE} from '../shared/HTMLNodeType'; /** * @param {DOMElement} outerNode * @return {?object} */ export function getOffsets(outerNode) { const {ownerDocument} = outerNode; const win = (ownerDocument && ownerDocument.defaultView) || window; const selection = win.getSelection && win.getSelection(); if (!selection || selection.rangeCount === 0) { return null; } const {anchorNode, anchorOffset, focusNode, focusOffset} = selection; // In Firefox, anchorNode and focusNode can be "anonymous divs", e.g. the // up/down buttons on an <input type="number">. Anonymous divs do not seem to // expose properties, triggering a "Permission denied error" if any of its // properties are accessed. The only seemingly possible way to avoid erroring // is to access a property that typically works for non-anonymous divs and // catch any error that may otherwise arise. See // https://bugzilla.mozilla.org/show_bug.cgi?id=208427 try { /* eslint-disable no-unused-expressions */ anchorNode.nodeType; focusNode.nodeType; /* eslint-enable no-unused-expressions */ } catch (e) { return null; } return getModernOffsetsFromPoints( outerNode, anchorNode, anchorOffset, focusNode, focusOffset, ); } /** * Returns {start, end} where `start` is the character/codepoint index of * (anchorNode, anchorOffset) within the textContent of `outerNode`, and * `end` is the index of (focusNode, focusOffset). * * Returns null if you pass in garbage input but we should probably just crash. * * Exported only for testing. */ export function getModernOffsetsFromPoints( outerNode, anchorNode, anchorOffset, focusNode, focusOffset, ) { let length = 0; let start = -1; let end = -1; let indexWithinAnchor = 0; let indexWithinFocus = 0; let node = outerNode; let parentNode = null; outer: while (true) { let next = null; while (true) { if ( node === anchorNode && (anchorOffset === 0 || node.nodeType === TEXT_NODE) ) { start = length + anchorOffset; } if ( node === focusNode && (focusOffset === 0 || node.nodeType === TEXT_NODE) ) { end = length + focusOffset; } if (node.nodeType === TEXT_NODE) { length += node.nodeValue.length; } if ((next = node.firstChild) === null) { break; } // Moving from `node` to its first child `next`. parentNode = node; node = next; } while (true) { if (node === outerNode) { // If `outerNode` has children, this is always the second time visiting // it. If it has no children, this is still the first loop, and the only // valid selection is anchorNode and focusNode both equal to this node // and both offsets 0, in which case we will have handled above. break outer; } if (parentNode === anchorNode && ++indexWithinAnchor === anchorOffset) { start = length; } if (parentNode === focusNode && ++indexWithinFocus === focusOffset) { end = length; } if ((next = node.nextSibling) !== null) { break; } node = parentNode; parentNode = node.parentNode; } // Moving from `node` to its next sibling `next`. node = next; } if (start === -1 || end === -1) { // This should never happen. (Would happen if the anchor/focus nodes aren't // actually inside the passed-in node.) return null; } return { start: start, end: end, }; } /** * In modern non-IE browsers, we can support both forward and backward * selections. * * Note: IE10+ supports the Selection object, but it does not support * the `extend` method, which means that even in modern IE, it's not possible * to programmatically create a backward selection. Thus, for all IE * versions, we use the old IE API to create our selections. * * @param {DOMElement|DOMTextNode} node * @param {object} offsets */ export function setOffsets(node, offsets) { const doc = node.ownerDocument || document; const win = (doc && doc.defaultView) || window; // Edge fails with "Object expected" in some scenarios. // (For instance: TinyMCE editor used in a list component that supports pasting to add more, // fails when pasting 100+ items) if (!win.getSelection) { return; } const selection = win.getSelection(); const length = node.textContent.length; let start = Math.min(offsets.start, length); let end = offsets.end === undefined ? start : Math.min(offsets.end, length); // IE 11 uses modern selection, but doesn't support the extend method. // Flip backward selections, so we can set with a single range. if (!selection.extend && start > end) { let temp = end; end = start; start = temp; } const startMarker = getNodeForCharacterOffset(node, start); const endMarker = getNodeForCharacterOffset(node, end); if (startMarker && endMarker) { if ( selection.rangeCount === 1 && selection.anchorNode === startMarker.node && selection.anchorOffset === startMarker.offset && selection.focusNode === endMarker.node && selection.focusOffset === endMarker.offset ) { return; } const range = doc.createRange(); range.setStart(startMarker.node, startMarker.offset); selection.removeAllRanges(); if (start > end) { selection.addRange(range); selection.extend(endMarker.node, endMarker.offset); } else { range.setEnd(endMarker.node, endMarker.offset); selection.addRange(range); } } }