mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
These used to be used by partial render. ReactDOMDispatcher ended up not being used in this way. Move shared DOM files to client. These are only used by client abstractions now. They're inlined in the Fizz code so they're no longer shared.
203 lines
5.8 KiB
JavaScript
203 lines
5.8 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and 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 './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 ft-flow/no-unused-expressions */
|
|
anchorNode.nodeType;
|
|
focusNode.nodeType;
|
|
/* eslint-enable ft-flow/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) {
|
|
const 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);
|
|
}
|
|
}
|
|
}
|