|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- /**
- * 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.
- *
- * @format
- *
- * @emails oncall+draft_js
- */
- 'use strict';
-
- var findAncestorOffsetKey = require("./findAncestorOffsetKey");
-
- var getSelectionOffsetKeyForNode = require("./getSelectionOffsetKeyForNode");
-
- var getUpdatedSelectionState = require("./getUpdatedSelectionState");
-
- var invariant = require("fbjs/lib/invariant");
-
- var isElement = require("./isElement");
-
- var nullthrows = require("fbjs/lib/nullthrows");
-
- /**
- * Convert the current selection range to an anchor/focus pair of offset keys
- * and values that can be interpreted by components.
- */
- function getDraftEditorSelectionWithNodes(editorState, root, anchorNode, anchorOffset, focusNode, focusOffset) {
- var anchorIsTextNode = anchorNode.nodeType === Node.TEXT_NODE;
- var focusIsTextNode = focusNode.nodeType === Node.TEXT_NODE; // If the selection range lies only on text nodes, the task is simple.
- // Find the nearest offset-aware elements and use the
- // offset values supplied by the selection range.
-
- if (anchorIsTextNode && focusIsTextNode) {
- return {
- selectionState: getUpdatedSelectionState(editorState, nullthrows(findAncestorOffsetKey(anchorNode)), anchorOffset, nullthrows(findAncestorOffsetKey(focusNode)), focusOffset),
- needsRecovery: false
- };
- }
-
- var anchorPoint = null;
- var focusPoint = null;
- var needsRecovery = true; // An element is selected. Convert this selection range into leaf offset
- // keys and offset values for consumption at the component level. This
- // is common in Firefox, where select-all and triple click behavior leads
- // to entire elements being selected.
- //
- // Note that we use the `needsRecovery` parameter in the callback here. This
- // is because when certain elements are selected, the behavior for subsequent
- // cursor movement (e.g. via arrow keys) is uncertain and may not match
- // expectations at the component level. For example, if an entire <div> is
- // selected and the user presses the right arrow, Firefox keeps the selection
- // on the <div>. If we allow subsequent keypresses to insert characters
- // natively, they will be inserted into a browser-created text node to the
- // right of that <div>. This is obviously undesirable.
- //
- // With the `needsRecovery` flag, we inform the caller that it is responsible
- // for manually setting the selection state on the rendered document to
- // ensure proper selection state maintenance.
-
- if (anchorIsTextNode) {
- anchorPoint = {
- key: nullthrows(findAncestorOffsetKey(anchorNode)),
- offset: anchorOffset
- };
- focusPoint = getPointForNonTextNode(root, focusNode, focusOffset);
- } else if (focusIsTextNode) {
- focusPoint = {
- key: nullthrows(findAncestorOffsetKey(focusNode)),
- offset: focusOffset
- };
- anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
- } else {
- anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
- focusPoint = getPointForNonTextNode(root, focusNode, focusOffset); // If the selection is collapsed on an empty block, don't force recovery.
- // This way, on arrow key selection changes, the browser can move the
- // cursor from a non-zero offset on one block, through empty blocks,
- // to a matching non-zero offset on other text blocks.
-
- if (anchorNode === focusNode && anchorOffset === focusOffset) {
- needsRecovery = !!anchorNode.firstChild && anchorNode.firstChild.nodeName !== 'BR';
- }
- }
-
- return {
- selectionState: getUpdatedSelectionState(editorState, anchorPoint.key, anchorPoint.offset, focusPoint.key, focusPoint.offset),
- needsRecovery: needsRecovery
- };
- }
- /**
- * Identify the first leaf descendant for the given node.
- */
-
-
- function getFirstLeaf(node) {
- while (node.firstChild && ( // data-blocks has no offset
- isElement(node.firstChild) && node.firstChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.firstChild))) {
- node = node.firstChild;
- }
-
- return node;
- }
- /**
- * Identify the last leaf descendant for the given node.
- */
-
-
- function getLastLeaf(node) {
- while (node.lastChild && ( // data-blocks has no offset
- isElement(node.lastChild) && node.lastChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.lastChild))) {
- node = node.lastChild;
- }
-
- return node;
- }
-
- function getPointForNonTextNode(editorRoot, startNode, childOffset) {
- var node = startNode;
- var offsetKey = findAncestorOffsetKey(node);
- !(offsetKey != null || editorRoot && (editorRoot === node || editorRoot.firstChild === node)) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unknown node in selection range.') : invariant(false) : void 0; // If the editorRoot is the selection, step downward into the content
- // wrapper.
-
- if (editorRoot === node) {
- node = node.firstChild;
- !isElement(node) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid DraftEditorContents node.') : invariant(false) : void 0;
- var castedNode = node; // assignment only added for flow :/
- // otherwise it throws in line 200 saying that node can be null or undefined
-
- node = castedNode;
- !(node.getAttribute('data-contents') === 'true') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid DraftEditorContents structure.') : invariant(false) : void 0;
-
- if (childOffset > 0) {
- childOffset = node.childNodes.length;
- }
- } // If the child offset is zero and we have an offset key, we're done.
- // If there's no offset key because the entire editor is selected,
- // find the leftmost ("first") leaf in the tree and use that as the offset
- // key.
-
-
- if (childOffset === 0) {
- var key = null;
-
- if (offsetKey != null) {
- key = offsetKey;
- } else {
- var firstLeaf = getFirstLeaf(node);
- key = nullthrows(getSelectionOffsetKeyForNode(firstLeaf));
- }
-
- return {
- key: key,
- offset: 0
- };
- }
-
- var nodeBeforeCursor = node.childNodes[childOffset - 1];
- var leafKey = null;
- var textLength = null;
-
- if (!getSelectionOffsetKeyForNode(nodeBeforeCursor)) {
- // Our target node may be a leaf or a text node, in which case we're
- // already where we want to be and can just use the child's length as
- // our offset.
- leafKey = nullthrows(offsetKey);
- textLength = getTextContentLength(nodeBeforeCursor);
- } else {
- // Otherwise, we'll look at the child to the left of the cursor and find
- // the last leaf node in its subtree.
- var lastLeaf = getLastLeaf(nodeBeforeCursor);
- leafKey = nullthrows(getSelectionOffsetKeyForNode(lastLeaf));
- textLength = getTextContentLength(lastLeaf);
- }
-
- return {
- key: leafKey,
- offset: textLength
- };
- }
- /**
- * Return the length of a node's textContent, regarding single newline
- * characters as zero-length. This allows us to avoid problems with identifying
- * the correct selection offset for empty blocks in IE, in which we
- * render newlines instead of break tags.
- */
-
-
- function getTextContentLength(node) {
- var textContent = node.textContent;
- return textContent === '\n' ? 0 : textContent.length;
- }
-
- module.exports = getDraftEditorSelectionWithNodes;
|