Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

194 строки
7.2 KiB

  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. *
  9. * @emails oncall+draft_js
  10. */
  11. 'use strict';
  12. var findAncestorOffsetKey = require("./findAncestorOffsetKey");
  13. var getSelectionOffsetKeyForNode = require("./getSelectionOffsetKeyForNode");
  14. var getUpdatedSelectionState = require("./getUpdatedSelectionState");
  15. var invariant = require("fbjs/lib/invariant");
  16. var isElement = require("./isElement");
  17. var nullthrows = require("fbjs/lib/nullthrows");
  18. /**
  19. * Convert the current selection range to an anchor/focus pair of offset keys
  20. * and values that can be interpreted by components.
  21. */
  22. function getDraftEditorSelectionWithNodes(editorState, root, anchorNode, anchorOffset, focusNode, focusOffset) {
  23. var anchorIsTextNode = anchorNode.nodeType === Node.TEXT_NODE;
  24. var focusIsTextNode = focusNode.nodeType === Node.TEXT_NODE; // If the selection range lies only on text nodes, the task is simple.
  25. // Find the nearest offset-aware elements and use the
  26. // offset values supplied by the selection range.
  27. if (anchorIsTextNode && focusIsTextNode) {
  28. return {
  29. selectionState: getUpdatedSelectionState(editorState, nullthrows(findAncestorOffsetKey(anchorNode)), anchorOffset, nullthrows(findAncestorOffsetKey(focusNode)), focusOffset),
  30. needsRecovery: false
  31. };
  32. }
  33. var anchorPoint = null;
  34. var focusPoint = null;
  35. var needsRecovery = true; // An element is selected. Convert this selection range into leaf offset
  36. // keys and offset values for consumption at the component level. This
  37. // is common in Firefox, where select-all and triple click behavior leads
  38. // to entire elements being selected.
  39. //
  40. // Note that we use the `needsRecovery` parameter in the callback here. This
  41. // is because when certain elements are selected, the behavior for subsequent
  42. // cursor movement (e.g. via arrow keys) is uncertain and may not match
  43. // expectations at the component level. For example, if an entire <div> is
  44. // selected and the user presses the right arrow, Firefox keeps the selection
  45. // on the <div>. If we allow subsequent keypresses to insert characters
  46. // natively, they will be inserted into a browser-created text node to the
  47. // right of that <div>. This is obviously undesirable.
  48. //
  49. // With the `needsRecovery` flag, we inform the caller that it is responsible
  50. // for manually setting the selection state on the rendered document to
  51. // ensure proper selection state maintenance.
  52. if (anchorIsTextNode) {
  53. anchorPoint = {
  54. key: nullthrows(findAncestorOffsetKey(anchorNode)),
  55. offset: anchorOffset
  56. };
  57. focusPoint = getPointForNonTextNode(root, focusNode, focusOffset);
  58. } else if (focusIsTextNode) {
  59. focusPoint = {
  60. key: nullthrows(findAncestorOffsetKey(focusNode)),
  61. offset: focusOffset
  62. };
  63. anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
  64. } else {
  65. anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
  66. focusPoint = getPointForNonTextNode(root, focusNode, focusOffset); // If the selection is collapsed on an empty block, don't force recovery.
  67. // This way, on arrow key selection changes, the browser can move the
  68. // cursor from a non-zero offset on one block, through empty blocks,
  69. // to a matching non-zero offset on other text blocks.
  70. if (anchorNode === focusNode && anchorOffset === focusOffset) {
  71. needsRecovery = !!anchorNode.firstChild && anchorNode.firstChild.nodeName !== 'BR';
  72. }
  73. }
  74. return {
  75. selectionState: getUpdatedSelectionState(editorState, anchorPoint.key, anchorPoint.offset, focusPoint.key, focusPoint.offset),
  76. needsRecovery: needsRecovery
  77. };
  78. }
  79. /**
  80. * Identify the first leaf descendant for the given node.
  81. */
  82. function getFirstLeaf(node) {
  83. while (node.firstChild && ( // data-blocks has no offset
  84. isElement(node.firstChild) && node.firstChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.firstChild))) {
  85. node = node.firstChild;
  86. }
  87. return node;
  88. }
  89. /**
  90. * Identify the last leaf descendant for the given node.
  91. */
  92. function getLastLeaf(node) {
  93. while (node.lastChild && ( // data-blocks has no offset
  94. isElement(node.lastChild) && node.lastChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.lastChild))) {
  95. node = node.lastChild;
  96. }
  97. return node;
  98. }
  99. function getPointForNonTextNode(editorRoot, startNode, childOffset) {
  100. var node = startNode;
  101. var offsetKey = findAncestorOffsetKey(node);
  102. !(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
  103. // wrapper.
  104. if (editorRoot === node) {
  105. node = node.firstChild;
  106. !isElement(node) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid DraftEditorContents node.') : invariant(false) : void 0;
  107. var castedNode = node; // assignment only added for flow :/
  108. // otherwise it throws in line 200 saying that node can be null or undefined
  109. node = castedNode;
  110. !(node.getAttribute('data-contents') === 'true') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid DraftEditorContents structure.') : invariant(false) : void 0;
  111. if (childOffset > 0) {
  112. childOffset = node.childNodes.length;
  113. }
  114. } // If the child offset is zero and we have an offset key, we're done.
  115. // If there's no offset key because the entire editor is selected,
  116. // find the leftmost ("first") leaf in the tree and use that as the offset
  117. // key.
  118. if (childOffset === 0) {
  119. var key = null;
  120. if (offsetKey != null) {
  121. key = offsetKey;
  122. } else {
  123. var firstLeaf = getFirstLeaf(node);
  124. key = nullthrows(getSelectionOffsetKeyForNode(firstLeaf));
  125. }
  126. return {
  127. key: key,
  128. offset: 0
  129. };
  130. }
  131. var nodeBeforeCursor = node.childNodes[childOffset - 1];
  132. var leafKey = null;
  133. var textLength = null;
  134. if (!getSelectionOffsetKeyForNode(nodeBeforeCursor)) {
  135. // Our target node may be a leaf or a text node, in which case we're
  136. // already where we want to be and can just use the child's length as
  137. // our offset.
  138. leafKey = nullthrows(offsetKey);
  139. textLength = getTextContentLength(nodeBeforeCursor);
  140. } else {
  141. // Otherwise, we'll look at the child to the left of the cursor and find
  142. // the last leaf node in its subtree.
  143. var lastLeaf = getLastLeaf(nodeBeforeCursor);
  144. leafKey = nullthrows(getSelectionOffsetKeyForNode(lastLeaf));
  145. textLength = getTextContentLength(lastLeaf);
  146. }
  147. return {
  148. key: leafKey,
  149. offset: textLength
  150. };
  151. }
  152. /**
  153. * Return the length of a node's textContent, regarding single newline
  154. * characters as zero-length. This allows us to avoid problems with identifying
  155. * the correct selection offset for empty blocks in IE, in which we
  156. * render newlines instead of break tags.
  157. */
  158. function getTextContentLength(node) {
  159. var textContent = node.textContent;
  160. return textContent === '\n' ? 0 : textContent.length;
  161. }
  162. module.exports = getDraftEditorSelectionWithNodes;