2836 lines
		
	
	
		
			87 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
		
		
			
		
	
	
			2836 lines
		
	
	
		
			87 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * 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 type {LexicalEditor} from './LexicalEditor';
							 | 
						||
| 
								 | 
							
								import type {EditorState} from './LexicalEditorState';
							 | 
						||
| 
								 | 
							
								import type {NodeKey} from './LexicalNode';
							 | 
						||
| 
								 | 
							
								import type {ElementNode} from './nodes/LexicalElementNode';
							 | 
						||
| 
								 | 
							
								import type {TextFormatType} from './nodes/LexicalTextNode';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import invariant from 'lexical/shared/invariant';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  $createLineBreakNode,
							 | 
						||
| 
								 | 
							
								  $createParagraphNode,
							 | 
						||
| 
								 | 
							
								  $createTextNode,
							 | 
						||
| 
								 | 
							
								  $isDecoratorNode,
							 | 
						||
| 
								 | 
							
								  $isElementNode,
							 | 
						||
| 
								 | 
							
								  $isLineBreakNode,
							 | 
						||
| 
								 | 
							
								  $isRootNode,
							 | 
						||
| 
								 | 
							
								  $isTextNode,
							 | 
						||
| 
								 | 
							
								  $setSelection,
							 | 
						||
| 
								 | 
							
								  SELECTION_CHANGE_COMMAND,
							 | 
						||
| 
								 | 
							
								  TextNode,
							 | 
						||
| 
								 | 
							
								} from '.';
							 | 
						||
| 
								 | 
							
								import {DOM_ELEMENT_TYPE, TEXT_TYPE_TO_FORMAT} from './LexicalConstants';
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  markCollapsedSelectionFormat,
							 | 
						||
| 
								 | 
							
								  markSelectionChangeFromDOMUpdate,
							 | 
						||
| 
								 | 
							
								} from './LexicalEvents';
							 | 
						||
| 
								 | 
							
								import {getIsProcessingMutations} from './LexicalMutations';
							 | 
						||
| 
								 | 
							
								import {insertRangeAfter, LexicalNode} from './LexicalNode';
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  getActiveEditor,
							 | 
						||
| 
								 | 
							
								  getActiveEditorState,
							 | 
						||
| 
								 | 
							
								  isCurrentlyReadOnlyMode,
							 | 
						||
| 
								 | 
							
								} from './LexicalUpdates';
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  $getAdjacentNode,
							 | 
						||
| 
								 | 
							
								  $getAncestor,
							 | 
						||
| 
								 | 
							
								  $getCompositionKey,
							 | 
						||
| 
								 | 
							
								  $getNearestRootOrShadowRoot,
							 | 
						||
| 
								 | 
							
								  $getNodeByKey,
							 | 
						||
| 
								 | 
							
								  $getNodeFromDOM,
							 | 
						||
| 
								 | 
							
								  $getRoot,
							 | 
						||
| 
								 | 
							
								  $hasAncestor,
							 | 
						||
| 
								 | 
							
								  $isTokenOrSegmented,
							 | 
						||
| 
								 | 
							
								  $setCompositionKey,
							 | 
						||
| 
								 | 
							
								  doesContainGrapheme,
							 | 
						||
| 
								 | 
							
								  getDOMSelection,
							 | 
						||
| 
								 | 
							
								  getDOMTextNode,
							 | 
						||
| 
								 | 
							
								  getElementByKeyOrThrow,
							 | 
						||
| 
								 | 
							
								  getTextNodeOffset,
							 | 
						||
| 
								 | 
							
								  INTERNAL_$isBlock,
							 | 
						||
| 
								 | 
							
								  isSelectionCapturedInDecoratorInput,
							 | 
						||
| 
								 | 
							
								  isSelectionWithinEditor,
							 | 
						||
| 
								 | 
							
								  removeDOMBlockCursorElement,
							 | 
						||
| 
								 | 
							
								  scrollIntoViewIfNeeded,
							 | 
						||
| 
								 | 
							
								  toggleTextFormatType,
							 | 
						||
| 
								 | 
							
								} from './LexicalUtils';
							 | 
						||
| 
								 | 
							
								import {$createTabNode, $isTabNode} from './nodes/LexicalTabNode';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export type TextPointType = {
							 | 
						||
| 
								 | 
							
								  _selection: BaseSelection;
							 | 
						||
| 
								 | 
							
								  getNode: () => TextNode;
							 | 
						||
| 
								 | 
							
								  is: (point: PointType) => boolean;
							 | 
						||
| 
								 | 
							
								  isBefore: (point: PointType) => boolean;
							 | 
						||
| 
								 | 
							
								  key: NodeKey;
							 | 
						||
| 
								 | 
							
								  offset: number;
							 | 
						||
| 
								 | 
							
								  set: (key: NodeKey, offset: number, type: 'text' | 'element') => void;
							 | 
						||
| 
								 | 
							
								  type: 'text';
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export type ElementPointType = {
							 | 
						||
| 
								 | 
							
								  _selection: BaseSelection;
							 | 
						||
| 
								 | 
							
								  getNode: () => ElementNode;
							 | 
						||
| 
								 | 
							
								  is: (point: PointType) => boolean;
							 | 
						||
| 
								 | 
							
								  isBefore: (point: PointType) => boolean;
							 | 
						||
| 
								 | 
							
								  key: NodeKey;
							 | 
						||
| 
								 | 
							
								  offset: number;
							 | 
						||
| 
								 | 
							
								  set: (key: NodeKey, offset: number, type: 'text' | 'element') => void;
							 | 
						||
| 
								 | 
							
								  type: 'element';
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export type PointType = TextPointType | ElementPointType;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export class Point {
							 | 
						||
| 
								 | 
							
								  key: NodeKey;
							 | 
						||
| 
								 | 
							
								  offset: number;
							 | 
						||
| 
								 | 
							
								  type: 'text' | 'element';
							 | 
						||
| 
								 | 
							
								  _selection: BaseSelection | null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  constructor(key: NodeKey, offset: number, type: 'text' | 'element') {
							 | 
						||
| 
								 | 
							
								    this._selection = null;
							 | 
						||
| 
								 | 
							
								    this.key = key;
							 | 
						||
| 
								 | 
							
								    this.offset = offset;
							 | 
						||
| 
								 | 
							
								    this.type = type;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  is(point: PointType): boolean {
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      this.key === point.key &&
							 | 
						||
| 
								 | 
							
								      this.offset === point.offset &&
							 | 
						||
| 
								 | 
							
								      this.type === point.type
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  isBefore(b: PointType): boolean {
							 | 
						||
| 
								 | 
							
								    let aNode = this.getNode();
							 | 
						||
| 
								 | 
							
								    let bNode = b.getNode();
							 | 
						||
| 
								 | 
							
								    const aOffset = this.offset;
							 | 
						||
| 
								 | 
							
								    const bOffset = b.offset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ($isElementNode(aNode)) {
							 | 
						||
| 
								 | 
							
								      const aNodeDescendant = aNode.getDescendantByIndex<ElementNode>(aOffset);
							 | 
						||
| 
								 | 
							
								      aNode = aNodeDescendant != null ? aNodeDescendant : aNode;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if ($isElementNode(bNode)) {
							 | 
						||
| 
								 | 
							
								      const bNodeDescendant = bNode.getDescendantByIndex<ElementNode>(bOffset);
							 | 
						||
| 
								 | 
							
								      bNode = bNodeDescendant != null ? bNodeDescendant : bNode;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (aNode === bNode) {
							 | 
						||
| 
								 | 
							
								      return aOffset < bOffset;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return aNode.isBefore(bNode);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getNode(): LexicalNode {
							 | 
						||
| 
								 | 
							
								    const key = this.key;
							 | 
						||
| 
								 | 
							
								    const node = $getNodeByKey(key);
							 | 
						||
| 
								 | 
							
								    if (node === null) {
							 | 
						||
| 
								 | 
							
								      invariant(false, 'Point.getNode: node not found');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return node;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  set(key: NodeKey, offset: number, type: 'text' | 'element'): void {
							 | 
						||
| 
								 | 
							
								    const selection = this._selection;
							 | 
						||
| 
								 | 
							
								    const oldKey = this.key;
							 | 
						||
| 
								 | 
							
								    this.key = key;
							 | 
						||
| 
								 | 
							
								    this.offset = offset;
							 | 
						||
| 
								 | 
							
								    this.type = type;
							 | 
						||
| 
								 | 
							
								    if (!isCurrentlyReadOnlyMode()) {
							 | 
						||
| 
								 | 
							
								      if ($getCompositionKey() === oldKey) {
							 | 
						||
| 
								 | 
							
								        $setCompositionKey(key);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (selection !== null) {
							 | 
						||
| 
								 | 
							
								        selection.setCachedNodes(null);
							 | 
						||
| 
								 | 
							
								        selection.dirty = true;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $createPoint(
							 | 
						||
| 
								 | 
							
								  key: NodeKey,
							 | 
						||
| 
								 | 
							
								  offset: number,
							 | 
						||
| 
								 | 
							
								  type: 'text' | 'element',
							 | 
						||
| 
								 | 
							
								): PointType {
							 | 
						||
| 
								 | 
							
								  // @ts-expect-error: intentionally cast as we use a class for perf reasons
							 | 
						||
| 
								 | 
							
								  return new Point(key, offset, type);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function selectPointOnNode(point: PointType, node: LexicalNode): void {
							 | 
						||
| 
								 | 
							
								  let key = node.__key;
							 | 
						||
| 
								 | 
							
								  let offset = point.offset;
							 | 
						||
| 
								 | 
							
								  let type: 'element' | 'text' = 'element';
							 | 
						||
| 
								 | 
							
								  if ($isTextNode(node)) {
							 | 
						||
| 
								 | 
							
								    type = 'text';
							 | 
						||
| 
								 | 
							
								    const textContentLength = node.getTextContentSize();
							 | 
						||
| 
								 | 
							
								    if (offset > textContentLength) {
							 | 
						||
| 
								 | 
							
								      offset = textContentLength;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else if (!$isElementNode(node)) {
							 | 
						||
| 
								 | 
							
								    const nextSibling = node.getNextSibling();
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(nextSibling)) {
							 | 
						||
| 
								 | 
							
								      key = nextSibling.__key;
							 | 
						||
| 
								 | 
							
								      offset = 0;
							 | 
						||
| 
								 | 
							
								      type = 'text';
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const parentNode = node.getParent();
							 | 
						||
| 
								 | 
							
								      if (parentNode) {
							 | 
						||
| 
								 | 
							
								        key = parentNode.__key;
							 | 
						||
| 
								 | 
							
								        offset = node.getIndexWithinParent() + 1;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  point.set(key, offset, type);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $moveSelectionPointToEnd(
							 | 
						||
| 
								 | 
							
								  point: PointType,
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  if ($isElementNode(node)) {
							 | 
						||
| 
								 | 
							
								    const lastNode = node.getLastDescendant();
							 | 
						||
| 
								 | 
							
								    if ($isElementNode(lastNode) || $isTextNode(lastNode)) {
							 | 
						||
| 
								 | 
							
								      selectPointOnNode(point, lastNode);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      selectPointOnNode(point, node);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    selectPointOnNode(point, node);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $transferStartingElementPointToTextPoint(
							 | 
						||
| 
								 | 
							
								  start: ElementPointType,
							 | 
						||
| 
								 | 
							
								  end: PointType,
							 | 
						||
| 
								 | 
							
								  format: number,
							 | 
						||
| 
								 | 
							
								  style: string,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const element = start.getNode();
							 | 
						||
| 
								 | 
							
								  const placementNode = element.getChildAtIndex(start.offset);
							 | 
						||
| 
								 | 
							
								  const textNode = $createTextNode();
							 | 
						||
| 
								 | 
							
								  const target = $isRootNode(element)
							 | 
						||
| 
								 | 
							
								    ? $createParagraphNode().append(textNode)
							 | 
						||
| 
								 | 
							
								    : textNode;
							 | 
						||
| 
								 | 
							
								  textNode.setFormat(format);
							 | 
						||
| 
								 | 
							
								  textNode.setStyle(style);
							 | 
						||
| 
								 | 
							
								  if (placementNode === null) {
							 | 
						||
| 
								 | 
							
								    element.append(target);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    placementNode.insertBefore(target);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // Transfer the element point to a text point.
							 | 
						||
| 
								 | 
							
								  if (start.is(end)) {
							 | 
						||
| 
								 | 
							
								    end.set(textNode.__key, 0, 'text');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  start.set(textNode.__key, 0, 'text');
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $setPointValues(
							 | 
						||
| 
								 | 
							
								  point: PointType,
							 | 
						||
| 
								 | 
							
								  key: NodeKey,
							 | 
						||
| 
								 | 
							
								  offset: number,
							 | 
						||
| 
								 | 
							
								  type: 'text' | 'element',
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  point.key = key;
							 | 
						||
| 
								 | 
							
								  point.offset = offset;
							 | 
						||
| 
								 | 
							
								  point.type = type;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export interface BaseSelection {
							 | 
						||
| 
								 | 
							
								  _cachedNodes: Array<LexicalNode> | null;
							 | 
						||
| 
								 | 
							
								  dirty: boolean;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  clone(): BaseSelection;
							 | 
						||
| 
								 | 
							
								  extract(): Array<LexicalNode>;
							 | 
						||
| 
								 | 
							
								  getNodes(): Array<LexicalNode>;
							 | 
						||
| 
								 | 
							
								  getTextContent(): string;
							 | 
						||
| 
								 | 
							
								  insertText(text: string): void;
							 | 
						||
| 
								 | 
							
								  insertRawText(text: string): void;
							 | 
						||
| 
								 | 
							
								  is(selection: null | BaseSelection): boolean;
							 | 
						||
| 
								 | 
							
								  insertNodes(nodes: Array<LexicalNode>): void;
							 | 
						||
| 
								 | 
							
								  getStartEndPoints(): null | [PointType, PointType];
							 | 
						||
| 
								 | 
							
								  isCollapsed(): boolean;
							 | 
						||
| 
								 | 
							
								  isBackward(): boolean;
							 | 
						||
| 
								 | 
							
								  getCachedNodes(): LexicalNode[] | null;
							 | 
						||
| 
								 | 
							
								  setCachedNodes(nodes: LexicalNode[] | null): void;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export class NodeSelection implements BaseSelection {
							 | 
						||
| 
								 | 
							
								  _nodes: Set<NodeKey>;
							 | 
						||
| 
								 | 
							
								  _cachedNodes: Array<LexicalNode> | null;
							 | 
						||
| 
								 | 
							
								  dirty: boolean;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  constructor(objects: Set<NodeKey>) {
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								    this._nodes = objects;
							 | 
						||
| 
								 | 
							
								    this.dirty = false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getCachedNodes(): LexicalNode[] | null {
							 | 
						||
| 
								 | 
							
								    return this._cachedNodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  setCachedNodes(nodes: LexicalNode[] | null): void {
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = nodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  is(selection: null | BaseSelection): boolean {
							 | 
						||
| 
								 | 
							
								    if (!$isNodeSelection(selection)) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const a: Set<NodeKey> = this._nodes;
							 | 
						||
| 
								 | 
							
								    const b: Set<NodeKey> = selection._nodes;
							 | 
						||
| 
								 | 
							
								    return a.size === b.size && Array.from(a).every((key) => b.has(key));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  isCollapsed(): boolean {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  isBackward(): boolean {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getStartEndPoints(): null {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  add(key: NodeKey): void {
							 | 
						||
| 
								 | 
							
								    this.dirty = true;
							 | 
						||
| 
								 | 
							
								    this._nodes.add(key);
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  delete(key: NodeKey): void {
							 | 
						||
| 
								 | 
							
								    this.dirty = true;
							 | 
						||
| 
								 | 
							
								    this._nodes.delete(key);
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  clear(): void {
							 | 
						||
| 
								 | 
							
								    this.dirty = true;
							 | 
						||
| 
								 | 
							
								    this._nodes.clear();
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  has(key: NodeKey): boolean {
							 | 
						||
| 
								 | 
							
								    return this._nodes.has(key);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  clone(): NodeSelection {
							 | 
						||
| 
								 | 
							
								    return new NodeSelection(new Set(this._nodes));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  extract(): Array<LexicalNode> {
							 | 
						||
| 
								 | 
							
								    return this.getNodes();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  insertRawText(text: string): void {
							 | 
						||
| 
								 | 
							
								    // Do nothing?
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  insertText(): void {
							 | 
						||
| 
								 | 
							
								    // Do nothing?
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  insertNodes(nodes: Array<LexicalNode>) {
							 | 
						||
| 
								 | 
							
								    const selectedNodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								    const selectedNodesLength = selectedNodes.length;
							 | 
						||
| 
								 | 
							
								    const lastSelectedNode = selectedNodes[selectedNodesLength - 1];
							 | 
						||
| 
								 | 
							
								    let selectionAtEnd: RangeSelection;
							 | 
						||
| 
								 | 
							
								    // Insert nodes
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(lastSelectedNode)) {
							 | 
						||
| 
								 | 
							
								      selectionAtEnd = lastSelectedNode.select();
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const index = lastSelectedNode.getIndexWithinParent() + 1;
							 | 
						||
| 
								 | 
							
								      selectionAtEnd = lastSelectedNode.getParentOrThrow().select(index, index);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    selectionAtEnd.insertNodes(nodes);
							 | 
						||
| 
								 | 
							
								    // Remove selected nodes
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < selectedNodesLength; i++) {
							 | 
						||
| 
								 | 
							
								      selectedNodes[i].remove();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getNodes(): Array<LexicalNode> {
							 | 
						||
| 
								 | 
							
								    const cachedNodes = this._cachedNodes;
							 | 
						||
| 
								 | 
							
								    if (cachedNodes !== null) {
							 | 
						||
| 
								 | 
							
								      return cachedNodes;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const objects = this._nodes;
							 | 
						||
| 
								 | 
							
								    const nodes = [];
							 | 
						||
| 
								 | 
							
								    for (const object of objects) {
							 | 
						||
| 
								 | 
							
								      const node = $getNodeByKey(object);
							 | 
						||
| 
								 | 
							
								      if (node !== null) {
							 | 
						||
| 
								 | 
							
								        nodes.push(node);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (!isCurrentlyReadOnlyMode()) {
							 | 
						||
| 
								 | 
							
								      this._cachedNodes = nodes;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return nodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getTextContent(): string {
							 | 
						||
| 
								 | 
							
								    const nodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								    let textContent = '';
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < nodes.length; i++) {
							 | 
						||
| 
								 | 
							
								      textContent += nodes[i].getTextContent();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return textContent;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isRangeSelection(x: unknown): x is RangeSelection {
							 | 
						||
| 
								 | 
							
								  return x instanceof RangeSelection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export class RangeSelection implements BaseSelection {
							 | 
						||
| 
								 | 
							
								  format: number;
							 | 
						||
| 
								 | 
							
								  style: string;
							 | 
						||
| 
								 | 
							
								  anchor: PointType;
							 | 
						||
| 
								 | 
							
								  focus: PointType;
							 | 
						||
| 
								 | 
							
								  _cachedNodes: Array<LexicalNode> | null;
							 | 
						||
| 
								 | 
							
								  dirty: boolean;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  constructor(
							 | 
						||
| 
								 | 
							
								    anchor: PointType,
							 | 
						||
| 
								 | 
							
								    focus: PointType,
							 | 
						||
| 
								 | 
							
								    format: number,
							 | 
						||
| 
								 | 
							
								    style: string,
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    this.anchor = anchor;
							 | 
						||
| 
								 | 
							
								    this.focus = focus;
							 | 
						||
| 
								 | 
							
								    anchor._selection = this;
							 | 
						||
| 
								 | 
							
								    focus._selection = this;
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								    this.format = format;
							 | 
						||
| 
								 | 
							
								    this.style = style;
							 | 
						||
| 
								 | 
							
								    this.dirty = false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getCachedNodes(): LexicalNode[] | null {
							 | 
						||
| 
								 | 
							
								    return this._cachedNodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  setCachedNodes(nodes: LexicalNode[] | null): void {
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = nodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Used to check if the provided selections is equal to this one by value,
							 | 
						||
| 
								 | 
							
								   * inluding anchor, focus, format, and style properties.
							 | 
						||
| 
								 | 
							
								   * @param selection - the Selection to compare this one to.
							 | 
						||
| 
								 | 
							
								   * @returns true if the Selections are equal, false otherwise.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  is(selection: null | BaseSelection): boolean {
							 | 
						||
| 
								 | 
							
								    if (!$isRangeSelection(selection)) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      this.anchor.is(selection.anchor) &&
							 | 
						||
| 
								 | 
							
								      this.focus.is(selection.focus) &&
							 | 
						||
| 
								 | 
							
								      this.format === selection.format &&
							 | 
						||
| 
								 | 
							
								      this.style === selection.style
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Returns whether the Selection is "collapsed", meaning the anchor and focus are
							 | 
						||
| 
								 | 
							
								   * the same node and have the same offset.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @returns true if the Selection is collapsed, false otherwise.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  isCollapsed(): boolean {
							 | 
						||
| 
								 | 
							
								    return this.anchor.is(this.focus);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Gets all the nodes in the Selection. Uses caching to make it generally suitable
							 | 
						||
| 
								 | 
							
								   * for use in hot paths.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @returns an Array containing all the nodes in the Selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  getNodes(): Array<LexicalNode> {
							 | 
						||
| 
								 | 
							
								    const cachedNodes = this._cachedNodes;
							 | 
						||
| 
								 | 
							
								    if (cachedNodes !== null) {
							 | 
						||
| 
								 | 
							
								      return cachedNodes;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    const isBefore = anchor.isBefore(focus);
							 | 
						||
| 
								 | 
							
								    const firstPoint = isBefore ? anchor : focus;
							 | 
						||
| 
								 | 
							
								    const lastPoint = isBefore ? focus : anchor;
							 | 
						||
| 
								 | 
							
								    let firstNode = firstPoint.getNode();
							 | 
						||
| 
								 | 
							
								    let lastNode = lastPoint.getNode();
							 | 
						||
| 
								 | 
							
								    const startOffset = firstPoint.offset;
							 | 
						||
| 
								 | 
							
								    const endOffset = lastPoint.offset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ($isElementNode(firstNode)) {
							 | 
						||
| 
								 | 
							
								      const firstNodeDescendant =
							 | 
						||
| 
								 | 
							
								        firstNode.getDescendantByIndex<ElementNode>(startOffset);
							 | 
						||
| 
								 | 
							
								      firstNode = firstNodeDescendant != null ? firstNodeDescendant : firstNode;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if ($isElementNode(lastNode)) {
							 | 
						||
| 
								 | 
							
								      let lastNodeDescendant =
							 | 
						||
| 
								 | 
							
								        lastNode.getDescendantByIndex<ElementNode>(endOffset);
							 | 
						||
| 
								 | 
							
								      // We don't want to over-select, as node selection infers the child before
							 | 
						||
| 
								 | 
							
								      // the last descendant, not including that descendant.
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        lastNodeDescendant !== null &&
							 | 
						||
| 
								 | 
							
								        lastNodeDescendant !== firstNode &&
							 | 
						||
| 
								 | 
							
								        lastNode.getChildAtIndex(endOffset) === lastNodeDescendant
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        lastNodeDescendant = lastNodeDescendant.getPreviousSibling();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      lastNode = lastNodeDescendant != null ? lastNodeDescendant : lastNode;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let nodes: Array<LexicalNode>;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (firstNode.is(lastNode)) {
							 | 
						||
| 
								 | 
							
								      if ($isElementNode(firstNode) && firstNode.getChildrenSize() > 0) {
							 | 
						||
| 
								 | 
							
								        nodes = [];
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        nodes = [firstNode];
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      nodes = firstNode.getNodesBetween(lastNode);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (!isCurrentlyReadOnlyMode()) {
							 | 
						||
| 
								 | 
							
								      this._cachedNodes = nodes;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return nodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Sets this Selection to be of type "text" at the provided anchor and focus values.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param anchorNode - the anchor node to set on the Selection
							 | 
						||
| 
								 | 
							
								   * @param anchorOffset - the offset to set on the Selection
							 | 
						||
| 
								 | 
							
								   * @param focusNode - the focus node to set on the Selection
							 | 
						||
| 
								 | 
							
								   * @param focusOffset - the focus offset to set on the Selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  setTextNodeRange(
							 | 
						||
| 
								 | 
							
								    anchorNode: TextNode,
							 | 
						||
| 
								 | 
							
								    anchorOffset: number,
							 | 
						||
| 
								 | 
							
								    focusNode: TextNode,
							 | 
						||
| 
								 | 
							
								    focusOffset: number,
							 | 
						||
| 
								 | 
							
								  ): void {
							 | 
						||
| 
								 | 
							
								    $setPointValues(this.anchor, anchorNode.__key, anchorOffset, 'text');
							 | 
						||
| 
								 | 
							
								    $setPointValues(this.focus, focusNode.__key, focusOffset, 'text');
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								    this.dirty = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Gets the (plain) text content of all the nodes in the selection.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @returns a string representing the text content of all the nodes in the Selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  getTextContent(): string {
							 | 
						||
| 
								 | 
							
								    const nodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								    if (nodes.length === 0) {
							 | 
						||
| 
								 | 
							
								      return '';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const firstNode = nodes[0];
							 | 
						||
| 
								 | 
							
								    const lastNode = nodes[nodes.length - 1];
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    const isBefore = anchor.isBefore(focus);
							 | 
						||
| 
								 | 
							
								    const [anchorOffset, focusOffset] = $getCharacterOffsets(this);
							 | 
						||
| 
								 | 
							
								    let textContent = '';
							 | 
						||
| 
								 | 
							
								    let prevWasElement = true;
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < nodes.length; i++) {
							 | 
						||
| 
								 | 
							
								      const node = nodes[i];
							 | 
						||
| 
								 | 
							
								      if ($isElementNode(node) && !node.isInline()) {
							 | 
						||
| 
								 | 
							
								        if (!prevWasElement) {
							 | 
						||
| 
								 | 
							
								          textContent += '\n';
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (node.isEmpty()) {
							 | 
						||
| 
								 | 
							
								          prevWasElement = false;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          prevWasElement = true;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        prevWasElement = false;
							 | 
						||
| 
								 | 
							
								        if ($isTextNode(node)) {
							 | 
						||
| 
								 | 
							
								          let text = node.getTextContent();
							 | 
						||
| 
								 | 
							
								          if (node === firstNode) {
							 | 
						||
| 
								 | 
							
								            if (node === lastNode) {
							 | 
						||
| 
								 | 
							
								              if (
							 | 
						||
| 
								 | 
							
								                anchor.type !== 'element' ||
							 | 
						||
| 
								 | 
							
								                focus.type !== 'element' ||
							 | 
						||
| 
								 | 
							
								                focus.offset === anchor.offset
							 | 
						||
| 
								 | 
							
								              ) {
							 | 
						||
| 
								 | 
							
								                text =
							 | 
						||
| 
								 | 
							
								                  anchorOffset < focusOffset
							 | 
						||
| 
								 | 
							
								                    ? text.slice(anchorOffset, focusOffset)
							 | 
						||
| 
								 | 
							
								                    : text.slice(focusOffset, anchorOffset);
							 | 
						||
| 
								 | 
							
								              }
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								              text = isBefore
							 | 
						||
| 
								 | 
							
								                ? text.slice(anchorOffset)
							 | 
						||
| 
								 | 
							
								                : text.slice(focusOffset);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          } else if (node === lastNode) {
							 | 
						||
| 
								 | 
							
								            text = isBefore
							 | 
						||
| 
								 | 
							
								              ? text.slice(0, focusOffset)
							 | 
						||
| 
								 | 
							
								              : text.slice(0, anchorOffset);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          textContent += text;
							 | 
						||
| 
								 | 
							
								        } else if (
							 | 
						||
| 
								 | 
							
								          ($isDecoratorNode(node) || $isLineBreakNode(node)) &&
							 | 
						||
| 
								 | 
							
								          (node !== lastNode || !this.isCollapsed())
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          textContent += node.getTextContent();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return textContent;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Attempts to map a DOM selection range onto this Lexical Selection,
							 | 
						||
| 
								 | 
							
								   * setting the anchor, focus, and type accordingly
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param range a DOM Selection range conforming to the StaticRange interface.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  applyDOMRange(range: StaticRange): void {
							 | 
						||
| 
								 | 
							
								    const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								    const currentEditorState = editor.getEditorState();
							 | 
						||
| 
								 | 
							
								    const lastSelection = currentEditorState._selection;
							 | 
						||
| 
								 | 
							
								    const resolvedSelectionPoints = $internalResolveSelectionPoints(
							 | 
						||
| 
								 | 
							
								      range.startContainer,
							 | 
						||
| 
								 | 
							
								      range.startOffset,
							 | 
						||
| 
								 | 
							
								      range.endContainer,
							 | 
						||
| 
								 | 
							
								      range.endOffset,
							 | 
						||
| 
								 | 
							
								      editor,
							 | 
						||
| 
								 | 
							
								      lastSelection,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    if (resolvedSelectionPoints === null) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const [anchorPoint, focusPoint] = resolvedSelectionPoints;
							 | 
						||
| 
								 | 
							
								    $setPointValues(
							 | 
						||
| 
								 | 
							
								      this.anchor,
							 | 
						||
| 
								 | 
							
								      anchorPoint.key,
							 | 
						||
| 
								 | 
							
								      anchorPoint.offset,
							 | 
						||
| 
								 | 
							
								      anchorPoint.type,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    $setPointValues(
							 | 
						||
| 
								 | 
							
								      this.focus,
							 | 
						||
| 
								 | 
							
								      focusPoint.key,
							 | 
						||
| 
								 | 
							
								      focusPoint.offset,
							 | 
						||
| 
								 | 
							
								      focusPoint.type,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    this._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Creates a new RangeSelection, copying over all the property values from this one.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @returns a new RangeSelection with the same property values as this one.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  clone(): RangeSelection {
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    const selection = new RangeSelection(
							 | 
						||
| 
								 | 
							
								      $createPoint(anchor.key, anchor.offset, anchor.type),
							 | 
						||
| 
								 | 
							
								      $createPoint(focus.key, focus.offset, focus.type),
							 | 
						||
| 
								 | 
							
								      this.format,
							 | 
						||
| 
								 | 
							
								      this.style,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    return selection;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Toggles the provided format on all the TextNodes in the Selection.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param format a string TextFormatType to toggle on the TextNodes in the selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  toggleFormat(format: TextFormatType): void {
							 | 
						||
| 
								 | 
							
								    this.format = toggleTextFormatType(this.format, format, null);
							 | 
						||
| 
								 | 
							
								    this.dirty = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Sets the value of the style property on the Selection
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param style - the style to set at the value of the style property.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  setStyle(style: string): void {
							 | 
						||
| 
								 | 
							
								    this.style = style;
							 | 
						||
| 
								 | 
							
								    this.dirty = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Returns whether the provided TextFormatType is present on the Selection. This will be true if any node in the Selection
							 | 
						||
| 
								 | 
							
								   * has the specified format.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param type the TextFormatType to check for.
							 | 
						||
| 
								 | 
							
								   * @returns true if the provided format is currently toggled on on the Selection, false otherwise.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  hasFormat(type: TextFormatType): boolean {
							 | 
						||
| 
								 | 
							
								    const formatFlag = TEXT_TYPE_TO_FORMAT[type];
							 | 
						||
| 
								 | 
							
								    return (this.format & formatFlag) !== 0;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Attempts to insert the provided text into the EditorState at the current Selection.
							 | 
						||
| 
								 | 
							
								   * converts tabs, newlines, and carriage returns into LexicalNodes.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param text the text to insert into the Selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  insertRawText(text: string): void {
							 | 
						||
| 
								 | 
							
								    const parts = text.split(/(\r?\n|\t)/);
							 | 
						||
| 
								 | 
							
								    const nodes = [];
							 | 
						||
| 
								 | 
							
								    const length = parts.length;
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < length; i++) {
							 | 
						||
| 
								 | 
							
								      const part = parts[i];
							 | 
						||
| 
								 | 
							
								      if (part === '\n' || part === '\r\n') {
							 | 
						||
| 
								 | 
							
								        nodes.push($createLineBreakNode());
							 | 
						||
| 
								 | 
							
								      } else if (part === '\t') {
							 | 
						||
| 
								 | 
							
								        nodes.push($createTabNode());
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        nodes.push($createTextNode(part));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.insertNodes(nodes);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Attempts to insert the provided text into the EditorState at the current Selection as a new
							 | 
						||
| 
								 | 
							
								   * Lexical TextNode, according to a series of insertion heuristics based on the selection type and position.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param text the text to insert into the Selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  insertText(text: string): void {
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    const format = this.format;
							 | 
						||
| 
								 | 
							
								    const style = this.style;
							 | 
						||
| 
								 | 
							
								    let firstPoint = anchor;
							 | 
						||
| 
								 | 
							
								    let endPoint = focus;
							 | 
						||
| 
								 | 
							
								    if (!this.isCollapsed() && focus.isBefore(anchor)) {
							 | 
						||
| 
								 | 
							
								      firstPoint = focus;
							 | 
						||
| 
								 | 
							
								      endPoint = anchor;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (firstPoint.type === 'element') {
							 | 
						||
| 
								 | 
							
								      $transferStartingElementPointToTextPoint(
							 | 
						||
| 
								 | 
							
								        firstPoint,
							 | 
						||
| 
								 | 
							
								        endPoint,
							 | 
						||
| 
								 | 
							
								        format,
							 | 
						||
| 
								 | 
							
								        style,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const startOffset = firstPoint.offset;
							 | 
						||
| 
								 | 
							
								    let endOffset = endPoint.offset;
							 | 
						||
| 
								 | 
							
								    const selectedNodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								    const selectedNodesLength = selectedNodes.length;
							 | 
						||
| 
								 | 
							
								    let firstNode: TextNode = selectedNodes[0] as TextNode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!$isTextNode(firstNode)) {
							 | 
						||
| 
								 | 
							
								      invariant(false, 'insertText: first node is not a text node');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const firstNodeText = firstNode.getTextContent();
							 | 
						||
| 
								 | 
							
								    const firstNodeTextLength = firstNodeText.length;
							 | 
						||
| 
								 | 
							
								    const firstNodeParent = firstNode.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								    const lastIndex = selectedNodesLength - 1;
							 | 
						||
| 
								 | 
							
								    let lastNode = selectedNodes[lastIndex];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (selectedNodesLength === 1 && endPoint.type === 'element') {
							 | 
						||
| 
								 | 
							
								      endOffset = firstNodeTextLength;
							 | 
						||
| 
								 | 
							
								      endPoint.set(firstPoint.key, endOffset, 'text');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      this.isCollapsed() &&
							 | 
						||
| 
								 | 
							
								      startOffset === firstNodeTextLength &&
							 | 
						||
| 
								 | 
							
								      (firstNode.isSegmented() ||
							 | 
						||
| 
								 | 
							
								        firstNode.isToken() ||
							 | 
						||
| 
								 | 
							
								        !firstNode.canInsertTextAfter() ||
							 | 
						||
| 
								 | 
							
								        (!firstNodeParent.canInsertTextAfter() &&
							 | 
						||
| 
								 | 
							
								          firstNode.getNextSibling() === null))
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      let nextSibling = firstNode.getNextSibling<TextNode>();
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        !$isTextNode(nextSibling) ||
							 | 
						||
| 
								 | 
							
								        !nextSibling.canInsertTextBefore() ||
							 | 
						||
| 
								 | 
							
								        $isTokenOrSegmented(nextSibling)
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        nextSibling = $createTextNode();
							 | 
						||
| 
								 | 
							
								        nextSibling.setFormat(format);
							 | 
						||
| 
								 | 
							
								        nextSibling.setStyle(style);
							 | 
						||
| 
								 | 
							
								        if (!firstNodeParent.canInsertTextAfter()) {
							 | 
						||
| 
								 | 
							
								          firstNodeParent.insertAfter(nextSibling);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          firstNode.insertAfter(nextSibling);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      nextSibling.select(0, 0);
							 | 
						||
| 
								 | 
							
								      firstNode = nextSibling;
							 | 
						||
| 
								 | 
							
								      if (text !== '') {
							 | 
						||
| 
								 | 
							
								        this.insertText(text);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else if (
							 | 
						||
| 
								 | 
							
								      this.isCollapsed() &&
							 | 
						||
| 
								 | 
							
								      startOffset === 0 &&
							 | 
						||
| 
								 | 
							
								      (firstNode.isSegmented() ||
							 | 
						||
| 
								 | 
							
								        firstNode.isToken() ||
							 | 
						||
| 
								 | 
							
								        !firstNode.canInsertTextBefore() ||
							 | 
						||
| 
								 | 
							
								        (!firstNodeParent.canInsertTextBefore() &&
							 | 
						||
| 
								 | 
							
								          firstNode.getPreviousSibling() === null))
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      let prevSibling = firstNode.getPreviousSibling<TextNode>();
							 | 
						||
| 
								 | 
							
								      if (!$isTextNode(prevSibling) || $isTokenOrSegmented(prevSibling)) {
							 | 
						||
| 
								 | 
							
								        prevSibling = $createTextNode();
							 | 
						||
| 
								 | 
							
								        prevSibling.setFormat(format);
							 | 
						||
| 
								 | 
							
								        if (!firstNodeParent.canInsertTextBefore()) {
							 | 
						||
| 
								 | 
							
								          firstNodeParent.insertBefore(prevSibling);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          firstNode.insertBefore(prevSibling);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      prevSibling.select();
							 | 
						||
| 
								 | 
							
								      firstNode = prevSibling;
							 | 
						||
| 
								 | 
							
								      if (text !== '') {
							 | 
						||
| 
								 | 
							
								        this.insertText(text);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else if (firstNode.isSegmented() && startOffset !== firstNodeTextLength) {
							 | 
						||
| 
								 | 
							
								      const textNode = $createTextNode(firstNode.getTextContent());
							 | 
						||
| 
								 | 
							
								      textNode.setFormat(format);
							 | 
						||
| 
								 | 
							
								      firstNode.replace(textNode);
							 | 
						||
| 
								 | 
							
								      firstNode = textNode;
							 | 
						||
| 
								 | 
							
								    } else if (!this.isCollapsed() && text !== '') {
							 | 
						||
| 
								 | 
							
								      // When the firstNode or lastNode parents are elements that
							 | 
						||
| 
								 | 
							
								      // do not allow text to be inserted before or after, we first
							 | 
						||
| 
								 | 
							
								      // clear the content. Then we normalize selection, then insert
							 | 
						||
| 
								 | 
							
								      // the new content.
							 | 
						||
| 
								 | 
							
								      const lastNodeParent = lastNode.getParent();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        !firstNodeParent.canInsertTextBefore() ||
							 | 
						||
| 
								 | 
							
								        !firstNodeParent.canInsertTextAfter() ||
							 | 
						||
| 
								 | 
							
								        ($isElementNode(lastNodeParent) &&
							 | 
						||
| 
								 | 
							
								          (!lastNodeParent.canInsertTextBefore() ||
							 | 
						||
| 
								 | 
							
								            !lastNodeParent.canInsertTextAfter()))
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        this.insertText('');
							 | 
						||
| 
								 | 
							
								        $normalizeSelectionPointsForBoundaries(this.anchor, this.focus, null);
							 | 
						||
| 
								 | 
							
								        this.insertText(text);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (selectedNodesLength === 1) {
							 | 
						||
| 
								 | 
							
								      if (firstNode.isToken()) {
							 | 
						||
| 
								 | 
							
								        const textNode = $createTextNode(text);
							 | 
						||
| 
								 | 
							
								        textNode.select();
							 | 
						||
| 
								 | 
							
								        firstNode.replace(textNode);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const firstNodeFormat = firstNode.getFormat();
							 | 
						||
| 
								 | 
							
								      const firstNodeStyle = firstNode.getStyle();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        startOffset === endOffset &&
							 | 
						||
| 
								 | 
							
								        (firstNodeFormat !== format || firstNodeStyle !== style)
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        if (firstNode.getTextContent() === '') {
							 | 
						||
| 
								 | 
							
								          firstNode.setFormat(format);
							 | 
						||
| 
								 | 
							
								          firstNode.setStyle(style);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          const textNode = $createTextNode(text);
							 | 
						||
| 
								 | 
							
								          textNode.setFormat(format);
							 | 
						||
| 
								 | 
							
								          textNode.setStyle(style);
							 | 
						||
| 
								 | 
							
								          textNode.select();
							 | 
						||
| 
								 | 
							
								          if (startOffset === 0) {
							 | 
						||
| 
								 | 
							
								            firstNode.insertBefore(textNode, false);
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            const [targetNode] = firstNode.splitText(startOffset);
							 | 
						||
| 
								 | 
							
								            targetNode.insertAfter(textNode, false);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          // When composing, we need to adjust the anchor offset so that
							 | 
						||
| 
								 | 
							
								          // we correctly replace that right range.
							 | 
						||
| 
								 | 
							
								          if (textNode.isComposing() && this.anchor.type === 'text') {
							 | 
						||
| 
								 | 
							
								            this.anchor.offset -= text.length;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else if ($isTabNode(firstNode)) {
							 | 
						||
| 
								 | 
							
								        // We don't need to check for delCount because there is only the entire selected node case
							 | 
						||
| 
								 | 
							
								        // that can hit here for content size 1 and with canInsertTextBeforeAfter false
							 | 
						||
| 
								 | 
							
								        const textNode = $createTextNode(text);
							 | 
						||
| 
								 | 
							
								        textNode.setFormat(format);
							 | 
						||
| 
								 | 
							
								        textNode.setStyle(style);
							 | 
						||
| 
								 | 
							
								        textNode.select();
							 | 
						||
| 
								 | 
							
								        firstNode.replace(textNode);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const delCount = endOffset - startOffset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      firstNode = firstNode.spliceText(startOffset, delCount, text, true);
							 | 
						||
| 
								 | 
							
								      if (firstNode.getTextContent() === '') {
							 | 
						||
| 
								 | 
							
								        firstNode.remove();
							 | 
						||
| 
								 | 
							
								      } else if (this.anchor.type === 'text') {
							 | 
						||
| 
								 | 
							
								        if (firstNode.isComposing()) {
							 | 
						||
| 
								 | 
							
								          // When composing, we need to adjust the anchor offset so that
							 | 
						||
| 
								 | 
							
								          // we correctly replace that right range.
							 | 
						||
| 
								 | 
							
								          this.anchor.offset -= text.length;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          this.format = firstNodeFormat;
							 | 
						||
| 
								 | 
							
								          this.style = firstNodeStyle;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const markedNodeKeysForKeep = new Set([
							 | 
						||
| 
								 | 
							
								        ...firstNode.getParentKeys(),
							 | 
						||
| 
								 | 
							
								        ...lastNode.getParentKeys(),
							 | 
						||
| 
								 | 
							
								      ]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // We have to get the parent elements before the next section,
							 | 
						||
| 
								 | 
							
								      // as in that section we might mutate the lastNode.
							 | 
						||
| 
								 | 
							
								      const firstElement = $isElementNode(firstNode)
							 | 
						||
| 
								 | 
							
								        ? firstNode
							 | 
						||
| 
								 | 
							
								        : firstNode.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								      let lastElement = $isElementNode(lastNode)
							 | 
						||
| 
								 | 
							
								        ? lastNode
							 | 
						||
| 
								 | 
							
								        : lastNode.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								      let lastElementChild = lastNode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // If the last element is inline, we should instead look at getting
							 | 
						||
| 
								 | 
							
								      // the nodes of its parent, rather than itself. This behavior will
							 | 
						||
| 
								 | 
							
								      // then better match how text node insertions work. We will need to
							 | 
						||
| 
								 | 
							
								      // also update the last element's child accordingly as we do this.
							 | 
						||
| 
								 | 
							
								      if (!firstElement.is(lastElement) && lastElement.isInline()) {
							 | 
						||
| 
								 | 
							
								        // Keep traversing till we have a non-inline element parent.
							 | 
						||
| 
								 | 
							
								        do {
							 | 
						||
| 
								 | 
							
								          lastElementChild = lastElement;
							 | 
						||
| 
								 | 
							
								          lastElement = lastElement.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								        } while (lastElement.isInline());
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Handle mutations to the last node.
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        (endPoint.type === 'text' &&
							 | 
						||
| 
								 | 
							
								          (endOffset !== 0 || lastNode.getTextContent() === '')) ||
							 | 
						||
| 
								 | 
							
								        (endPoint.type === 'element' &&
							 | 
						||
| 
								 | 
							
								          lastNode.getIndexWithinParent() < endOffset)
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          $isTextNode(lastNode) &&
							 | 
						||
| 
								 | 
							
								          !lastNode.isToken() &&
							 | 
						||
| 
								 | 
							
								          endOffset !== lastNode.getTextContentSize()
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          if (lastNode.isSegmented()) {
							 | 
						||
| 
								 | 
							
								            const textNode = $createTextNode(lastNode.getTextContent());
							 | 
						||
| 
								 | 
							
								            lastNode.replace(textNode);
							 | 
						||
| 
								 | 
							
								            lastNode = textNode;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          // root node selections only select whole nodes, so no text splice is necessary
							 | 
						||
| 
								 | 
							
								          if (!$isRootNode(endPoint.getNode()) && endPoint.type === 'text') {
							 | 
						||
| 
								 | 
							
								            lastNode = (lastNode as TextNode).spliceText(0, endOffset, '');
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          markedNodeKeysForKeep.add(lastNode.__key);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          const lastNodeParent = lastNode.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            !lastNodeParent.canBeEmpty() &&
							 | 
						||
| 
								 | 
							
								            lastNodeParent.getChildrenSize() === 1
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            lastNodeParent.remove();
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            lastNode.remove();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        markedNodeKeysForKeep.add(lastNode.__key);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Either move the remaining nodes of the last parent to after
							 | 
						||
| 
								 | 
							
								      // the first child, or remove them entirely. If the last parent
							 | 
						||
| 
								 | 
							
								      // is the same as the first parent, this logic also works.
							 | 
						||
| 
								 | 
							
								      const lastNodeChildren = lastElement.getChildren();
							 | 
						||
| 
								 | 
							
								      const selectedNodesSet = new Set(selectedNodes);
							 | 
						||
| 
								 | 
							
								      const firstAndLastElementsAreEqual = firstElement.is(lastElement);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // We choose a target to insert all nodes after. In the case of having
							 | 
						||
| 
								 | 
							
								      // and inline starting parent element with a starting node that has no
							 | 
						||
| 
								 | 
							
								      // siblings, we should insert after the starting parent element, otherwise
							 | 
						||
| 
								 | 
							
								      // we will incorrectly merge into the starting parent element.
							 | 
						||
| 
								 | 
							
								      // TODO: should we keep on traversing parents if we're inside another
							 | 
						||
| 
								 | 
							
								      // nested inline element?
							 | 
						||
| 
								 | 
							
								      const insertionTarget =
							 | 
						||
| 
								 | 
							
								        firstElement.isInline() && firstNode.getNextSibling() === null
							 | 
						||
| 
								 | 
							
								          ? firstElement
							 | 
						||
| 
								 | 
							
								          : firstNode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      for (let i = lastNodeChildren.length - 1; i >= 0; i--) {
							 | 
						||
| 
								 | 
							
								        const lastNodeChild = lastNodeChildren[i];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          lastNodeChild.is(firstNode) ||
							 | 
						||
| 
								 | 
							
								          ($isElementNode(lastNodeChild) && lastNodeChild.isParentOf(firstNode))
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          break;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (lastNodeChild.isAttached()) {
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            !selectedNodesSet.has(lastNodeChild) ||
							 | 
						||
| 
								 | 
							
								            lastNodeChild.is(lastElementChild)
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            if (!firstAndLastElementsAreEqual) {
							 | 
						||
| 
								 | 
							
								              insertionTarget.insertAfter(lastNodeChild, false);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            lastNodeChild.remove();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!firstAndLastElementsAreEqual) {
							 | 
						||
| 
								 | 
							
								        // Check if we have already moved out all the nodes of the
							 | 
						||
| 
								 | 
							
								        // last parent, and if so, traverse the parent tree and mark
							 | 
						||
| 
								 | 
							
								        // them all as being able to deleted too.
							 | 
						||
| 
								 | 
							
								        let parent: ElementNode | null = lastElement;
							 | 
						||
| 
								 | 
							
								        let lastRemovedParent = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        while (parent !== null) {
							 | 
						||
| 
								 | 
							
								          const children = parent.getChildren();
							 | 
						||
| 
								 | 
							
								          const childrenLength = children.length;
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            childrenLength === 0 ||
							 | 
						||
| 
								 | 
							
								            children[childrenLength - 1].is(lastRemovedParent)
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            markedNodeKeysForKeep.delete(parent.__key);
							 | 
						||
| 
								 | 
							
								            lastRemovedParent = parent;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          parent = parent.getParent();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Ensure we do splicing after moving of nodes, as splicing
							 | 
						||
| 
								 | 
							
								      // can have side-effects (in the case of hashtags).
							 | 
						||
| 
								 | 
							
								      if (!firstNode.isToken()) {
							 | 
						||
| 
								 | 
							
								        firstNode = firstNode.spliceText(
							 | 
						||
| 
								 | 
							
								          startOffset,
							 | 
						||
| 
								 | 
							
								          firstNodeTextLength - startOffset,
							 | 
						||
| 
								 | 
							
								          text,
							 | 
						||
| 
								 | 
							
								          true,
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        if (firstNode.getTextContent() === '') {
							 | 
						||
| 
								 | 
							
								          firstNode.remove();
							 | 
						||
| 
								 | 
							
								        } else if (firstNode.isComposing() && this.anchor.type === 'text') {
							 | 
						||
| 
								 | 
							
								          // When composing, we need to adjust the anchor offset so that
							 | 
						||
| 
								 | 
							
								          // we correctly replace that right range.
							 | 
						||
| 
								 | 
							
								          this.anchor.offset -= text.length;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else if (startOffset === firstNodeTextLength) {
							 | 
						||
| 
								 | 
							
								        firstNode.select();
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        const textNode = $createTextNode(text);
							 | 
						||
| 
								 | 
							
								        textNode.select();
							 | 
						||
| 
								 | 
							
								        firstNode.replace(textNode);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Remove all selected nodes that haven't already been removed.
							 | 
						||
| 
								 | 
							
								      for (let i = 1; i < selectedNodesLength; i++) {
							 | 
						||
| 
								 | 
							
								        const selectedNode = selectedNodes[i];
							 | 
						||
| 
								 | 
							
								        const key = selectedNode.__key;
							 | 
						||
| 
								 | 
							
								        if (!markedNodeKeysForKeep.has(key)) {
							 | 
						||
| 
								 | 
							
								          selectedNode.remove();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Removes the text in the Selection, adjusting the EditorState accordingly.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  removeText(): void {
							 | 
						||
| 
								 | 
							
								    this.insertText('');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Applies the provided format to the TextNodes in the Selection, splitting or
							 | 
						||
| 
								 | 
							
								   * merging nodes as necessary.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param formatType the format type to apply to the nodes in the Selection.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  formatText(formatType: TextFormatType): void {
							 | 
						||
| 
								 | 
							
								    if (this.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								      this.toggleFormat(formatType);
							 | 
						||
| 
								 | 
							
								      // When changing format, we should stop composition
							 | 
						||
| 
								 | 
							
								      $setCompositionKey(null);
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const selectedNodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								    const selectedTextNodes: Array<TextNode> = [];
							 | 
						||
| 
								 | 
							
								    for (const selectedNode of selectedNodes) {
							 | 
						||
| 
								 | 
							
								      if ($isTextNode(selectedNode)) {
							 | 
						||
| 
								 | 
							
								        selectedTextNodes.push(selectedNode);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const selectedTextNodesLength = selectedTextNodes.length;
							 | 
						||
| 
								 | 
							
								    if (selectedTextNodesLength === 0) {
							 | 
						||
| 
								 | 
							
								      this.toggleFormat(formatType);
							 | 
						||
| 
								 | 
							
								      // When changing format, we should stop composition
							 | 
						||
| 
								 | 
							
								      $setCompositionKey(null);
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    const isBackward = this.isBackward();
							 | 
						||
| 
								 | 
							
								    const startPoint = isBackward ? focus : anchor;
							 | 
						||
| 
								 | 
							
								    const endPoint = isBackward ? anchor : focus;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let firstIndex = 0;
							 | 
						||
| 
								 | 
							
								    let firstNode = selectedTextNodes[0];
							 | 
						||
| 
								 | 
							
								    let startOffset = startPoint.type === 'element' ? 0 : startPoint.offset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // In case selection started at the end of text node use next text node
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      startPoint.type === 'text' &&
							 | 
						||
| 
								 | 
							
								      startOffset === firstNode.getTextContentSize()
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      firstIndex = 1;
							 | 
						||
| 
								 | 
							
								      firstNode = selectedTextNodes[1];
							 | 
						||
| 
								 | 
							
								      startOffset = 0;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (firstNode == null) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const firstNextFormat = firstNode.getFormatFlags(formatType, null);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const lastIndex = selectedTextNodesLength - 1;
							 | 
						||
| 
								 | 
							
								    let lastNode = selectedTextNodes[lastIndex];
							 | 
						||
| 
								 | 
							
								    const endOffset =
							 | 
						||
| 
								 | 
							
								      endPoint.type === 'text'
							 | 
						||
| 
								 | 
							
								        ? endPoint.offset
							 | 
						||
| 
								 | 
							
								        : lastNode.getTextContentSize();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Single node selected
							 | 
						||
| 
								 | 
							
								    if (firstNode.is(lastNode)) {
							 | 
						||
| 
								 | 
							
								      // No actual text is selected, so do nothing.
							 | 
						||
| 
								 | 
							
								      if (startOffset === endOffset) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      // The entire node is selected or it is token, so just format it
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        $isTokenOrSegmented(firstNode) ||
							 | 
						||
| 
								 | 
							
								        (startOffset === 0 && endOffset === firstNode.getTextContentSize())
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        firstNode.setFormat(firstNextFormat);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        // Node is partially selected, so split it into two nodes
							 | 
						||
| 
								 | 
							
								        // add style the selected one.
							 | 
						||
| 
								 | 
							
								        const splitNodes = firstNode.splitText(startOffset, endOffset);
							 | 
						||
| 
								 | 
							
								        const replacement = startOffset === 0 ? splitNodes[0] : splitNodes[1];
							 | 
						||
| 
								 | 
							
								        replacement.setFormat(firstNextFormat);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Update selection only if starts/ends on text node
							 | 
						||
| 
								 | 
							
								        if (startPoint.type === 'text') {
							 | 
						||
| 
								 | 
							
								          startPoint.set(replacement.__key, 0, 'text');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (endPoint.type === 'text') {
							 | 
						||
| 
								 | 
							
								          endPoint.set(replacement.__key, endOffset - startOffset, 'text');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      this.format = firstNextFormat;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    // Multiple nodes selected
							 | 
						||
| 
								 | 
							
								    // The entire first node isn't selected, so split it
							 | 
						||
| 
								 | 
							
								    if (startOffset !== 0 && !$isTokenOrSegmented(firstNode)) {
							 | 
						||
| 
								 | 
							
								      [, firstNode as TextNode] = firstNode.splitText(startOffset);
							 | 
						||
| 
								 | 
							
								      startOffset = 0;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    firstNode.setFormat(firstNextFormat);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const lastNextFormat = lastNode.getFormatFlags(formatType, firstNextFormat);
							 | 
						||
| 
								 | 
							
								    // If the offset is 0, it means no actual characters are selected,
							 | 
						||
| 
								 | 
							
								    // so we skip formatting the last node altogether.
							 | 
						||
| 
								 | 
							
								    if (endOffset > 0) {
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        endOffset !== lastNode.getTextContentSize() &&
							 | 
						||
| 
								 | 
							
								        !$isTokenOrSegmented(lastNode)
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        [lastNode as TextNode] = lastNode.splitText(endOffset);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      lastNode.setFormat(lastNextFormat);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Process all text nodes in between
							 | 
						||
| 
								 | 
							
								    for (let i = firstIndex + 1; i < lastIndex; i++) {
							 | 
						||
| 
								 | 
							
								      const textNode = selectedTextNodes[i];
							 | 
						||
| 
								 | 
							
								      const nextFormat = textNode.getFormatFlags(formatType, lastNextFormat);
							 | 
						||
| 
								 | 
							
								      textNode.setFormat(nextFormat);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Update selection only if starts/ends on text node
							 | 
						||
| 
								 | 
							
								    if (startPoint.type === 'text') {
							 | 
						||
| 
								 | 
							
								      startPoint.set(firstNode.__key, startOffset, 'text');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (endPoint.type === 'text') {
							 | 
						||
| 
								 | 
							
								      endPoint.set(lastNode.__key, endOffset, 'text');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.format = firstNextFormat | lastNextFormat;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Attempts to "intelligently" insert an arbitrary list of Lexical nodes into the EditorState at the
							 | 
						||
| 
								 | 
							
								   * current Selection according to a set of heuristics that determine how surrounding nodes
							 | 
						||
| 
								 | 
							
								   * should be changed, replaced, or moved to accomodate the incoming ones.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param nodes - the nodes to insert
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  insertNodes(nodes: Array<LexicalNode>): void {
							 | 
						||
| 
								 | 
							
								    if (nodes.length === 0) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (this.anchor.key === 'root') {
							 | 
						||
| 
								 | 
							
								      this.insertParagraph();
							 | 
						||
| 
								 | 
							
								      const selection = $getSelection();
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        $isRangeSelection(selection),
							 | 
						||
| 
								 | 
							
								        'Expected RangeSelection after insertParagraph',
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								      return selection.insertNodes(nodes);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const firstPoint = this.isBackward() ? this.focus : this.anchor;
							 | 
						||
| 
								 | 
							
								    const firstBlock = $getAncestor(firstPoint.getNode(), INTERNAL_$isBlock)!;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const last = nodes[nodes.length - 1]!;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // CASE 1: insert inside a code block
							 | 
						||
| 
								 | 
							
								    if ('__language' in firstBlock && $isElementNode(firstBlock)) {
							 | 
						||
| 
								 | 
							
								      if ('__language' in nodes[0]) {
							 | 
						||
| 
								 | 
							
								        this.insertText(nodes[0].getTextContent());
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        const index = $removeTextAndSplitBlock(this);
							 | 
						||
| 
								 | 
							
								        firstBlock.splice(index, 0, nodes);
							 | 
						||
| 
								 | 
							
								        last.selectEnd();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // CASE 2: All elements of the array are inline
							 | 
						||
| 
								 | 
							
								    const notInline = (node: LexicalNode) =>
							 | 
						||
| 
								 | 
							
								      ($isElementNode(node) || $isDecoratorNode(node)) && !node.isInline();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!nodes.some(notInline)) {
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        $isElementNode(firstBlock),
							 | 
						||
| 
								 | 
							
								        "Expected 'firstBlock' to be an ElementNode",
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								      const index = $removeTextAndSplitBlock(this);
							 | 
						||
| 
								 | 
							
								      firstBlock.splice(index, 0, nodes);
							 | 
						||
| 
								 | 
							
								      last.selectEnd();
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // CASE 3: At least 1 element of the array is not inline
							 | 
						||
| 
								 | 
							
								    const blocksParent = $wrapInlineNodes(nodes);
							 | 
						||
| 
								 | 
							
								    const nodeToSelect = blocksParent.getLastDescendant()!;
							 | 
						||
| 
								 | 
							
								    const blocks = blocksParent.getChildren();
							 | 
						||
| 
								 | 
							
								    const isMergeable = (node: LexicalNode): node is ElementNode =>
							 | 
						||
| 
								 | 
							
								      $isElementNode(node) &&
							 | 
						||
| 
								 | 
							
								      INTERNAL_$isBlock(node) &&
							 | 
						||
| 
								 | 
							
								      !node.isEmpty() &&
							 | 
						||
| 
								 | 
							
								      $isElementNode(firstBlock) &&
							 | 
						||
| 
								 | 
							
								      (!firstBlock.isEmpty() || firstBlock.canMergeWhenEmpty());
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const shouldInsert = !$isElementNode(firstBlock) || !firstBlock.isEmpty();
							 | 
						||
| 
								 | 
							
								    const insertedParagraph = shouldInsert ? this.insertParagraph() : null;
							 | 
						||
| 
								 | 
							
								    const lastToInsert = blocks[blocks.length - 1];
							 | 
						||
| 
								 | 
							
								    let firstToInsert = blocks[0];
							 | 
						||
| 
								 | 
							
								    if (isMergeable(firstToInsert)) {
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        $isElementNode(firstBlock),
							 | 
						||
| 
								 | 
							
								        "Expected 'firstBlock' to be an ElementNode",
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								      firstBlock.append(...firstToInsert.getChildren());
							 | 
						||
| 
								 | 
							
								      firstToInsert = blocks[1];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (firstToInsert) {
							 | 
						||
| 
								 | 
							
								      insertRangeAfter(firstBlock, firstToInsert);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const lastInsertedBlock = $getAncestor(nodeToSelect, INTERNAL_$isBlock)!;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      insertedParagraph &&
							 | 
						||
| 
								 | 
							
								      $isElementNode(lastInsertedBlock) &&
							 | 
						||
| 
								 | 
							
								      (insertedParagraph.canMergeWhenEmpty() || INTERNAL_$isBlock(lastToInsert))
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      lastInsertedBlock.append(...insertedParagraph.getChildren());
							 | 
						||
| 
								 | 
							
								      insertedParagraph.remove();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if ($isElementNode(firstBlock) && firstBlock.isEmpty()) {
							 | 
						||
| 
								 | 
							
								      firstBlock.remove();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    nodeToSelect.selectEnd();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // To understand this take a look at the test "can wrap post-linebreak nodes into new element"
							 | 
						||
| 
								 | 
							
								    const lastChild = $isElementNode(firstBlock)
							 | 
						||
| 
								 | 
							
								      ? firstBlock.getLastChild()
							 | 
						||
| 
								 | 
							
								      : null;
							 | 
						||
| 
								 | 
							
								    if ($isLineBreakNode(lastChild) && lastInsertedBlock !== firstBlock) {
							 | 
						||
| 
								 | 
							
								      lastChild.remove();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Inserts a new ParagraphNode into the EditorState at the current Selection
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @returns the newly inserted node.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  insertParagraph(): ElementNode | null {
							 | 
						||
| 
								 | 
							
								    if (this.anchor.key === 'root') {
							 | 
						||
| 
								 | 
							
								      const paragraph = $createParagraphNode();
							 | 
						||
| 
								 | 
							
								      $getRoot().splice(this.anchor.offset, 0, [paragraph]);
							 | 
						||
| 
								 | 
							
								      paragraph.select();
							 | 
						||
| 
								 | 
							
								      return paragraph;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const index = $removeTextAndSplitBlock(this);
							 | 
						||
| 
								 | 
							
								    const block = $getAncestor(this.anchor.getNode(), INTERNAL_$isBlock)!;
							 | 
						||
| 
								 | 
							
								    invariant($isElementNode(block), 'Expected ancestor to be an ElementNode');
							 | 
						||
| 
								 | 
							
								    const firstToAppend = block.getChildAtIndex(index);
							 | 
						||
| 
								 | 
							
								    const nodesToInsert = firstToAppend
							 | 
						||
| 
								 | 
							
								      ? [firstToAppend, ...firstToAppend.getNextSiblings()]
							 | 
						||
| 
								 | 
							
								      : [];
							 | 
						||
| 
								 | 
							
								    const newBlock = block.insertNewAfter(this, false) as ElementNode | null;
							 | 
						||
| 
								 | 
							
								    if (newBlock) {
							 | 
						||
| 
								 | 
							
								      newBlock.append(...nodesToInsert);
							 | 
						||
| 
								 | 
							
								      newBlock.selectStart();
							 | 
						||
| 
								 | 
							
								      return newBlock;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    // if newBlock is null, it means that block is of type CodeNode.
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Inserts a logical linebreak, which may be a new LineBreakNode or a new ParagraphNode, into the EditorState at the
							 | 
						||
| 
								 | 
							
								   * current Selection.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  insertLineBreak(selectStart?: boolean): void {
							 | 
						||
| 
								 | 
							
								    const lineBreak = $createLineBreakNode();
							 | 
						||
| 
								 | 
							
								    this.insertNodes([lineBreak]);
							 | 
						||
| 
								 | 
							
								    // this is used in MacOS with the command 'ctrl-O' (openLineBreak)
							 | 
						||
| 
								 | 
							
								    if (selectStart) {
							 | 
						||
| 
								 | 
							
								      const parent = lineBreak.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								      const index = lineBreak.getIndexWithinParent();
							 | 
						||
| 
								 | 
							
								      parent.select(index, index);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Extracts the nodes in the Selection, splitting nodes where necessary
							 | 
						||
| 
								 | 
							
								   * to get offset-level precision.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @returns The nodes in the Selection
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  extract(): Array<LexicalNode> {
							 | 
						||
| 
								 | 
							
								    const selectedNodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								    const selectedNodesLength = selectedNodes.length;
							 | 
						||
| 
								 | 
							
								    const lastIndex = selectedNodesLength - 1;
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    let firstNode = selectedNodes[0];
							 | 
						||
| 
								 | 
							
								    let lastNode = selectedNodes[lastIndex];
							 | 
						||
| 
								 | 
							
								    const [anchorOffset, focusOffset] = $getCharacterOffsets(this);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (selectedNodesLength === 0) {
							 | 
						||
| 
								 | 
							
								      return [];
							 | 
						||
| 
								 | 
							
								    } else if (selectedNodesLength === 1) {
							 | 
						||
| 
								 | 
							
								      if ($isTextNode(firstNode) && !this.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								        const startOffset =
							 | 
						||
| 
								 | 
							
								          anchorOffset > focusOffset ? focusOffset : anchorOffset;
							 | 
						||
| 
								 | 
							
								        const endOffset =
							 | 
						||
| 
								 | 
							
								          anchorOffset > focusOffset ? anchorOffset : focusOffset;
							 | 
						||
| 
								 | 
							
								        const splitNodes = firstNode.splitText(startOffset, endOffset);
							 | 
						||
| 
								 | 
							
								        const node = startOffset === 0 ? splitNodes[0] : splitNodes[1];
							 | 
						||
| 
								 | 
							
								        return node != null ? [node] : [];
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return [firstNode];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const isBefore = anchor.isBefore(focus);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(firstNode)) {
							 | 
						||
| 
								 | 
							
								      const startOffset = isBefore ? anchorOffset : focusOffset;
							 | 
						||
| 
								 | 
							
								      if (startOffset === firstNode.getTextContentSize()) {
							 | 
						||
| 
								 | 
							
								        selectedNodes.shift();
							 | 
						||
| 
								 | 
							
								      } else if (startOffset !== 0) {
							 | 
						||
| 
								 | 
							
								        [, firstNode] = firstNode.splitText(startOffset);
							 | 
						||
| 
								 | 
							
								        selectedNodes[0] = firstNode;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(lastNode)) {
							 | 
						||
| 
								 | 
							
								      const lastNodeText = lastNode.getTextContent();
							 | 
						||
| 
								 | 
							
								      const lastNodeTextLength = lastNodeText.length;
							 | 
						||
| 
								 | 
							
								      const endOffset = isBefore ? focusOffset : anchorOffset;
							 | 
						||
| 
								 | 
							
								      if (endOffset === 0) {
							 | 
						||
| 
								 | 
							
								        selectedNodes.pop();
							 | 
						||
| 
								 | 
							
								      } else if (endOffset !== lastNodeTextLength) {
							 | 
						||
| 
								 | 
							
								        [lastNode] = lastNode.splitText(endOffset);
							 | 
						||
| 
								 | 
							
								        selectedNodes[lastIndex] = lastNode;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return selectedNodes;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Modifies the Selection according to the parameters and a set of heuristics that account for
							 | 
						||
| 
								 | 
							
								   * various node types. Can be used to safely move or extend selection by one logical "unit" without
							 | 
						||
| 
								 | 
							
								   * dealing explicitly with all the possible node types.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param alter the type of modification to perform
							 | 
						||
| 
								 | 
							
								   * @param isBackward whether or not selection is backwards
							 | 
						||
| 
								 | 
							
								   * @param granularity the granularity at which to apply the modification
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  modify(
							 | 
						||
| 
								 | 
							
								    alter: 'move' | 'extend',
							 | 
						||
| 
								 | 
							
								    isBackward: boolean,
							 | 
						||
| 
								 | 
							
								    granularity: 'character' | 'word' | 'lineboundary',
							 | 
						||
| 
								 | 
							
								  ): void {
							 | 
						||
| 
								 | 
							
								    const focus = this.focus;
							 | 
						||
| 
								 | 
							
								    const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								    const collapse = alter === 'move';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Handle the selection movement around decorators.
							 | 
						||
| 
								 | 
							
								    const possibleNode = $getAdjacentNode(focus, isBackward);
							 | 
						||
| 
								 | 
							
								    if ($isDecoratorNode(possibleNode) && !possibleNode.isIsolated()) {
							 | 
						||
| 
								 | 
							
								      // Make it possible to move selection from range selection to
							 | 
						||
| 
								 | 
							
								      // node selection on the node.
							 | 
						||
| 
								 | 
							
								      if (collapse && possibleNode.isKeyboardSelectable()) {
							 | 
						||
| 
								 | 
							
								        const nodeSelection = $createNodeSelection();
							 | 
						||
| 
								 | 
							
								        nodeSelection.add(possibleNode.__key);
							 | 
						||
| 
								 | 
							
								        $setSelection(nodeSelection);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const sibling = isBackward
							 | 
						||
| 
								 | 
							
								        ? possibleNode.getPreviousSibling()
							 | 
						||
| 
								 | 
							
								        : possibleNode.getNextSibling();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!$isTextNode(sibling)) {
							 | 
						||
| 
								 | 
							
								        const parent = possibleNode.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								        let offset;
							 | 
						||
| 
								 | 
							
								        let elementKey;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($isElementNode(sibling)) {
							 | 
						||
| 
								 | 
							
								          elementKey = sibling.__key;
							 | 
						||
| 
								 | 
							
								          offset = isBackward ? sibling.getChildrenSize() : 0;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          offset = possibleNode.getIndexWithinParent();
							 | 
						||
| 
								 | 
							
								          elementKey = parent.__key;
							 | 
						||
| 
								 | 
							
								          if (!isBackward) {
							 | 
						||
| 
								 | 
							
								            offset++;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        focus.set(elementKey, offset, 'element');
							 | 
						||
| 
								 | 
							
								        if (collapse) {
							 | 
						||
| 
								 | 
							
								          anchor.set(elementKey, offset, 'element');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        const siblingKey = sibling.__key;
							 | 
						||
| 
								 | 
							
								        const offset = isBackward ? sibling.getTextContent().length : 0;
							 | 
						||
| 
								 | 
							
								        focus.set(siblingKey, offset, 'text');
							 | 
						||
| 
								 | 
							
								        if (collapse) {
							 | 
						||
| 
								 | 
							
								          anchor.set(siblingKey, offset, 'text');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								    const domSelection = getDOMSelection(editor._window);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!domSelection) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const blockCursorElement = editor._blockCursorElement;
							 | 
						||
| 
								 | 
							
								    const rootElement = editor._rootElement;
							 | 
						||
| 
								 | 
							
								    // Remove the block cursor element if it exists. This will ensure selection
							 | 
						||
| 
								 | 
							
								    // works as intended. If we leave it in the DOM all sorts of strange bugs
							 | 
						||
| 
								 | 
							
								    // occur. :/
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      rootElement !== null &&
							 | 
						||
| 
								 | 
							
								      blockCursorElement !== null &&
							 | 
						||
| 
								 | 
							
								      $isElementNode(possibleNode) &&
							 | 
						||
| 
								 | 
							
								      !possibleNode.isInline() &&
							 | 
						||
| 
								 | 
							
								      !possibleNode.canBeEmpty()
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      removeDOMBlockCursorElement(blockCursorElement, editor, rootElement);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    // We use the DOM selection.modify API here to "tell" us what the selection
							 | 
						||
| 
								 | 
							
								    // will be. We then use it to update the Lexical selection accordingly. This
							 | 
						||
| 
								 | 
							
								    // is much more reliable than waiting for a beforeinput and using the ranges
							 | 
						||
| 
								 | 
							
								    // from getTargetRanges(), and is also better than trying to do it ourselves
							 | 
						||
| 
								 | 
							
								    // using Intl.Segmenter or other workarounds that struggle with word segments
							 | 
						||
| 
								 | 
							
								    // and line segments (especially with word wrapping and non-Roman languages).
							 | 
						||
| 
								 | 
							
								    moveNativeSelection(
							 | 
						||
| 
								 | 
							
								      domSelection,
							 | 
						||
| 
								 | 
							
								      alter,
							 | 
						||
| 
								 | 
							
								      isBackward ? 'backward' : 'forward',
							 | 
						||
| 
								 | 
							
								      granularity,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    // Guard against no ranges
							 | 
						||
| 
								 | 
							
								    if (domSelection.rangeCount > 0) {
							 | 
						||
| 
								 | 
							
								      const range = domSelection.getRangeAt(0);
							 | 
						||
| 
								 | 
							
								      // Apply the DOM selection to our Lexical selection.
							 | 
						||
| 
								 | 
							
								      const anchorNode = this.anchor.getNode();
							 | 
						||
| 
								 | 
							
								      const root = $isRootNode(anchorNode)
							 | 
						||
| 
								 | 
							
								        ? anchorNode
							 | 
						||
| 
								 | 
							
								        : $getNearestRootOrShadowRoot(anchorNode);
							 | 
						||
| 
								 | 
							
								      this.applyDOMRange(range);
							 | 
						||
| 
								 | 
							
								      this.dirty = true;
							 | 
						||
| 
								 | 
							
								      if (!collapse) {
							 | 
						||
| 
								 | 
							
								        // Validate selection; make sure that the new extended selection respects shadow roots
							 | 
						||
| 
								 | 
							
								        const nodes = this.getNodes();
							 | 
						||
| 
								 | 
							
								        const validNodes = [];
							 | 
						||
| 
								 | 
							
								        let shrinkSelection = false;
							 | 
						||
| 
								 | 
							
								        for (let i = 0; i < nodes.length; i++) {
							 | 
						||
| 
								 | 
							
								          const nextNode = nodes[i];
							 | 
						||
| 
								 | 
							
								          if ($hasAncestor(nextNode, root)) {
							 | 
						||
| 
								 | 
							
								            validNodes.push(nextNode);
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            shrinkSelection = true;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (shrinkSelection && validNodes.length > 0) {
							 | 
						||
| 
								 | 
							
								          // validNodes length check is a safeguard against an invalid selection; as getNodes()
							 | 
						||
| 
								 | 
							
								          // will return an empty array in this case
							 | 
						||
| 
								 | 
							
								          if (isBackward) {
							 | 
						||
| 
								 | 
							
								            const firstValidNode = validNodes[0];
							 | 
						||
| 
								 | 
							
								            if ($isElementNode(firstValidNode)) {
							 | 
						||
| 
								 | 
							
								              firstValidNode.selectStart();
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								              firstValidNode.getParentOrThrow().selectStart();
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            const lastValidNode = validNodes[validNodes.length - 1];
							 | 
						||
| 
								 | 
							
								            if ($isElementNode(lastValidNode)) {
							 | 
						||
| 
								 | 
							
								              lastValidNode.selectEnd();
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								              lastValidNode.getParentOrThrow().selectEnd();
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Because a range works on start and end, we might need to flip
							 | 
						||
| 
								 | 
							
								        // the anchor and focus points to match what the DOM has, not what
							 | 
						||
| 
								 | 
							
								        // the range has specifically.
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          domSelection.anchorNode !== range.startContainer ||
							 | 
						||
| 
								 | 
							
								          domSelection.anchorOffset !== range.startOffset
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          $swapPoints(this);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Helper for handling forward character and word deletion that prevents element nodes
							 | 
						||
| 
								 | 
							
								   * like a table, columns layout being destroyed
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param anchor the anchor
							 | 
						||
| 
								 | 
							
								   * @param anchorNode the anchor node in the selection
							 | 
						||
| 
								 | 
							
								   * @param isBackward whether or not selection is backwards
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  forwardDeletion(
							 | 
						||
| 
								 | 
							
								    anchor: PointType,
							 | 
						||
| 
								 | 
							
								    anchorNode: TextNode | ElementNode,
							 | 
						||
| 
								 | 
							
								    isBackward: boolean,
							 | 
						||
| 
								 | 
							
								  ): boolean {
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      !isBackward &&
							 | 
						||
| 
								 | 
							
								      // Delete forward handle case
							 | 
						||
| 
								 | 
							
								      ((anchor.type === 'element' &&
							 | 
						||
| 
								 | 
							
								        $isElementNode(anchorNode) &&
							 | 
						||
| 
								 | 
							
								        anchor.offset === anchorNode.getChildrenSize()) ||
							 | 
						||
| 
								 | 
							
								        (anchor.type === 'text' &&
							 | 
						||
| 
								 | 
							
								          anchor.offset === anchorNode.getTextContentSize()))
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const parent = anchorNode.getParent();
							 | 
						||
| 
								 | 
							
								      const nextSibling =
							 | 
						||
| 
								 | 
							
								        anchorNode.getNextSibling() ||
							 | 
						||
| 
								 | 
							
								        (parent === null ? null : parent.getNextSibling());
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if ($isElementNode(nextSibling) && nextSibling.isShadowRoot()) {
							 | 
						||
| 
								 | 
							
								        return true;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Performs one logical character deletion operation on the EditorState based on the current Selection.
							 | 
						||
| 
								 | 
							
								   * Handles different node types.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param isBackward whether or not the selection is backwards.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  deleteCharacter(isBackward: boolean): void {
							 | 
						||
| 
								 | 
							
								    const wasCollapsed = this.isCollapsed();
							 | 
						||
| 
								 | 
							
								    if (this.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								      const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								      let anchorNode: TextNode | ElementNode | null = anchor.getNode();
							 | 
						||
| 
								 | 
							
								      if (this.forwardDeletion(anchor, anchorNode, isBackward)) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Handle the deletion around decorators.
							 | 
						||
| 
								 | 
							
								      const focus = this.focus;
							 | 
						||
| 
								 | 
							
								      const possibleNode = $getAdjacentNode(focus, isBackward);
							 | 
						||
| 
								 | 
							
								      if ($isDecoratorNode(possibleNode) && !possibleNode.isIsolated()) {
							 | 
						||
| 
								 | 
							
								        // Make it possible to move selection from range selection to
							 | 
						||
| 
								 | 
							
								        // node selection on the node.
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          possibleNode.isKeyboardSelectable() &&
							 | 
						||
| 
								 | 
							
								          $isElementNode(anchorNode) &&
							 | 
						||
| 
								 | 
							
								          anchorNode.getChildrenSize() === 0
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          anchorNode.remove();
							 | 
						||
| 
								 | 
							
								          const nodeSelection = $createNodeSelection();
							 | 
						||
| 
								 | 
							
								          nodeSelection.add(possibleNode.__key);
							 | 
						||
| 
								 | 
							
								          $setSelection(nodeSelection);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          possibleNode.remove();
							 | 
						||
| 
								 | 
							
								          const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								          editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      } else if (
							 | 
						||
| 
								 | 
							
								        !isBackward &&
							 | 
						||
| 
								 | 
							
								        $isElementNode(possibleNode) &&
							 | 
						||
| 
								 | 
							
								        $isElementNode(anchorNode) &&
							 | 
						||
| 
								 | 
							
								        anchorNode.isEmpty()
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        anchorNode.remove();
							 | 
						||
| 
								 | 
							
								        possibleNode.selectStart();
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.modify('extend', isBackward, 'character');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!this.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								        const focusNode = focus.type === 'text' ? focus.getNode() : null;
							 | 
						||
| 
								 | 
							
								        anchorNode = anchor.type === 'text' ? anchor.getNode() : null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (focusNode !== null && focusNode.isSegmented()) {
							 | 
						||
| 
								 | 
							
								          const offset = focus.offset;
							 | 
						||
| 
								 | 
							
								          const textContentSize = focusNode.getTextContentSize();
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            focusNode.is(anchorNode) ||
							 | 
						||
| 
								 | 
							
								            (isBackward && offset !== textContentSize) ||
							 | 
						||
| 
								 | 
							
								            (!isBackward && offset !== 0)
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            $removeSegment(focusNode, isBackward, offset);
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        } else if (anchorNode !== null && anchorNode.isSegmented()) {
							 | 
						||
| 
								 | 
							
								          const offset = anchor.offset;
							 | 
						||
| 
								 | 
							
								          const textContentSize = anchorNode.getTextContentSize();
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            anchorNode.is(focusNode) ||
							 | 
						||
| 
								 | 
							
								            (isBackward && offset !== 0) ||
							 | 
						||
| 
								 | 
							
								            (!isBackward && offset !== textContentSize)
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            $removeSegment(anchorNode, isBackward, offset);
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        $updateCaretSelectionForUnicodeCharacter(this, isBackward);
							 | 
						||
| 
								 | 
							
								      } else if (isBackward && anchor.offset === 0) {
							 | 
						||
| 
								 | 
							
								        // Special handling around rich text nodes
							 | 
						||
| 
								 | 
							
								        const element =
							 | 
						||
| 
								 | 
							
								          anchor.type === 'element'
							 | 
						||
| 
								 | 
							
								            ? anchor.getNode()
							 | 
						||
| 
								 | 
							
								            : anchor.getNode().getParentOrThrow();
							 | 
						||
| 
								 | 
							
								        if (element.collapseAtStart(this)) {
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.removeText();
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      isBackward &&
							 | 
						||
| 
								 | 
							
								      !wasCollapsed &&
							 | 
						||
| 
								 | 
							
								      this.isCollapsed() &&
							 | 
						||
| 
								 | 
							
								      this.anchor.type === 'element' &&
							 | 
						||
| 
								 | 
							
								      this.anchor.offset === 0
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const anchorNode = this.anchor.getNode();
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        anchorNode.isEmpty() &&
							 | 
						||
| 
								 | 
							
								        $isRootNode(anchorNode.getParent()) &&
							 | 
						||
| 
								 | 
							
								        anchorNode.getIndexWithinParent() === 0
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        anchorNode.collapseAtStart(this);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Performs one logical line deletion operation on the EditorState based on the current Selection.
							 | 
						||
| 
								 | 
							
								   * Handles different node types.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param isBackward whether or not the selection is backwards.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  deleteLine(isBackward: boolean): void {
							 | 
						||
| 
								 | 
							
								    if (this.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								      // Since `domSelection.modify('extend', ..., 'lineboundary')` works well for text selections
							 | 
						||
| 
								 | 
							
								      // but doesn't properly handle selections which end on elements, a space character is added
							 | 
						||
| 
								 | 
							
								      // for such selections transforming their anchor's type to 'text'
							 | 
						||
| 
								 | 
							
								      const anchorIsElement = this.anchor.type === 'element';
							 | 
						||
| 
								 | 
							
								      if (anchorIsElement) {
							 | 
						||
| 
								 | 
							
								        this.insertText(' ');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      this.modify('extend', isBackward, 'lineboundary');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // If selection is extended to cover text edge then extend it one character more
							 | 
						||
| 
								 | 
							
								      // to delete its parent element. Otherwise text content will be deleted but empty
							 | 
						||
| 
								 | 
							
								      // parent node will remain
							 | 
						||
| 
								 | 
							
								      const endPoint = isBackward ? this.focus : this.anchor;
							 | 
						||
| 
								 | 
							
								      if (endPoint.offset === 0) {
							 | 
						||
| 
								 | 
							
								        this.modify('extend', isBackward, 'character');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Adjusts selection to include an extra character added for element anchors to remove it
							 | 
						||
| 
								 | 
							
								      if (anchorIsElement) {
							 | 
						||
| 
								 | 
							
								        const startPoint = isBackward ? this.anchor : this.focus;
							 | 
						||
| 
								 | 
							
								        startPoint.set(startPoint.key, startPoint.offset + 1, startPoint.type);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.removeText();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Performs one logical word deletion operation on the EditorState based on the current Selection.
							 | 
						||
| 
								 | 
							
								   * Handles different node types.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param isBackward whether or not the selection is backwards.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  deleteWord(isBackward: boolean): void {
							 | 
						||
| 
								 | 
							
								    if (this.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								      const anchor = this.anchor;
							 | 
						||
| 
								 | 
							
								      const anchorNode: TextNode | ElementNode | null = anchor.getNode();
							 | 
						||
| 
								 | 
							
								      if (this.forwardDeletion(anchor, anchorNode, isBackward)) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.modify('extend', isBackward, 'word');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.removeText();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Returns whether the Selection is "backwards", meaning the focus
							 | 
						||
| 
								 | 
							
								   * logically precedes the anchor in the EditorState.
							 | 
						||
| 
								 | 
							
								   * @returns true if the Selection is backwards, false otherwise.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  isBackward(): boolean {
							 | 
						||
| 
								 | 
							
								    return this.focus.isBefore(this.anchor);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getStartEndPoints(): null | [PointType, PointType] {
							 | 
						||
| 
								 | 
							
								    return [this.anchor, this.focus];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isNodeSelection(x: unknown): x is NodeSelection {
							 | 
						||
| 
								 | 
							
								  return x instanceof NodeSelection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getCharacterOffset(point: PointType): number {
							 | 
						||
| 
								 | 
							
								  const offset = point.offset;
							 | 
						||
| 
								 | 
							
								  if (point.type === 'text') {
							 | 
						||
| 
								 | 
							
								    return offset;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const parent = point.getNode();
							 | 
						||
| 
								 | 
							
								  return offset === parent.getChildrenSize()
							 | 
						||
| 
								 | 
							
								    ? parent.getTextContent().length
							 | 
						||
| 
								 | 
							
								    : 0;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getCharacterOffsets(
							 | 
						||
| 
								 | 
							
								  selection: BaseSelection,
							 | 
						||
| 
								 | 
							
								): [number, number] {
							 | 
						||
| 
								 | 
							
								  const anchorAndFocus = selection.getStartEndPoints();
							 | 
						||
| 
								 | 
							
								  if (anchorAndFocus === null) {
							 | 
						||
| 
								 | 
							
								    return [0, 0];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const [anchor, focus] = anchorAndFocus;
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    anchor.type === 'element' &&
							 | 
						||
| 
								 | 
							
								    focus.type === 'element' &&
							 | 
						||
| 
								 | 
							
								    anchor.key === focus.key &&
							 | 
						||
| 
								 | 
							
								    anchor.offset === focus.offset
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    return [0, 0];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return [getCharacterOffset(anchor), getCharacterOffset(focus)];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $swapPoints(selection: RangeSelection): void {
							 | 
						||
| 
								 | 
							
								  const focus = selection.focus;
							 | 
						||
| 
								 | 
							
								  const anchor = selection.anchor;
							 | 
						||
| 
								 | 
							
								  const anchorKey = anchor.key;
							 | 
						||
| 
								 | 
							
								  const anchorOffset = anchor.offset;
							 | 
						||
| 
								 | 
							
								  const anchorType = anchor.type;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  $setPointValues(anchor, focus.key, focus.offset, focus.type);
							 | 
						||
| 
								 | 
							
								  $setPointValues(focus, anchorKey, anchorOffset, anchorType);
							 | 
						||
| 
								 | 
							
								  selection._cachedNodes = null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function moveNativeSelection(
							 | 
						||
| 
								 | 
							
								  domSelection: Selection,
							 | 
						||
| 
								 | 
							
								  alter: 'move' | 'extend',
							 | 
						||
| 
								 | 
							
								  direction: 'backward' | 'forward' | 'left' | 'right',
							 | 
						||
| 
								 | 
							
								  granularity: 'character' | 'word' | 'lineboundary',
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  // Selection.modify() method applies a change to the current selection or cursor position,
							 | 
						||
| 
								 | 
							
								  // but is still non-standard in some browsers.
							 | 
						||
| 
								 | 
							
								  domSelection.modify(alter, direction, granularity);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $updateCaretSelectionForUnicodeCharacter(
							 | 
						||
| 
								 | 
							
								  selection: RangeSelection,
							 | 
						||
| 
								 | 
							
								  isBackward: boolean,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const anchor = selection.anchor;
							 | 
						||
| 
								 | 
							
								  const focus = selection.focus;
							 | 
						||
| 
								 | 
							
								  const anchorNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								  const focusNode = focus.getNode();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    anchorNode === focusNode &&
							 | 
						||
| 
								 | 
							
								    anchor.type === 'text' &&
							 | 
						||
| 
								 | 
							
								    focus.type === 'text'
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    // Handling of multibyte characters
							 | 
						||
| 
								 | 
							
								    const anchorOffset = anchor.offset;
							 | 
						||
| 
								 | 
							
								    const focusOffset = focus.offset;
							 | 
						||
| 
								 | 
							
								    const isBefore = anchorOffset < focusOffset;
							 | 
						||
| 
								 | 
							
								    const startOffset = isBefore ? anchorOffset : focusOffset;
							 | 
						||
| 
								 | 
							
								    const endOffset = isBefore ? focusOffset : anchorOffset;
							 | 
						||
| 
								 | 
							
								    const characterOffset = endOffset - 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (startOffset !== characterOffset) {
							 | 
						||
| 
								 | 
							
								      const text = anchorNode.getTextContent().slice(startOffset, endOffset);
							 | 
						||
| 
								 | 
							
								      if (!doesContainGrapheme(text)) {
							 | 
						||
| 
								 | 
							
								        if (isBackward) {
							 | 
						||
| 
								 | 
							
								          focus.offset = characterOffset;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          anchor.offset = characterOffset;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    // TODO Handling of multibyte characters
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $removeSegment(
							 | 
						||
| 
								 | 
							
								  node: TextNode,
							 | 
						||
| 
								 | 
							
								  isBackward: boolean,
							 | 
						||
| 
								 | 
							
								  offset: number,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const textNode = node;
							 | 
						||
| 
								 | 
							
								  const textContent = textNode.getTextContent();
							 | 
						||
| 
								 | 
							
								  const split = textContent.split(/(?=\s)/g);
							 | 
						||
| 
								 | 
							
								  const splitLength = split.length;
							 | 
						||
| 
								 | 
							
								  let segmentOffset = 0;
							 | 
						||
| 
								 | 
							
								  let restoreOffset: number | undefined = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (let i = 0; i < splitLength; i++) {
							 | 
						||
| 
								 | 
							
								    const text = split[i];
							 | 
						||
| 
								 | 
							
								    const isLast = i === splitLength - 1;
							 | 
						||
| 
								 | 
							
								    restoreOffset = segmentOffset;
							 | 
						||
| 
								 | 
							
								    segmentOffset += text.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      (isBackward && segmentOffset === offset) ||
							 | 
						||
| 
								 | 
							
								      segmentOffset > offset ||
							 | 
						||
| 
								 | 
							
								      isLast
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      split.splice(i, 1);
							 | 
						||
| 
								 | 
							
								      if (isLast) {
							 | 
						||
| 
								 | 
							
								        restoreOffset = undefined;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const nextTextContent = split.join('').trim();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (nextTextContent === '') {
							 | 
						||
| 
								 | 
							
								    textNode.remove();
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    textNode.setTextContent(nextTextContent);
							 | 
						||
| 
								 | 
							
								    textNode.select(restoreOffset, restoreOffset);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function shouldResolveAncestor(
							 | 
						||
| 
								 | 
							
								  resolvedElement: ElementNode,
							 | 
						||
| 
								 | 
							
								  resolvedOffset: number,
							 | 
						||
| 
								 | 
							
								  lastPoint: null | PointType,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  const parent = resolvedElement.getParent();
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    lastPoint === null ||
							 | 
						||
| 
								 | 
							
								    parent === null ||
							 | 
						||
| 
								 | 
							
								    !parent.canBeEmpty() ||
							 | 
						||
| 
								 | 
							
								    parent !== lastPoint.getNode()
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $internalResolveSelectionPoint(
							 | 
						||
| 
								 | 
							
								  dom: Node,
							 | 
						||
| 
								 | 
							
								  offset: number,
							 | 
						||
| 
								 | 
							
								  lastPoint: null | PointType,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): null | PointType {
							 | 
						||
| 
								 | 
							
								  let resolvedOffset = offset;
							 | 
						||
| 
								 | 
							
								  let resolvedNode: TextNode | LexicalNode | null;
							 | 
						||
| 
								 | 
							
								  // If we have selection on an element, we will
							 | 
						||
| 
								 | 
							
								  // need to figure out (using the offset) what text
							 | 
						||
| 
								 | 
							
								  // node should be selected.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (dom.nodeType === DOM_ELEMENT_TYPE) {
							 | 
						||
| 
								 | 
							
								    // Resolve element to a ElementNode, or TextNode, or null
							 | 
						||
| 
								 | 
							
								    let moveSelectionToEnd = false;
							 | 
						||
| 
								 | 
							
								    // Given we're moving selection to another node, selection is
							 | 
						||
| 
								 | 
							
								    // definitely dirty.
							 | 
						||
| 
								 | 
							
								    // We use the anchor to find which child node to select
							 | 
						||
| 
								 | 
							
								    const childNodes = dom.childNodes;
							 | 
						||
| 
								 | 
							
								    const childNodesLength = childNodes.length;
							 | 
						||
| 
								 | 
							
								    const blockCursorElement = editor._blockCursorElement;
							 | 
						||
| 
								 | 
							
								    // If the anchor is the same as length, then this means we
							 | 
						||
| 
								 | 
							
								    // need to select the very last text node.
							 | 
						||
| 
								 | 
							
								    if (resolvedOffset === childNodesLength) {
							 | 
						||
| 
								 | 
							
								      moveSelectionToEnd = true;
							 | 
						||
| 
								 | 
							
								      resolvedOffset = childNodesLength - 1;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    let childDOM = childNodes[resolvedOffset];
							 | 
						||
| 
								 | 
							
								    let hasBlockCursor = false;
							 | 
						||
| 
								 | 
							
								    if (childDOM === blockCursorElement) {
							 | 
						||
| 
								 | 
							
								      childDOM = childNodes[resolvedOffset + 1];
							 | 
						||
| 
								 | 
							
								      hasBlockCursor = true;
							 | 
						||
| 
								 | 
							
								    } else if (blockCursorElement !== null) {
							 | 
						||
| 
								 | 
							
								      const blockCursorElementParent = blockCursorElement.parentNode;
							 | 
						||
| 
								 | 
							
								      if (dom === blockCursorElementParent) {
							 | 
						||
| 
								 | 
							
								        const blockCursorOffset = Array.prototype.indexOf.call(
							 | 
						||
| 
								 | 
							
								          blockCursorElementParent.children,
							 | 
						||
| 
								 | 
							
								          blockCursorElement,
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        if (offset > blockCursorOffset) {
							 | 
						||
| 
								 | 
							
								          resolvedOffset--;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    resolvedNode = $getNodeFromDOM(childDOM);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(resolvedNode)) {
							 | 
						||
| 
								 | 
							
								      resolvedOffset = getTextNodeOffset(resolvedNode, moveSelectionToEnd);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      let resolvedElement = $getNodeFromDOM(dom);
							 | 
						||
| 
								 | 
							
								      // Ensure resolvedElement is actually a element.
							 | 
						||
| 
								 | 
							
								      if (resolvedElement === null) {
							 | 
						||
| 
								 | 
							
								        return null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if ($isElementNode(resolvedElement)) {
							 | 
						||
| 
								 | 
							
								        resolvedOffset = Math.min(
							 | 
						||
| 
								 | 
							
								          resolvedElement.getChildrenSize(),
							 | 
						||
| 
								 | 
							
								          resolvedOffset,
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        let child = resolvedElement.getChildAtIndex(resolvedOffset);
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          $isElementNode(child) &&
							 | 
						||
| 
								 | 
							
								          shouldResolveAncestor(child, resolvedOffset, lastPoint)
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          const descendant = moveSelectionToEnd
							 | 
						||
| 
								 | 
							
								            ? child.getLastDescendant()
							 | 
						||
| 
								 | 
							
								            : child.getFirstDescendant();
							 | 
						||
| 
								 | 
							
								          if (descendant === null) {
							 | 
						||
| 
								 | 
							
								            resolvedElement = child;
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            child = descendant;
							 | 
						||
| 
								 | 
							
								            resolvedElement = $isElementNode(child)
							 | 
						||
| 
								 | 
							
								              ? child
							 | 
						||
| 
								 | 
							
								              : child.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          resolvedOffset = 0;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if ($isTextNode(child)) {
							 | 
						||
| 
								 | 
							
								          resolvedNode = child;
							 | 
						||
| 
								 | 
							
								          resolvedElement = null;
							 | 
						||
| 
								 | 
							
								          resolvedOffset = getTextNodeOffset(child, moveSelectionToEnd);
							 | 
						||
| 
								 | 
							
								        } else if (
							 | 
						||
| 
								 | 
							
								          child !== resolvedElement &&
							 | 
						||
| 
								 | 
							
								          moveSelectionToEnd &&
							 | 
						||
| 
								 | 
							
								          !hasBlockCursor
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          resolvedOffset++;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        const index = resolvedElement.getIndexWithinParent();
							 | 
						||
| 
								 | 
							
								        // When selecting decorators, there can be some selection issues when using resolvedOffset,
							 | 
						||
| 
								 | 
							
								        // and instead we should be checking if we're using the offset
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          offset === 0 &&
							 | 
						||
| 
								 | 
							
								          $isDecoratorNode(resolvedElement) &&
							 | 
						||
| 
								 | 
							
								          $getNodeFromDOM(dom) === resolvedElement
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          resolvedOffset = index;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          resolvedOffset = index + 1;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        resolvedElement = resolvedElement.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if ($isElementNode(resolvedElement)) {
							 | 
						||
| 
								 | 
							
								        return $createPoint(resolvedElement.__key, resolvedOffset, 'element');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    // TextNode or null
							 | 
						||
| 
								 | 
							
								    resolvedNode = $getNodeFromDOM(dom);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (!$isTextNode(resolvedNode)) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return $createPoint(resolvedNode.__key, resolvedOffset, 'text');
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function resolveSelectionPointOnBoundary(
							 | 
						||
| 
								 | 
							
								  point: TextPointType,
							 | 
						||
| 
								 | 
							
								  isBackward: boolean,
							 | 
						||
| 
								 | 
							
								  isCollapsed: boolean,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const offset = point.offset;
							 | 
						||
| 
								 | 
							
								  const node = point.getNode();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (offset === 0) {
							 | 
						||
| 
								 | 
							
								    const prevSibling = node.getPreviousSibling();
							 | 
						||
| 
								 | 
							
								    const parent = node.getParent();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!isBackward) {
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        $isElementNode(prevSibling) &&
							 | 
						||
| 
								 | 
							
								        !isCollapsed &&
							 | 
						||
| 
								 | 
							
								        prevSibling.isInline()
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        point.key = prevSibling.__key;
							 | 
						||
| 
								 | 
							
								        point.offset = prevSibling.getChildrenSize();
							 | 
						||
| 
								 | 
							
								        // @ts-expect-error: intentional
							 | 
						||
| 
								 | 
							
								        point.type = 'element';
							 | 
						||
| 
								 | 
							
								      } else if ($isTextNode(prevSibling)) {
							 | 
						||
| 
								 | 
							
								        point.key = prevSibling.__key;
							 | 
						||
| 
								 | 
							
								        point.offset = prevSibling.getTextContent().length;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else if (
							 | 
						||
| 
								 | 
							
								      (isCollapsed || !isBackward) &&
							 | 
						||
| 
								 | 
							
								      prevSibling === null &&
							 | 
						||
| 
								 | 
							
								      $isElementNode(parent) &&
							 | 
						||
| 
								 | 
							
								      parent.isInline()
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const parentSibling = parent.getPreviousSibling();
							 | 
						||
| 
								 | 
							
								      if ($isTextNode(parentSibling)) {
							 | 
						||
| 
								 | 
							
								        point.key = parentSibling.__key;
							 | 
						||
| 
								 | 
							
								        point.offset = parentSibling.getTextContent().length;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else if (offset === node.getTextContent().length) {
							 | 
						||
| 
								 | 
							
								    const nextSibling = node.getNextSibling();
							 | 
						||
| 
								 | 
							
								    const parent = node.getParent();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (isBackward && $isElementNode(nextSibling) && nextSibling.isInline()) {
							 | 
						||
| 
								 | 
							
								      point.key = nextSibling.__key;
							 | 
						||
| 
								 | 
							
								      point.offset = 0;
							 | 
						||
| 
								 | 
							
								      // @ts-expect-error: intentional
							 | 
						||
| 
								 | 
							
								      point.type = 'element';
							 | 
						||
| 
								 | 
							
								    } else if (
							 | 
						||
| 
								 | 
							
								      (isCollapsed || isBackward) &&
							 | 
						||
| 
								 | 
							
								      nextSibling === null &&
							 | 
						||
| 
								 | 
							
								      $isElementNode(parent) &&
							 | 
						||
| 
								 | 
							
								      parent.isInline() &&
							 | 
						||
| 
								 | 
							
								      !parent.canInsertTextAfter()
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const parentSibling = parent.getNextSibling();
							 | 
						||
| 
								 | 
							
								      if ($isTextNode(parentSibling)) {
							 | 
						||
| 
								 | 
							
								        point.key = parentSibling.__key;
							 | 
						||
| 
								 | 
							
								        point.offset = 0;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $normalizeSelectionPointsForBoundaries(
							 | 
						||
| 
								 | 
							
								  anchor: PointType,
							 | 
						||
| 
								 | 
							
								  focus: PointType,
							 | 
						||
| 
								 | 
							
								  lastSelection: null | BaseSelection,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  if (anchor.type === 'text' && focus.type === 'text') {
							 | 
						||
| 
								 | 
							
								    const isBackward = anchor.isBefore(focus);
							 | 
						||
| 
								 | 
							
								    const isCollapsed = anchor.is(focus);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Attempt to normalize the offset to the previous sibling if we're at the
							 | 
						||
| 
								 | 
							
								    // start of a text node and the sibling is a text node or inline element.
							 | 
						||
| 
								 | 
							
								    resolveSelectionPointOnBoundary(anchor, isBackward, isCollapsed);
							 | 
						||
| 
								 | 
							
								    resolveSelectionPointOnBoundary(focus, !isBackward, isCollapsed);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (isCollapsed) {
							 | 
						||
| 
								 | 
							
								      focus.key = anchor.key;
							 | 
						||
| 
								 | 
							
								      focus.offset = anchor.offset;
							 | 
						||
| 
								 | 
							
								      focus.type = anchor.type;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      editor.isComposing() &&
							 | 
						||
| 
								 | 
							
								      editor._compositionKey !== anchor.key &&
							 | 
						||
| 
								 | 
							
								      $isRangeSelection(lastSelection)
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const lastAnchor = lastSelection.anchor;
							 | 
						||
| 
								 | 
							
								      const lastFocus = lastSelection.focus;
							 | 
						||
| 
								 | 
							
								      $setPointValues(
							 | 
						||
| 
								 | 
							
								        anchor,
							 | 
						||
| 
								 | 
							
								        lastAnchor.key,
							 | 
						||
| 
								 | 
							
								        lastAnchor.offset,
							 | 
						||
| 
								 | 
							
								        lastAnchor.type,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								      $setPointValues(focus, lastFocus.key, lastFocus.offset, lastFocus.type);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $internalResolveSelectionPoints(
							 | 
						||
| 
								 | 
							
								  anchorDOM: null | Node,
							 | 
						||
| 
								 | 
							
								  anchorOffset: number,
							 | 
						||
| 
								 | 
							
								  focusDOM: null | Node,
							 | 
						||
| 
								 | 
							
								  focusOffset: number,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  lastSelection: null | BaseSelection,
							 | 
						||
| 
								 | 
							
								): null | [PointType, PointType] {
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    anchorDOM === null ||
							 | 
						||
| 
								 | 
							
								    focusDOM === null ||
							 | 
						||
| 
								 | 
							
								    !isSelectionWithinEditor(editor, anchorDOM, focusDOM)
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const resolvedAnchorPoint = $internalResolveSelectionPoint(
							 | 
						||
| 
								 | 
							
								    anchorDOM,
							 | 
						||
| 
								 | 
							
								    anchorOffset,
							 | 
						||
| 
								 | 
							
								    $isRangeSelection(lastSelection) ? lastSelection.anchor : null,
							 | 
						||
| 
								 | 
							
								    editor,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  if (resolvedAnchorPoint === null) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const resolvedFocusPoint = $internalResolveSelectionPoint(
							 | 
						||
| 
								 | 
							
								    focusDOM,
							 | 
						||
| 
								 | 
							
								    focusOffset,
							 | 
						||
| 
								 | 
							
								    $isRangeSelection(lastSelection) ? lastSelection.focus : null,
							 | 
						||
| 
								 | 
							
								    editor,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  if (resolvedFocusPoint === null) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    resolvedAnchorPoint.type === 'element' &&
							 | 
						||
| 
								 | 
							
								    resolvedFocusPoint.type === 'element'
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    const anchorNode = $getNodeFromDOM(anchorDOM);
							 | 
						||
| 
								 | 
							
								    const focusNode = $getNodeFromDOM(focusDOM);
							 | 
						||
| 
								 | 
							
								    // Ensure if we're selecting the content of a decorator that we
							 | 
						||
| 
								 | 
							
								    // return null for this point, as it's not in the controlled scope
							 | 
						||
| 
								 | 
							
								    // of Lexical.
							 | 
						||
| 
								 | 
							
								    if ($isDecoratorNode(anchorNode) && $isDecoratorNode(focusNode)) {
							 | 
						||
| 
								 | 
							
								      return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Handle normalization of selection when it is at the boundaries.
							 | 
						||
| 
								 | 
							
								  $normalizeSelectionPointsForBoundaries(
							 | 
						||
| 
								 | 
							
								    resolvedAnchorPoint,
							 | 
						||
| 
								 | 
							
								    resolvedFocusPoint,
							 | 
						||
| 
								 | 
							
								    lastSelection,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return [resolvedAnchorPoint, resolvedFocusPoint];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isBlockElementNode(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode | null | undefined,
							 | 
						||
| 
								 | 
							
								): node is ElementNode {
							 | 
						||
| 
								 | 
							
								  return $isElementNode(node) && !node.isInline();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This is used to make a selection when the existing
							 | 
						||
| 
								 | 
							
								// selection is null, i.e. forcing selection on the editor
							 | 
						||
| 
								 | 
							
								// when it current exists outside the editor.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $internalMakeRangeSelection(
							 | 
						||
| 
								 | 
							
								  anchorKey: NodeKey,
							 | 
						||
| 
								 | 
							
								  anchorOffset: number,
							 | 
						||
| 
								 | 
							
								  focusKey: NodeKey,
							 | 
						||
| 
								 | 
							
								  focusOffset: number,
							 | 
						||
| 
								 | 
							
								  anchorType: 'text' | 'element',
							 | 
						||
| 
								 | 
							
								  focusType: 'text' | 'element',
							 | 
						||
| 
								 | 
							
								): RangeSelection {
							 | 
						||
| 
								 | 
							
								  const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  const selection = new RangeSelection(
							 | 
						||
| 
								 | 
							
								    $createPoint(anchorKey, anchorOffset, anchorType),
							 | 
						||
| 
								 | 
							
								    $createPoint(focusKey, focusOffset, focusType),
							 | 
						||
| 
								 | 
							
								    0,
							 | 
						||
| 
								 | 
							
								    '',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  selection.dirty = true;
							 | 
						||
| 
								 | 
							
								  editorState._selection = selection;
							 | 
						||
| 
								 | 
							
								  return selection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $createRangeSelection(): RangeSelection {
							 | 
						||
| 
								 | 
							
								  const anchor = $createPoint('root', 0, 'element');
							 | 
						||
| 
								 | 
							
								  const focus = $createPoint('root', 0, 'element');
							 | 
						||
| 
								 | 
							
								  return new RangeSelection(anchor, focus, 0, '');
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $createNodeSelection(): NodeSelection {
							 | 
						||
| 
								 | 
							
								  return new NodeSelection(new Set());
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $internalCreateSelection(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): null | BaseSelection {
							 | 
						||
| 
								 | 
							
								  const currentEditorState = editor.getEditorState();
							 | 
						||
| 
								 | 
							
								  const lastSelection = currentEditorState._selection;
							 | 
						||
| 
								 | 
							
								  const domSelection = getDOMSelection(editor._window);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if ($isRangeSelection(lastSelection) || lastSelection == null) {
							 | 
						||
| 
								 | 
							
								    return $internalCreateRangeSelection(
							 | 
						||
| 
								 | 
							
								      lastSelection,
							 | 
						||
| 
								 | 
							
								      domSelection,
							 | 
						||
| 
								 | 
							
								      editor,
							 | 
						||
| 
								 | 
							
								      null,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return lastSelection.clone();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $createRangeSelectionFromDom(
							 | 
						||
| 
								 | 
							
								  domSelection: Selection | null,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): null | RangeSelection {
							 | 
						||
| 
								 | 
							
								  return $internalCreateRangeSelection(null, domSelection, editor, null);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $internalCreateRangeSelection(
							 | 
						||
| 
								 | 
							
								  lastSelection: null | BaseSelection,
							 | 
						||
| 
								 | 
							
								  domSelection: Selection | null,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  event: UIEvent | Event | null,
							 | 
						||
| 
								 | 
							
								): null | RangeSelection {
							 | 
						||
| 
								 | 
							
								  const windowObj = editor._window;
							 | 
						||
| 
								 | 
							
								  if (windowObj === null) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // When we create a selection, we try to use the previous
							 | 
						||
| 
								 | 
							
								  // selection where possible, unless an actual user selection
							 | 
						||
| 
								 | 
							
								  // change has occurred. When we do need to create a new selection
							 | 
						||
| 
								 | 
							
								  // we validate we can have text nodes for both anchor and focus
							 | 
						||
| 
								 | 
							
								  // nodes. If that holds true, we then return that selection
							 | 
						||
| 
								 | 
							
								  // as a mutable object that we use for the editor state for this
							 | 
						||
| 
								 | 
							
								  // update cycle. If a selection gets changed, and requires a
							 | 
						||
| 
								 | 
							
								  // update to native DOM selection, it gets marked as "dirty".
							 | 
						||
| 
								 | 
							
								  // If the selection changes, but matches with the existing
							 | 
						||
| 
								 | 
							
								  // DOM selection, then we only need to sync it. Otherwise,
							 | 
						||
| 
								 | 
							
								  // we generally bail out of doing an update to selection during
							 | 
						||
| 
								 | 
							
								  // reconciliation unless there are dirty nodes that need
							 | 
						||
| 
								 | 
							
								  // reconciling.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const windowEvent = event || windowObj.event;
							 | 
						||
| 
								 | 
							
								  const eventType = windowEvent ? windowEvent.type : undefined;
							 | 
						||
| 
								 | 
							
								  const isSelectionChange = eventType === 'selectionchange';
							 | 
						||
| 
								 | 
							
								  const useDOMSelection =
							 | 
						||
| 
								 | 
							
								    !getIsProcessingMutations() &&
							 | 
						||
| 
								 | 
							
								    (isSelectionChange ||
							 | 
						||
| 
								 | 
							
								      eventType === 'beforeinput' ||
							 | 
						||
| 
								 | 
							
								      eventType === 'compositionstart' ||
							 | 
						||
| 
								 | 
							
								      eventType === 'compositionend' ||
							 | 
						||
| 
								 | 
							
								      (eventType === 'click' &&
							 | 
						||
| 
								 | 
							
								        windowEvent &&
							 | 
						||
| 
								 | 
							
								        (windowEvent as InputEvent).detail === 3) ||
							 | 
						||
| 
								 | 
							
								      eventType === 'drop' ||
							 | 
						||
| 
								 | 
							
								      eventType === undefined);
							 | 
						||
| 
								 | 
							
								  let anchorDOM, focusDOM, anchorOffset, focusOffset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!$isRangeSelection(lastSelection) || useDOMSelection) {
							 | 
						||
| 
								 | 
							
								    if (domSelection === null) {
							 | 
						||
| 
								 | 
							
								      return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    anchorDOM = domSelection.anchorNode;
							 | 
						||
| 
								 | 
							
								    focusDOM = domSelection.focusNode;
							 | 
						||
| 
								 | 
							
								    anchorOffset = domSelection.anchorOffset;
							 | 
						||
| 
								 | 
							
								    focusOffset = domSelection.focusOffset;
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      isSelectionChange &&
							 | 
						||
| 
								 | 
							
								      $isRangeSelection(lastSelection) &&
							 | 
						||
| 
								 | 
							
								      !isSelectionWithinEditor(editor, anchorDOM, focusDOM)
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      return lastSelection.clone();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    return lastSelection.clone();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // Let's resolve the text nodes from the offsets and DOM nodes we have from
							 | 
						||
| 
								 | 
							
								  // native selection.
							 | 
						||
| 
								 | 
							
								  const resolvedSelectionPoints = $internalResolveSelectionPoints(
							 | 
						||
| 
								 | 
							
								    anchorDOM,
							 | 
						||
| 
								 | 
							
								    anchorOffset,
							 | 
						||
| 
								 | 
							
								    focusDOM,
							 | 
						||
| 
								 | 
							
								    focusOffset,
							 | 
						||
| 
								 | 
							
								    editor,
							 | 
						||
| 
								 | 
							
								    lastSelection,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  if (resolvedSelectionPoints === null) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const [resolvedAnchorPoint, resolvedFocusPoint] = resolvedSelectionPoints;
							 | 
						||
| 
								 | 
							
								  return new RangeSelection(
							 | 
						||
| 
								 | 
							
								    resolvedAnchorPoint,
							 | 
						||
| 
								 | 
							
								    resolvedFocusPoint,
							 | 
						||
| 
								 | 
							
								    !$isRangeSelection(lastSelection) ? 0 : lastSelection.format,
							 | 
						||
| 
								 | 
							
								    !$isRangeSelection(lastSelection) ? '' : lastSelection.style,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getSelection(): null | BaseSelection {
							 | 
						||
| 
								 | 
							
								  const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  return editorState._selection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getPreviousSelection(): null | BaseSelection {
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  return editor._editorState._selection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $updateElementSelectionOnCreateDeleteNode(
							 | 
						||
| 
								 | 
							
								  selection: RangeSelection,
							 | 
						||
| 
								 | 
							
								  parentNode: LexicalNode,
							 | 
						||
| 
								 | 
							
								  nodeOffset: number,
							 | 
						||
| 
								 | 
							
								  times = 1,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const anchor = selection.anchor;
							 | 
						||
| 
								 | 
							
								  const focus = selection.focus;
							 | 
						||
| 
								 | 
							
								  const anchorNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								  const focusNode = focus.getNode();
							 | 
						||
| 
								 | 
							
								  if (!parentNode.is(anchorNode) && !parentNode.is(focusNode)) {
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const parentKey = parentNode.__key;
							 | 
						||
| 
								 | 
							
								  // Single node. We shift selection but never redimension it
							 | 
						||
| 
								 | 
							
								  if (selection.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								    const selectionOffset = anchor.offset;
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      (nodeOffset <= selectionOffset && times > 0) ||
							 | 
						||
| 
								 | 
							
								      (nodeOffset < selectionOffset && times < 0)
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const newSelectionOffset = Math.max(0, selectionOffset + times);
							 | 
						||
| 
								 | 
							
								      anchor.set(parentKey, newSelectionOffset, 'element');
							 | 
						||
| 
								 | 
							
								      focus.set(parentKey, newSelectionOffset, 'element');
							 | 
						||
| 
								 | 
							
								      // The new selection might point to text nodes, try to resolve them
							 | 
						||
| 
								 | 
							
								      $updateSelectionResolveTextNodes(selection);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    // Multiple nodes selected. We shift or redimension selection
							 | 
						||
| 
								 | 
							
								    const isBackward = selection.isBackward();
							 | 
						||
| 
								 | 
							
								    const firstPoint = isBackward ? focus : anchor;
							 | 
						||
| 
								 | 
							
								    const firstPointNode = firstPoint.getNode();
							 | 
						||
| 
								 | 
							
								    const lastPoint = isBackward ? anchor : focus;
							 | 
						||
| 
								 | 
							
								    const lastPointNode = lastPoint.getNode();
							 | 
						||
| 
								 | 
							
								    if (parentNode.is(firstPointNode)) {
							 | 
						||
| 
								 | 
							
								      const firstPointOffset = firstPoint.offset;
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        (nodeOffset <= firstPointOffset && times > 0) ||
							 | 
						||
| 
								 | 
							
								        (nodeOffset < firstPointOffset && times < 0)
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        firstPoint.set(
							 | 
						||
| 
								 | 
							
								          parentKey,
							 | 
						||
| 
								 | 
							
								          Math.max(0, firstPointOffset + times),
							 | 
						||
| 
								 | 
							
								          'element',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (parentNode.is(lastPointNode)) {
							 | 
						||
| 
								 | 
							
								      const lastPointOffset = lastPoint.offset;
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        (nodeOffset <= lastPointOffset && times > 0) ||
							 | 
						||
| 
								 | 
							
								        (nodeOffset < lastPointOffset && times < 0)
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        lastPoint.set(
							 | 
						||
| 
								 | 
							
								          parentKey,
							 | 
						||
| 
								 | 
							
								          Math.max(0, lastPointOffset + times),
							 | 
						||
| 
								 | 
							
								          'element',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // The new selection might point to text nodes, try to resolve them
							 | 
						||
| 
								 | 
							
								  $updateSelectionResolveTextNodes(selection);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $updateSelectionResolveTextNodes(selection: RangeSelection): void {
							 | 
						||
| 
								 | 
							
								  const anchor = selection.anchor;
							 | 
						||
| 
								 | 
							
								  const anchorOffset = anchor.offset;
							 | 
						||
| 
								 | 
							
								  const focus = selection.focus;
							 | 
						||
| 
								 | 
							
								  const focusOffset = focus.offset;
							 | 
						||
| 
								 | 
							
								  const anchorNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								  const focusNode = focus.getNode();
							 | 
						||
| 
								 | 
							
								  if (selection.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								    if (!$isElementNode(anchorNode)) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const childSize = anchorNode.getChildrenSize();
							 | 
						||
| 
								 | 
							
								    const anchorOffsetAtEnd = anchorOffset >= childSize;
							 | 
						||
| 
								 | 
							
								    const child = anchorOffsetAtEnd
							 | 
						||
| 
								 | 
							
								      ? anchorNode.getChildAtIndex(childSize - 1)
							 | 
						||
| 
								 | 
							
								      : anchorNode.getChildAtIndex(anchorOffset);
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(child)) {
							 | 
						||
| 
								 | 
							
								      let newOffset = 0;
							 | 
						||
| 
								 | 
							
								      if (anchorOffsetAtEnd) {
							 | 
						||
| 
								 | 
							
								        newOffset = child.getTextContentSize();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      anchor.set(child.__key, newOffset, 'text');
							 | 
						||
| 
								 | 
							
								      focus.set(child.__key, newOffset, 'text');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if ($isElementNode(anchorNode)) {
							 | 
						||
| 
								 | 
							
								    const childSize = anchorNode.getChildrenSize();
							 | 
						||
| 
								 | 
							
								    const anchorOffsetAtEnd = anchorOffset >= childSize;
							 | 
						||
| 
								 | 
							
								    const child = anchorOffsetAtEnd
							 | 
						||
| 
								 | 
							
								      ? anchorNode.getChildAtIndex(childSize - 1)
							 | 
						||
| 
								 | 
							
								      : anchorNode.getChildAtIndex(anchorOffset);
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(child)) {
							 | 
						||
| 
								 | 
							
								      let newOffset = 0;
							 | 
						||
| 
								 | 
							
								      if (anchorOffsetAtEnd) {
							 | 
						||
| 
								 | 
							
								        newOffset = child.getTextContentSize();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      anchor.set(child.__key, newOffset, 'text');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if ($isElementNode(focusNode)) {
							 | 
						||
| 
								 | 
							
								    const childSize = focusNode.getChildrenSize();
							 | 
						||
| 
								 | 
							
								    const focusOffsetAtEnd = focusOffset >= childSize;
							 | 
						||
| 
								 | 
							
								    const child = focusOffsetAtEnd
							 | 
						||
| 
								 | 
							
								      ? focusNode.getChildAtIndex(childSize - 1)
							 | 
						||
| 
								 | 
							
								      : focusNode.getChildAtIndex(focusOffset);
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(child)) {
							 | 
						||
| 
								 | 
							
								      let newOffset = 0;
							 | 
						||
| 
								 | 
							
								      if (focusOffsetAtEnd) {
							 | 
						||
| 
								 | 
							
								        newOffset = child.getTextContentSize();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      focus.set(child.__key, newOffset, 'text');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function applySelectionTransforms(
							 | 
						||
| 
								 | 
							
								  nextEditorState: EditorState,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const prevEditorState = editor.getEditorState();
							 | 
						||
| 
								 | 
							
								  const prevSelection = prevEditorState._selection;
							 | 
						||
| 
								 | 
							
								  const nextSelection = nextEditorState._selection;
							 | 
						||
| 
								 | 
							
								  if ($isRangeSelection(nextSelection)) {
							 | 
						||
| 
								 | 
							
								    const anchor = nextSelection.anchor;
							 | 
						||
| 
								 | 
							
								    const focus = nextSelection.focus;
							 | 
						||
| 
								 | 
							
								    let anchorNode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (anchor.type === 'text') {
							 | 
						||
| 
								 | 
							
								      anchorNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								      anchorNode.selectionTransform(prevSelection, nextSelection);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (focus.type === 'text') {
							 | 
						||
| 
								 | 
							
								      const focusNode = focus.getNode();
							 | 
						||
| 
								 | 
							
								      if (anchorNode !== focusNode) {
							 | 
						||
| 
								 | 
							
								        focusNode.selectionTransform(prevSelection, nextSelection);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function moveSelectionPointToSibling(
							 | 
						||
| 
								 | 
							
								  point: PointType,
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  parent: ElementNode,
							 | 
						||
| 
								 | 
							
								  prevSibling: LexicalNode | null,
							 | 
						||
| 
								 | 
							
								  nextSibling: LexicalNode | null,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  let siblingKey = null;
							 | 
						||
| 
								 | 
							
								  let offset = 0;
							 | 
						||
| 
								 | 
							
								  let type: 'text' | 'element' | null = null;
							 | 
						||
| 
								 | 
							
								  if (prevSibling !== null) {
							 | 
						||
| 
								 | 
							
								    siblingKey = prevSibling.__key;
							 | 
						||
| 
								 | 
							
								    if ($isTextNode(prevSibling)) {
							 | 
						||
| 
								 | 
							
								      offset = prevSibling.getTextContentSize();
							 | 
						||
| 
								 | 
							
								      type = 'text';
							 | 
						||
| 
								 | 
							
								    } else if ($isElementNode(prevSibling)) {
							 | 
						||
| 
								 | 
							
								      offset = prevSibling.getChildrenSize();
							 | 
						||
| 
								 | 
							
								      type = 'element';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    if (nextSibling !== null) {
							 | 
						||
| 
								 | 
							
								      siblingKey = nextSibling.__key;
							 | 
						||
| 
								 | 
							
								      if ($isTextNode(nextSibling)) {
							 | 
						||
| 
								 | 
							
								        type = 'text';
							 | 
						||
| 
								 | 
							
								      } else if ($isElementNode(nextSibling)) {
							 | 
						||
| 
								 | 
							
								        type = 'element';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (siblingKey !== null && type !== null) {
							 | 
						||
| 
								 | 
							
								    point.set(siblingKey, offset, type);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    offset = node.getIndexWithinParent();
							 | 
						||
| 
								 | 
							
								    if (offset === -1) {
							 | 
						||
| 
								 | 
							
								      // Move selection to end of parent
							 | 
						||
| 
								 | 
							
								      offset = parent.getChildrenSize();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    point.set(parent.__key, offset, 'element');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function adjustPointOffsetForMergedSibling(
							 | 
						||
| 
								 | 
							
								  point: PointType,
							 | 
						||
| 
								 | 
							
								  isBefore: boolean,
							 | 
						||
| 
								 | 
							
								  key: NodeKey,
							 | 
						||
| 
								 | 
							
								  target: TextNode,
							 | 
						||
| 
								 | 
							
								  textLength: number,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  if (point.type === 'text') {
							 | 
						||
| 
								 | 
							
								    point.key = key;
							 | 
						||
| 
								 | 
							
								    if (!isBefore) {
							 | 
						||
| 
								 | 
							
								      point.offset += textLength;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else if (point.offset > target.getIndexWithinParent()) {
							 | 
						||
| 
								 | 
							
								    point.offset -= 1;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function updateDOMSelection(
							 | 
						||
| 
								 | 
							
								  prevSelection: BaseSelection | null,
							 | 
						||
| 
								 | 
							
								  nextSelection: BaseSelection | null,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  domSelection: Selection,
							 | 
						||
| 
								 | 
							
								  tags: Set<string>,
							 | 
						||
| 
								 | 
							
								  rootElement: HTMLElement,
							 | 
						||
| 
								 | 
							
								  nodeCount: number,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const anchorDOMNode = domSelection.anchorNode;
							 | 
						||
| 
								 | 
							
								  const focusDOMNode = domSelection.focusNode;
							 | 
						||
| 
								 | 
							
								  const anchorOffset = domSelection.anchorOffset;
							 | 
						||
| 
								 | 
							
								  const focusOffset = domSelection.focusOffset;
							 | 
						||
| 
								 | 
							
								  const activeElement = document.activeElement;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // TODO: make this not hard-coded, and add another config option
							 | 
						||
| 
								 | 
							
								  // that makes this configurable.
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    (tags.has('collaboration') && activeElement !== rootElement) ||
							 | 
						||
| 
								 | 
							
								    (activeElement !== null &&
							 | 
						||
| 
								 | 
							
								      isSelectionCapturedInDecoratorInput(activeElement))
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!$isRangeSelection(nextSelection)) {
							 | 
						||
| 
								 | 
							
								    // We don't remove selection if the prevSelection is null because
							 | 
						||
| 
								 | 
							
								    // of editor.setRootElement(). If this occurs on init when the
							 | 
						||
| 
								 | 
							
								    // editor is already focused, then this can cause the editor to
							 | 
						||
| 
								 | 
							
								    // lose focus.
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      prevSelection !== null &&
							 | 
						||
| 
								 | 
							
								      isSelectionWithinEditor(editor, anchorDOMNode, focusDOMNode)
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      domSelection.removeAllRanges();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const anchor = nextSelection.anchor;
							 | 
						||
| 
								 | 
							
								  const focus = nextSelection.focus;
							 | 
						||
| 
								 | 
							
								  const anchorKey = anchor.key;
							 | 
						||
| 
								 | 
							
								  const focusKey = focus.key;
							 | 
						||
| 
								 | 
							
								  const anchorDOM = getElementByKeyOrThrow(editor, anchorKey);
							 | 
						||
| 
								 | 
							
								  const focusDOM = getElementByKeyOrThrow(editor, focusKey);
							 | 
						||
| 
								 | 
							
								  const nextAnchorOffset = anchor.offset;
							 | 
						||
| 
								 | 
							
								  const nextFocusOffset = focus.offset;
							 | 
						||
| 
								 | 
							
								  const nextFormat = nextSelection.format;
							 | 
						||
| 
								 | 
							
								  const nextStyle = nextSelection.style;
							 | 
						||
| 
								 | 
							
								  const isCollapsed = nextSelection.isCollapsed();
							 | 
						||
| 
								 | 
							
								  let nextAnchorNode: HTMLElement | Text | null = anchorDOM;
							 | 
						||
| 
								 | 
							
								  let nextFocusNode: HTMLElement | Text | null = focusDOM;
							 | 
						||
| 
								 | 
							
								  let anchorFormatOrStyleChanged = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (anchor.type === 'text') {
							 | 
						||
| 
								 | 
							
								    nextAnchorNode = getDOMTextNode(anchorDOM);
							 | 
						||
| 
								 | 
							
								    const anchorNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								    anchorFormatOrStyleChanged =
							 | 
						||
| 
								 | 
							
								      anchorNode.getFormat() !== nextFormat ||
							 | 
						||
| 
								 | 
							
								      anchorNode.getStyle() !== nextStyle;
							 | 
						||
| 
								 | 
							
								  } else if (
							 | 
						||
| 
								 | 
							
								    $isRangeSelection(prevSelection) &&
							 | 
						||
| 
								 | 
							
								    prevSelection.anchor.type === 'text'
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    anchorFormatOrStyleChanged = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (focus.type === 'text') {
							 | 
						||
| 
								 | 
							
								    nextFocusNode = getDOMTextNode(focusDOM);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // If we can't get an underlying text node for selection, then
							 | 
						||
| 
								 | 
							
								  // we should avoid setting selection to something incorrect.
							 | 
						||
| 
								 | 
							
								  if (nextAnchorNode === null || nextFocusNode === null) {
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    isCollapsed &&
							 | 
						||
| 
								 | 
							
								    (prevSelection === null ||
							 | 
						||
| 
								 | 
							
								      anchorFormatOrStyleChanged ||
							 | 
						||
| 
								 | 
							
								      ($isRangeSelection(prevSelection) &&
							 | 
						||
| 
								 | 
							
								        (prevSelection.format !== nextFormat ||
							 | 
						||
| 
								 | 
							
								          prevSelection.style !== nextStyle)))
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    markCollapsedSelectionFormat(
							 | 
						||
| 
								 | 
							
								      nextFormat,
							 | 
						||
| 
								 | 
							
								      nextStyle,
							 | 
						||
| 
								 | 
							
								      nextAnchorOffset,
							 | 
						||
| 
								 | 
							
								      anchorKey,
							 | 
						||
| 
								 | 
							
								      performance.now(),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Diff against the native DOM selection to ensure we don't do
							 | 
						||
| 
								 | 
							
								  // an unnecessary selection update. We also skip this check if
							 | 
						||
| 
								 | 
							
								  // we're moving selection to within an element, as this can
							 | 
						||
| 
								 | 
							
								  // sometimes be problematic around scrolling.
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    anchorOffset === nextAnchorOffset &&
							 | 
						||
| 
								 | 
							
								    focusOffset === nextFocusOffset &&
							 | 
						||
| 
								 | 
							
								    anchorDOMNode === nextAnchorNode &&
							 | 
						||
| 
								 | 
							
								    focusDOMNode === nextFocusNode && // Badly interpreted range selection when collapsed - #1482
							 | 
						||
| 
								 | 
							
								    !(domSelection.type === 'Range' && isCollapsed)
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    // If the root element does not have focus, ensure it has focus
							 | 
						||
| 
								 | 
							
								    if (activeElement === null || !rootElement.contains(activeElement)) {
							 | 
						||
| 
								 | 
							
								      rootElement.focus({
							 | 
						||
| 
								 | 
							
								        preventScroll: true,
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (anchor.type !== 'element') {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Apply the updated selection to the DOM. Note: this will trigger
							 | 
						||
| 
								 | 
							
								  // a "selectionchange" event, although it will be asynchronous.
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    domSelection.setBaseAndExtent(
							 | 
						||
| 
								 | 
							
								      nextAnchorNode,
							 | 
						||
| 
								 | 
							
								      nextAnchorOffset,
							 | 
						||
| 
								 | 
							
								      nextFocusNode,
							 | 
						||
| 
								 | 
							
								      nextFocusOffset,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  } catch (error) {
							 | 
						||
| 
								 | 
							
								    // If we encounter an error, continue. This can sometimes
							 | 
						||
| 
								 | 
							
								    // occur with FF and there's no good reason as to why it
							 | 
						||
| 
								 | 
							
								    // should happen.
							 | 
						||
| 
								 | 
							
								    if (__DEV__) {
							 | 
						||
| 
								 | 
							
								      console.warn(error);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    !tags.has('skip-scroll-into-view') &&
							 | 
						||
| 
								 | 
							
								    nextSelection.isCollapsed() &&
							 | 
						||
| 
								 | 
							
								    rootElement !== null &&
							 | 
						||
| 
								 | 
							
								    rootElement === document.activeElement
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    const selectionTarget: null | Range | HTMLElement | Text =
							 | 
						||
| 
								 | 
							
								      nextSelection instanceof RangeSelection &&
							 | 
						||
| 
								 | 
							
								      nextSelection.anchor.type === 'element'
							 | 
						||
| 
								 | 
							
								        ? (nextAnchorNode.childNodes[nextAnchorOffset] as HTMLElement | Text) ||
							 | 
						||
| 
								 | 
							
								          null
							 | 
						||
| 
								 | 
							
								        : domSelection.rangeCount > 0
							 | 
						||
| 
								 | 
							
								        ? domSelection.getRangeAt(0)
							 | 
						||
| 
								 | 
							
								        : null;
							 | 
						||
| 
								 | 
							
								    if (selectionTarget !== null) {
							 | 
						||
| 
								 | 
							
								      let selectionRect: DOMRect;
							 | 
						||
| 
								 | 
							
								      if (selectionTarget instanceof Text) {
							 | 
						||
| 
								 | 
							
								        const range = document.createRange();
							 | 
						||
| 
								 | 
							
								        range.selectNode(selectionTarget);
							 | 
						||
| 
								 | 
							
								        selectionRect = range.getBoundingClientRect();
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        selectionRect = selectionTarget.getBoundingClientRect();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      scrollIntoViewIfNeeded(editor, selectionRect, rootElement);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  markSelectionChangeFromDOMUpdate();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $insertNodes(nodes: Array<LexicalNode>) {
							 | 
						||
| 
								 | 
							
								  let selection = $getSelection() || $getPreviousSelection();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (selection === null) {
							 | 
						||
| 
								 | 
							
								    selection = $getRoot().selectEnd();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  selection.insertNodes(nodes);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getTextContent(): string {
							 | 
						||
| 
								 | 
							
								  const selection = $getSelection();
							 | 
						||
| 
								 | 
							
								  if (selection === null) {
							 | 
						||
| 
								 | 
							
								    return '';
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return selection.getTextContent();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $removeTextAndSplitBlock(selection: RangeSelection): number {
							 | 
						||
| 
								 | 
							
								  let selection_ = selection;
							 | 
						||
| 
								 | 
							
								  if (!selection.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								    selection_.removeText();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // A new selection can originate as a result of node replacement, in which case is registered via
							 | 
						||
| 
								 | 
							
								  // $setSelection
							 | 
						||
| 
								 | 
							
								  const newSelection = $getSelection();
							 | 
						||
| 
								 | 
							
								  if ($isRangeSelection(newSelection)) {
							 | 
						||
| 
								 | 
							
								    selection_ = newSelection;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  invariant(
							 | 
						||
| 
								 | 
							
								    $isRangeSelection(selection_),
							 | 
						||
| 
								 | 
							
								    'Unexpected dirty selection to be null',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const anchor = selection_.anchor;
							 | 
						||
| 
								 | 
							
								  let node = anchor.getNode();
							 | 
						||
| 
								 | 
							
								  let offset = anchor.offset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (!INTERNAL_$isBlock(node)) {
							 | 
						||
| 
								 | 
							
								    [node, offset] = $splitNodeAtPoint(node, offset);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return offset;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $splitNodeAtPoint(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  offset: number,
							 | 
						||
| 
								 | 
							
								): [parent: ElementNode, offset: number] {
							 | 
						||
| 
								 | 
							
								  const parent = node.getParent();
							 | 
						||
| 
								 | 
							
								  if (!parent) {
							 | 
						||
| 
								 | 
							
								    const paragraph = $createParagraphNode();
							 | 
						||
| 
								 | 
							
								    $getRoot().append(paragraph);
							 | 
						||
| 
								 | 
							
								    paragraph.select();
							 | 
						||
| 
								 | 
							
								    return [$getRoot(), 0];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if ($isTextNode(node)) {
							 | 
						||
| 
								 | 
							
								    const split = node.splitText(offset);
							 | 
						||
| 
								 | 
							
								    if (split.length === 0) {
							 | 
						||
| 
								 | 
							
								      return [parent, node.getIndexWithinParent()];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const x = offset === 0 ? 0 : 1;
							 | 
						||
| 
								 | 
							
								    const index = split[0].getIndexWithinParent() + x;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return [parent, index];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!$isElementNode(node) || offset === 0) {
							 | 
						||
| 
								 | 
							
								    return [parent, node.getIndexWithinParent()];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const firstToAppend = node.getChildAtIndex(offset);
							 | 
						||
| 
								 | 
							
								  if (firstToAppend) {
							 | 
						||
| 
								 | 
							
								    const insertPoint = new RangeSelection(
							 | 
						||
| 
								 | 
							
								      $createPoint(node.__key, offset, 'element'),
							 | 
						||
| 
								 | 
							
								      $createPoint(node.__key, offset, 'element'),
							 | 
						||
| 
								 | 
							
								      0,
							 | 
						||
| 
								 | 
							
								      '',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    const newElement = node.insertNewAfter(insertPoint) as ElementNode | null;
							 | 
						||
| 
								 | 
							
								    if (newElement) {
							 | 
						||
| 
								 | 
							
								      newElement.append(firstToAppend, ...firstToAppend.getNextSiblings());
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return [parent, node.getIndexWithinParent() + 1];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $wrapInlineNodes(nodes: LexicalNode[]) {
							 | 
						||
| 
								 | 
							
								  // We temporarily insert the topLevelNodes into an arbitrary ElementNode,
							 | 
						||
| 
								 | 
							
								  // since insertAfter does not work on nodes that have no parent (TO-DO: fix that).
							 | 
						||
| 
								 | 
							
								  const virtualRoot = $createParagraphNode();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let currentBlock = null;
							 | 
						||
| 
								 | 
							
								  for (let i = 0; i < nodes.length; i++) {
							 | 
						||
| 
								 | 
							
								    const node = nodes[i];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const isLineBreakNode = $isLineBreakNode(node);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      isLineBreakNode ||
							 | 
						||
| 
								 | 
							
								      ($isDecoratorNode(node) && node.isInline()) ||
							 | 
						||
| 
								 | 
							
								      ($isElementNode(node) && node.isInline()) ||
							 | 
						||
| 
								 | 
							
								      $isTextNode(node) ||
							 | 
						||
| 
								 | 
							
								      node.isParentRequired()
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      if (currentBlock === null) {
							 | 
						||
| 
								 | 
							
								        currentBlock = node.createParentElementNode();
							 | 
						||
| 
								 | 
							
								        virtualRoot.append(currentBlock);
							 | 
						||
| 
								 | 
							
								        // In the case of LineBreakNode, we just need to
							 | 
						||
| 
								 | 
							
								        // add an empty ParagraphNode to the topLevelBlocks.
							 | 
						||
| 
								 | 
							
								        if (isLineBreakNode) {
							 | 
						||
| 
								 | 
							
								          continue;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (currentBlock !== null) {
							 | 
						||
| 
								 | 
							
								        currentBlock.append(node);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      virtualRoot.append(node);
							 | 
						||
| 
								 | 
							
								      currentBlock = null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return virtualRoot;
							 | 
						||
| 
								 | 
							
								}
							 |