1789 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
		
		
			
		
	
	
			1789 lines
		
	
	
		
			48 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 {
							 | 
						||
| 
								 | 
							
								  CommandPayloadType,
							 | 
						||
| 
								 | 
							
								  EditorConfig,
							 | 
						||
| 
								 | 
							
								  EditorThemeClasses,
							 | 
						||
| 
								 | 
							
								  Klass,
							 | 
						||
| 
								 | 
							
								  LexicalCommand,
							 | 
						||
| 
								 | 
							
								  MutatedNodes,
							 | 
						||
| 
								 | 
							
								  MutationListeners,
							 | 
						||
| 
								 | 
							
								  NodeMutation,
							 | 
						||
| 
								 | 
							
								  RegisteredNode,
							 | 
						||
| 
								 | 
							
								  RegisteredNodes,
							 | 
						||
| 
								 | 
							
								  Spread,
							 | 
						||
| 
								 | 
							
								} from './LexicalEditor';
							 | 
						||
| 
								 | 
							
								import type {EditorState} from './LexicalEditorState';
							 | 
						||
| 
								 | 
							
								import type {LexicalNode, NodeKey, NodeMap} from './LexicalNode';
							 | 
						||
| 
								 | 
							
								import type {
							 | 
						||
| 
								 | 
							
								  BaseSelection,
							 | 
						||
| 
								 | 
							
								  PointType,
							 | 
						||
| 
								 | 
							
								  RangeSelection,
							 | 
						||
| 
								 | 
							
								} from './LexicalSelection';
							 | 
						||
| 
								 | 
							
								import type {RootNode} from './nodes/LexicalRootNode';
							 | 
						||
| 
								 | 
							
								import type {TextFormatType, TextNode} from './nodes/LexicalTextNode';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {CAN_USE_DOM} from 'lexical/shared/canUseDOM';
							 | 
						||
| 
								 | 
							
								import {IS_APPLE, IS_APPLE_WEBKIT, IS_IOS, IS_SAFARI} from 'lexical/shared/environment';
							 | 
						||
| 
								 | 
							
								import invariant from 'lexical/shared/invariant';
							 | 
						||
| 
								 | 
							
								import normalizeClassNames from 'lexical/shared/normalizeClassNames';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  $createTextNode,
							 | 
						||
| 
								 | 
							
								  $getPreviousSelection,
							 | 
						||
| 
								 | 
							
								  $getSelection,
							 | 
						||
| 
								 | 
							
								  $isDecoratorNode,
							 | 
						||
| 
								 | 
							
								  $isElementNode,
							 | 
						||
| 
								 | 
							
								  $isLineBreakNode,
							 | 
						||
| 
								 | 
							
								  $isRangeSelection,
							 | 
						||
| 
								 | 
							
								  $isRootNode,
							 | 
						||
| 
								 | 
							
								  $isTextNode,
							 | 
						||
| 
								 | 
							
								  DecoratorNode,
							 | 
						||
| 
								 | 
							
								  ElementNode,
							 | 
						||
| 
								 | 
							
								  LineBreakNode,
							 | 
						||
| 
								 | 
							
								} from '.';
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  COMPOSITION_SUFFIX,
							 | 
						||
| 
								 | 
							
								  DOM_TEXT_TYPE,
							 | 
						||
| 
								 | 
							
								  HAS_DIRTY_NODES,
							 | 
						||
| 
								 | 
							
								  LTR_REGEX,
							 | 
						||
| 
								 | 
							
								  RTL_REGEX,
							 | 
						||
| 
								 | 
							
								  TEXT_TYPE_TO_FORMAT,
							 | 
						||
| 
								 | 
							
								} from './LexicalConstants';
							 | 
						||
| 
								 | 
							
								import {LexicalEditor} from './LexicalEditor';
							 | 
						||
| 
								 | 
							
								import {$flushRootMutations} from './LexicalMutations';
							 | 
						||
| 
								 | 
							
								import {$normalizeSelection} from './LexicalNormalization';
							 | 
						||
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
								  errorOnInfiniteTransforms,
							 | 
						||
| 
								 | 
							
								  errorOnReadOnly,
							 | 
						||
| 
								 | 
							
								  getActiveEditor,
							 | 
						||
| 
								 | 
							
								  getActiveEditorState,
							 | 
						||
| 
								 | 
							
								  internalGetActiveEditorState,
							 | 
						||
| 
								 | 
							
								  isCurrentlyReadOnlyMode,
							 | 
						||
| 
								 | 
							
								  triggerCommandListeners,
							 | 
						||
| 
								 | 
							
								  updateEditor,
							 | 
						||
| 
								 | 
							
								} from './LexicalUpdates';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export const emptyFunction = () => {
							 | 
						||
| 
								 | 
							
								  return;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								let keyCounter = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function resetRandomKey(): void {
							 | 
						||
| 
								 | 
							
								  keyCounter = 1;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function generateRandomKey(): string {
							 | 
						||
| 
								 | 
							
								  return '' + keyCounter++;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getRegisteredNodeOrThrow(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  nodeType: string,
							 | 
						||
| 
								 | 
							
								): RegisteredNode {
							 | 
						||
| 
								 | 
							
								  const registeredNode = editor._nodes.get(nodeType);
							 | 
						||
| 
								 | 
							
								  if (registeredNode === undefined) {
							 | 
						||
| 
								 | 
							
								    invariant(false, 'registeredNode: Type %s not found', nodeType);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return registeredNode;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export const isArray = Array.isArray;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export const scheduleMicroTask: (fn: () => void) => void =
							 | 
						||
| 
								 | 
							
								  typeof queueMicrotask === 'function'
							 | 
						||
| 
								 | 
							
								    ? queueMicrotask
							 | 
						||
| 
								 | 
							
								    : (fn) => {
							 | 
						||
| 
								 | 
							
								        // No window prefix intended (#1400)
							 | 
						||
| 
								 | 
							
								        Promise.resolve().then(fn);
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isSelectionCapturedInDecorator(node: Node): boolean {
							 | 
						||
| 
								 | 
							
								  return $isDecoratorNode($getNearestNodeFromDOMNode(node));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isSelectionCapturedInDecoratorInput(anchorDOM: Node): boolean {
							 | 
						||
| 
								 | 
							
								  const activeElement = document.activeElement as HTMLElement;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (activeElement === null) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const nodeName = activeElement.nodeName;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    $isDecoratorNode($getNearestNodeFromDOMNode(anchorDOM)) &&
							 | 
						||
| 
								 | 
							
								    (nodeName === 'INPUT' ||
							 | 
						||
| 
								 | 
							
								      nodeName === 'TEXTAREA' ||
							 | 
						||
| 
								 | 
							
								      (activeElement.contentEditable === 'true' &&
							 | 
						||
| 
								 | 
							
								        getEditorPropertyFromDOMNode(activeElement) == null))
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isSelectionWithinEditor(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  anchorDOM: null | Node,
							 | 
						||
| 
								 | 
							
								  focusDOM: null | Node,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  const rootElement = editor.getRootElement();
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      rootElement !== null &&
							 | 
						||
| 
								 | 
							
								      rootElement.contains(anchorDOM) &&
							 | 
						||
| 
								 | 
							
								      rootElement.contains(focusDOM) &&
							 | 
						||
| 
								 | 
							
								      // Ignore if selection is within nested editor
							 | 
						||
| 
								 | 
							
								      anchorDOM !== null &&
							 | 
						||
| 
								 | 
							
								      !isSelectionCapturedInDecoratorInput(anchorDOM as Node) &&
							 | 
						||
| 
								 | 
							
								      getNearestEditorFromDOMNode(anchorDOM) === editor
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  } catch (error) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @returns true if the given argument is a LexicalEditor instance from this build of Lexical
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function isLexicalEditor(editor: unknown): editor is LexicalEditor {
							 | 
						||
| 
								 | 
							
								  // Check instanceof to prevent issues with multiple embedded Lexical installations
							 | 
						||
| 
								 | 
							
								  return editor instanceof LexicalEditor;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getNearestEditorFromDOMNode(
							 | 
						||
| 
								 | 
							
								  node: Node | null,
							 | 
						||
| 
								 | 
							
								): LexicalEditor | null {
							 | 
						||
| 
								 | 
							
								  let currentNode = node;
							 | 
						||
| 
								 | 
							
								  while (currentNode != null) {
							 | 
						||
| 
								 | 
							
								    const editor = getEditorPropertyFromDOMNode(currentNode);
							 | 
						||
| 
								 | 
							
								    if (isLexicalEditor(editor)) {
							 | 
						||
| 
								 | 
							
								      return editor;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    currentNode = getParentElement(currentNode);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/** @internal */
							 | 
						||
| 
								 | 
							
								export function getEditorPropertyFromDOMNode(node: Node | null): unknown {
							 | 
						||
| 
								 | 
							
								  // @ts-expect-error: internal field
							 | 
						||
| 
								 | 
							
								  return node ? node.__lexicalEditor : null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getTextDirection(text: string): 'ltr' | 'rtl' | null {
							 | 
						||
| 
								 | 
							
								  if (RTL_REGEX.test(text)) {
							 | 
						||
| 
								 | 
							
								    return 'rtl';
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (LTR_REGEX.test(text)) {
							 | 
						||
| 
								 | 
							
								    return 'ltr';
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isTokenOrSegmented(node: TextNode): boolean {
							 | 
						||
| 
								 | 
							
								  return node.isToken() || node.isSegmented();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isDOMNodeLexicalTextNode(node: Node): node is Text {
							 | 
						||
| 
								 | 
							
								  return node.nodeType === DOM_TEXT_TYPE;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getDOMTextNode(element: Node | null): Text | null {
							 | 
						||
| 
								 | 
							
								  let node = element;
							 | 
						||
| 
								 | 
							
								  while (node != null) {
							 | 
						||
| 
								 | 
							
								    if (isDOMNodeLexicalTextNode(node)) {
							 | 
						||
| 
								 | 
							
								      return node;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    node = node.firstChild;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function toggleTextFormatType(
							 | 
						||
| 
								 | 
							
								  format: number,
							 | 
						||
| 
								 | 
							
								  type: TextFormatType,
							 | 
						||
| 
								 | 
							
								  alignWithFormat: null | number,
							 | 
						||
| 
								 | 
							
								): number {
							 | 
						||
| 
								 | 
							
								  const activeFormat = TEXT_TYPE_TO_FORMAT[type];
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    alignWithFormat !== null &&
							 | 
						||
| 
								 | 
							
								    (format & activeFormat) === (alignWithFormat & activeFormat)
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    return format;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  let newFormat = format ^ activeFormat;
							 | 
						||
| 
								 | 
							
								  if (type === 'subscript') {
							 | 
						||
| 
								 | 
							
								    newFormat &= ~TEXT_TYPE_TO_FORMAT.superscript;
							 | 
						||
| 
								 | 
							
								  } else if (type === 'superscript') {
							 | 
						||
| 
								 | 
							
								    newFormat &= ~TEXT_TYPE_TO_FORMAT.subscript;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return newFormat;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isLeafNode(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode | null | undefined,
							 | 
						||
| 
								 | 
							
								): node is TextNode | LineBreakNode | DecoratorNode<unknown> {
							 | 
						||
| 
								 | 
							
								  return $isTextNode(node) || $isLineBreakNode(node) || $isDecoratorNode(node);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $setNodeKey(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  existingKey: NodeKey | null | undefined,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  if (existingKey != null) {
							 | 
						||
| 
								 | 
							
								    if (__DEV__) {
							 | 
						||
| 
								 | 
							
								      errorOnNodeKeyConstructorMismatch(node, existingKey);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    node.__key = existingKey;
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  errorOnReadOnly();
							 | 
						||
| 
								 | 
							
								  errorOnInfiniteTransforms();
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  const key = generateRandomKey();
							 | 
						||
| 
								 | 
							
								  editorState._nodeMap.set(key, node);
							 | 
						||
| 
								 | 
							
								  // TODO Split this function into leaf/element
							 | 
						||
| 
								 | 
							
								  if ($isElementNode(node)) {
							 | 
						||
| 
								 | 
							
								    editor._dirtyElements.set(key, true);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    editor._dirtyLeaves.add(key);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  editor._cloneNotNeeded.add(key);
							 | 
						||
| 
								 | 
							
								  editor._dirtyType = HAS_DIRTY_NODES;
							 | 
						||
| 
								 | 
							
								  node.__key = key;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function errorOnNodeKeyConstructorMismatch(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  existingKey: NodeKey,
							 | 
						||
| 
								 | 
							
								) {
							 | 
						||
| 
								 | 
							
								  const editorState = internalGetActiveEditorState();
							 | 
						||
| 
								 | 
							
								  if (!editorState) {
							 | 
						||
| 
								 | 
							
								    // tests expect to be able to do this kind of clone without an active editor state
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const existingNode = editorState._nodeMap.get(existingKey);
							 | 
						||
| 
								 | 
							
								  if (existingNode && existingNode.constructor !== node.constructor) {
							 | 
						||
| 
								 | 
							
								    // Lifted condition to if statement because the inverted logic is a bit confusing
							 | 
						||
| 
								 | 
							
								    if (node.constructor.name !== existingNode.constructor.name) {
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        false,
							 | 
						||
| 
								 | 
							
								        'Lexical node with constructor %s attempted to re-use key from node in active editor state with constructor %s. Keys must not be re-used when the type is changed.',
							 | 
						||
| 
								 | 
							
								        node.constructor.name,
							 | 
						||
| 
								 | 
							
								        existingNode.constructor.name,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        false,
							 | 
						||
| 
								 | 
							
								        'Lexical node with constructor %s attempted to re-use key from node in active editor state with different constructor with the same name (possibly due to invalid Hot Module Replacement). Keys must not be re-used when the type is changed.',
							 | 
						||
| 
								 | 
							
								        node.constructor.name,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type IntentionallyMarkedAsDirtyElement = boolean;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function internalMarkParentElementsAsDirty(
							 | 
						||
| 
								 | 
							
								  parentKey: NodeKey,
							 | 
						||
| 
								 | 
							
								  nodeMap: NodeMap,
							 | 
						||
| 
								 | 
							
								  dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  let nextParentKey: string | null = parentKey;
							 | 
						||
| 
								 | 
							
								  while (nextParentKey !== null) {
							 | 
						||
| 
								 | 
							
								    if (dirtyElements.has(nextParentKey)) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const node = nodeMap.get(nextParentKey);
							 | 
						||
| 
								 | 
							
								    if (node === undefined) {
							 | 
						||
| 
								 | 
							
								      break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    dirtyElements.set(nextParentKey, false);
							 | 
						||
| 
								 | 
							
								    nextParentKey = node.__parent;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// TODO #6031 this function or their callers have to adjust selection (i.e. insertBefore)
							 | 
						||
| 
								 | 
							
								export function removeFromParent(node: LexicalNode): void {
							 | 
						||
| 
								 | 
							
								  const oldParent = node.getParent();
							 | 
						||
| 
								 | 
							
								  if (oldParent !== null) {
							 | 
						||
| 
								 | 
							
								    const writableNode = node.getWritable();
							 | 
						||
| 
								 | 
							
								    const writableParent = oldParent.getWritable();
							 | 
						||
| 
								 | 
							
								    const prevSibling = node.getPreviousSibling();
							 | 
						||
| 
								 | 
							
								    const nextSibling = node.getNextSibling();
							 | 
						||
| 
								 | 
							
								    // TODO: this function duplicates a bunch of operations, can be simplified.
							 | 
						||
| 
								 | 
							
								    if (prevSibling === null) {
							 | 
						||
| 
								 | 
							
								      if (nextSibling !== null) {
							 | 
						||
| 
								 | 
							
								        const writableNextSibling = nextSibling.getWritable();
							 | 
						||
| 
								 | 
							
								        writableParent.__first = nextSibling.__key;
							 | 
						||
| 
								 | 
							
								        writableNextSibling.__prev = null;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        writableParent.__first = null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const writablePrevSibling = prevSibling.getWritable();
							 | 
						||
| 
								 | 
							
								      if (nextSibling !== null) {
							 | 
						||
| 
								 | 
							
								        const writableNextSibling = nextSibling.getWritable();
							 | 
						||
| 
								 | 
							
								        writableNextSibling.__prev = writablePrevSibling.__key;
							 | 
						||
| 
								 | 
							
								        writablePrevSibling.__next = writableNextSibling.__key;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        writablePrevSibling.__next = null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      writableNode.__prev = null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (nextSibling === null) {
							 | 
						||
| 
								 | 
							
								      if (prevSibling !== null) {
							 | 
						||
| 
								 | 
							
								        const writablePrevSibling = prevSibling.getWritable();
							 | 
						||
| 
								 | 
							
								        writableParent.__last = prevSibling.__key;
							 | 
						||
| 
								 | 
							
								        writablePrevSibling.__next = null;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        writableParent.__last = null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const writableNextSibling = nextSibling.getWritable();
							 | 
						||
| 
								 | 
							
								      if (prevSibling !== null) {
							 | 
						||
| 
								 | 
							
								        const writablePrevSibling = prevSibling.getWritable();
							 | 
						||
| 
								 | 
							
								        writablePrevSibling.__next = writableNextSibling.__key;
							 | 
						||
| 
								 | 
							
								        writableNextSibling.__prev = writablePrevSibling.__key;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        writableNextSibling.__prev = null;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      writableNode.__next = null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    writableParent.__size--;
							 | 
						||
| 
								 | 
							
								    writableNode.__parent = null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Never use this function directly! It will break
							 | 
						||
| 
								 | 
							
								// the cloning heuristic. Instead use node.getWritable().
							 | 
						||
| 
								 | 
							
								export function internalMarkNodeAsDirty(node: LexicalNode): void {
							 | 
						||
| 
								 | 
							
								  errorOnInfiniteTransforms();
							 | 
						||
| 
								 | 
							
								  const latest = node.getLatest();
							 | 
						||
| 
								 | 
							
								  const parent = latest.__parent;
							 | 
						||
| 
								 | 
							
								  const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  const nodeMap = editorState._nodeMap;
							 | 
						||
| 
								 | 
							
								  const dirtyElements = editor._dirtyElements;
							 | 
						||
| 
								 | 
							
								  if (parent !== null) {
							 | 
						||
| 
								 | 
							
								    internalMarkParentElementsAsDirty(parent, nodeMap, dirtyElements);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const key = latest.__key;
							 | 
						||
| 
								 | 
							
								  editor._dirtyType = HAS_DIRTY_NODES;
							 | 
						||
| 
								 | 
							
								  if ($isElementNode(node)) {
							 | 
						||
| 
								 | 
							
								    dirtyElements.set(key, true);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    // TODO split internally MarkNodeAsDirty into two dedicated Element/leave functions
							 | 
						||
| 
								 | 
							
								    editor._dirtyLeaves.add(key);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function internalMarkSiblingsAsDirty(node: LexicalNode) {
							 | 
						||
| 
								 | 
							
								  const previousNode = node.getPreviousSibling();
							 | 
						||
| 
								 | 
							
								  const nextNode = node.getNextSibling();
							 | 
						||
| 
								 | 
							
								  if (previousNode !== null) {
							 | 
						||
| 
								 | 
							
								    internalMarkNodeAsDirty(previousNode);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (nextNode !== null) {
							 | 
						||
| 
								 | 
							
								    internalMarkNodeAsDirty(nextNode);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $setCompositionKey(compositionKey: null | NodeKey): void {
							 | 
						||
| 
								 | 
							
								  errorOnReadOnly();
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  const previousCompositionKey = editor._compositionKey;
							 | 
						||
| 
								 | 
							
								  if (compositionKey !== previousCompositionKey) {
							 | 
						||
| 
								 | 
							
								    editor._compositionKey = compositionKey;
							 | 
						||
| 
								 | 
							
								    if (previousCompositionKey !== null) {
							 | 
						||
| 
								 | 
							
								      const node = $getNodeByKey(previousCompositionKey);
							 | 
						||
| 
								 | 
							
								      if (node !== null) {
							 | 
						||
| 
								 | 
							
								        node.getWritable();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (compositionKey !== null) {
							 | 
						||
| 
								 | 
							
								      const node = $getNodeByKey(compositionKey);
							 | 
						||
| 
								 | 
							
								      if (node !== null) {
							 | 
						||
| 
								 | 
							
								        node.getWritable();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getCompositionKey(): null | NodeKey {
							 | 
						||
| 
								 | 
							
								  if (isCurrentlyReadOnlyMode()) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  return editor._compositionKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getNodeByKey<T extends LexicalNode>(
							 | 
						||
| 
								 | 
							
								  key: NodeKey,
							 | 
						||
| 
								 | 
							
								  _editorState?: EditorState,
							 | 
						||
| 
								 | 
							
								): T | null {
							 | 
						||
| 
								 | 
							
								  const editorState = _editorState || getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  const node = editorState._nodeMap.get(key) as T;
							 | 
						||
| 
								 | 
							
								  if (node === undefined) {
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return node;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getNodeFromDOMNode(
							 | 
						||
| 
								 | 
							
								  dom: Node,
							 | 
						||
| 
								 | 
							
								  editorState?: EditorState,
							 | 
						||
| 
								 | 
							
								): LexicalNode | null {
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  // @ts-ignore We intentionally add this to the Node.
							 | 
						||
| 
								 | 
							
								  const key = dom[`__lexicalKey_${editor._key}`];
							 | 
						||
| 
								 | 
							
								  if (key !== undefined) {
							 | 
						||
| 
								 | 
							
								    return $getNodeByKey(key, editorState);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getNearestNodeFromDOMNode(
							 | 
						||
| 
								 | 
							
								  startingDOM: Node,
							 | 
						||
| 
								 | 
							
								  editorState?: EditorState,
							 | 
						||
| 
								 | 
							
								): LexicalNode | null {
							 | 
						||
| 
								 | 
							
								  let dom: Node | null = startingDOM;
							 | 
						||
| 
								 | 
							
								  while (dom != null) {
							 | 
						||
| 
								 | 
							
								    const node = $getNodeFromDOMNode(dom, editorState);
							 | 
						||
| 
								 | 
							
								    if (node !== null) {
							 | 
						||
| 
								 | 
							
								      return node;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    dom = getParentElement(dom);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function cloneDecorators(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): Record<NodeKey, unknown> {
							 | 
						||
| 
								 | 
							
								  const currentDecorators = editor._decorators;
							 | 
						||
| 
								 | 
							
								  const pendingDecorators = Object.assign({}, currentDecorators);
							 | 
						||
| 
								 | 
							
								  editor._pendingDecorators = pendingDecorators;
							 | 
						||
| 
								 | 
							
								  return pendingDecorators;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getEditorStateTextContent(editorState: EditorState): string {
							 | 
						||
| 
								 | 
							
								  return editorState.read(() => $getRoot().getTextContent());
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function markAllNodesAsDirty(editor: LexicalEditor, type: string): void {
							 | 
						||
| 
								 | 
							
								  // Mark all existing text nodes as dirty
							 | 
						||
| 
								 | 
							
								  updateEditor(
							 | 
						||
| 
								 | 
							
								    editor,
							 | 
						||
| 
								 | 
							
								    () => {
							 | 
						||
| 
								 | 
							
								      const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								      if (editorState.isEmpty()) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (type === 'root') {
							 | 
						||
| 
								 | 
							
								        $getRoot().markDirty();
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const nodeMap = editorState._nodeMap;
							 | 
						||
| 
								 | 
							
								      for (const [, node] of nodeMap) {
							 | 
						||
| 
								 | 
							
								        node.markDirty();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    editor._pendingEditorState === null
							 | 
						||
| 
								 | 
							
								      ? {
							 | 
						||
| 
								 | 
							
								          tag: 'history-merge',
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      : undefined,
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getRoot(): RootNode {
							 | 
						||
| 
								 | 
							
								  return internalGetRoot(getActiveEditorState());
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function internalGetRoot(editorState: EditorState): RootNode {
							 | 
						||
| 
								 | 
							
								  return editorState._nodeMap.get('root') as RootNode;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $setSelection(selection: null | BaseSelection): void {
							 | 
						||
| 
								 | 
							
								  errorOnReadOnly();
							 | 
						||
| 
								 | 
							
								  const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  if (selection !== null) {
							 | 
						||
| 
								 | 
							
								    if (__DEV__) {
							 | 
						||
| 
								 | 
							
								      if (Object.isFrozen(selection)) {
							 | 
						||
| 
								 | 
							
								        invariant(
							 | 
						||
| 
								 | 
							
								          false,
							 | 
						||
| 
								 | 
							
								          '$setSelection called on frozen selection object. Ensure selection is cloned before passing in.',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    selection.dirty = true;
							 | 
						||
| 
								 | 
							
								    selection.setCachedNodes(null);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  editorState._selection = selection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $flushMutations(): void {
							 | 
						||
| 
								 | 
							
								  errorOnReadOnly();
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  $flushRootMutations(editor);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getNodeFromDOM(dom: Node): null | LexicalNode {
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  const nodeKey = getNodeKeyFromDOM(dom, editor);
							 | 
						||
| 
								 | 
							
								  if (nodeKey === null) {
							 | 
						||
| 
								 | 
							
								    const rootElement = editor.getRootElement();
							 | 
						||
| 
								 | 
							
								    if (dom === rootElement) {
							 | 
						||
| 
								 | 
							
								      return $getNodeByKey('root');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return $getNodeByKey(nodeKey);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getTextNodeOffset(
							 | 
						||
| 
								 | 
							
								  node: TextNode,
							 | 
						||
| 
								 | 
							
								  moveSelectionToEnd: boolean,
							 | 
						||
| 
								 | 
							
								): number {
							 | 
						||
| 
								 | 
							
								  return moveSelectionToEnd ? node.getTextContentSize() : 0;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getNodeKeyFromDOM(
							 | 
						||
| 
								 | 
							
								  // Note that node here refers to a DOM Node, not an Lexical Node
							 | 
						||
| 
								 | 
							
								  dom: Node,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): NodeKey | null {
							 | 
						||
| 
								 | 
							
								  let node: Node | null = dom;
							 | 
						||
| 
								 | 
							
								  while (node != null) {
							 | 
						||
| 
								 | 
							
								    // @ts-ignore We intentionally add this to the Node.
							 | 
						||
| 
								 | 
							
								    const key: NodeKey = node[`__lexicalKey_${editor._key}`];
							 | 
						||
| 
								 | 
							
								    if (key !== undefined) {
							 | 
						||
| 
								 | 
							
								      return key;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    node = getParentElement(node);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function doesContainGrapheme(str: string): boolean {
							 | 
						||
| 
								 | 
							
								  return /[\uD800-\uDBFF][\uDC00-\uDFFF]/g.test(str);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getEditorsToPropagate(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								): Array<LexicalEditor> {
							 | 
						||
| 
								 | 
							
								  const editorsToPropagate = [];
							 | 
						||
| 
								 | 
							
								  let currentEditor: LexicalEditor | null = editor;
							 | 
						||
| 
								 | 
							
								  while (currentEditor !== null) {
							 | 
						||
| 
								 | 
							
								    editorsToPropagate.push(currentEditor);
							 | 
						||
| 
								 | 
							
								    currentEditor = currentEditor._parentEditor;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return editorsToPropagate;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function createUID(): string {
							 | 
						||
| 
								 | 
							
								  return Math.random()
							 | 
						||
| 
								 | 
							
								    .toString(36)
							 | 
						||
| 
								 | 
							
								    .replace(/[^a-z]+/g, '')
							 | 
						||
| 
								 | 
							
								    .substr(0, 5);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getAnchorTextFromDOM(anchorNode: Node): null | string {
							 | 
						||
| 
								 | 
							
								  if (anchorNode.nodeType === DOM_TEXT_TYPE) {
							 | 
						||
| 
								 | 
							
								    return anchorNode.nodeValue;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $updateSelectedTextFromDOM(
							 | 
						||
| 
								 | 
							
								  isCompositionEnd: boolean,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  data?: string,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  // Update the text content with the latest composition text
							 | 
						||
| 
								 | 
							
								  const domSelection = getDOMSelection(editor._window);
							 | 
						||
| 
								 | 
							
								  if (domSelection === null) {
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const anchorNode = domSelection.anchorNode;
							 | 
						||
| 
								 | 
							
								  let {anchorOffset, focusOffset} = domSelection;
							 | 
						||
| 
								 | 
							
								  if (anchorNode !== null) {
							 | 
						||
| 
								 | 
							
								    let textContent = getAnchorTextFromDOM(anchorNode);
							 | 
						||
| 
								 | 
							
								    const node = $getNearestNodeFromDOMNode(anchorNode);
							 | 
						||
| 
								 | 
							
								    if (textContent !== null && $isTextNode(node)) {
							 | 
						||
| 
								 | 
							
								      // Data is intentionally truthy, as we check for boolean, null and empty string.
							 | 
						||
| 
								 | 
							
								      if (textContent === COMPOSITION_SUFFIX && data) {
							 | 
						||
| 
								 | 
							
								        const offset = data.length;
							 | 
						||
| 
								 | 
							
								        textContent = data;
							 | 
						||
| 
								 | 
							
								        anchorOffset = offset;
							 | 
						||
| 
								 | 
							
								        focusOffset = offset;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (textContent !== null) {
							 | 
						||
| 
								 | 
							
								        $updateTextNodeFromDOMContent(
							 | 
						||
| 
								 | 
							
								          node,
							 | 
						||
| 
								 | 
							
								          textContent,
							 | 
						||
| 
								 | 
							
								          anchorOffset,
							 | 
						||
| 
								 | 
							
								          focusOffset,
							 | 
						||
| 
								 | 
							
								          isCompositionEnd,
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $updateTextNodeFromDOMContent(
							 | 
						||
| 
								 | 
							
								  textNode: TextNode,
							 | 
						||
| 
								 | 
							
								  textContent: string,
							 | 
						||
| 
								 | 
							
								  anchorOffset: null | number,
							 | 
						||
| 
								 | 
							
								  focusOffset: null | number,
							 | 
						||
| 
								 | 
							
								  compositionEnd: boolean,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  let node = textNode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (node.isAttached() && (compositionEnd || !node.isDirty())) {
							 | 
						||
| 
								 | 
							
								    const isComposing = node.isComposing();
							 | 
						||
| 
								 | 
							
								    let normalizedTextContent = textContent;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      (isComposing || compositionEnd) &&
							 | 
						||
| 
								 | 
							
								      textContent[textContent.length - 1] === COMPOSITION_SUFFIX
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      normalizedTextContent = textContent.slice(0, -1);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const prevTextContent = node.getTextContent();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (compositionEnd || normalizedTextContent !== prevTextContent) {
							 | 
						||
| 
								 | 
							
								      if (normalizedTextContent === '') {
							 | 
						||
| 
								 | 
							
								        $setCompositionKey(null);
							 | 
						||
| 
								 | 
							
								        if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT) {
							 | 
						||
| 
								 | 
							
								          // For composition (mainly Android), we have to remove the node on a later update
							 | 
						||
| 
								 | 
							
								          const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								          setTimeout(() => {
							 | 
						||
| 
								 | 
							
								            editor.update(() => {
							 | 
						||
| 
								 | 
							
								              if (node.isAttached()) {
							 | 
						||
| 
								 | 
							
								                node.remove();
							 | 
						||
| 
								 | 
							
								              }
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								          }, 20);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          node.remove();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const parent = node.getParent();
							 | 
						||
| 
								 | 
							
								      const prevSelection = $getPreviousSelection();
							 | 
						||
| 
								 | 
							
								      const prevTextContentSize = node.getTextContentSize();
							 | 
						||
| 
								 | 
							
								      const compositionKey = $getCompositionKey();
							 | 
						||
| 
								 | 
							
								      const nodeKey = node.getKey();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        node.isToken() ||
							 | 
						||
| 
								 | 
							
								        (compositionKey !== null &&
							 | 
						||
| 
								 | 
							
								          nodeKey === compositionKey &&
							 | 
						||
| 
								 | 
							
								          !isComposing) ||
							 | 
						||
| 
								 | 
							
								        // Check if character was added at the start or boundaries when not insertable, and we need
							 | 
						||
| 
								 | 
							
								        // to clear this input from occurring as that action wasn't permitted.
							 | 
						||
| 
								 | 
							
								        ($isRangeSelection(prevSelection) &&
							 | 
						||
| 
								 | 
							
								          ((parent !== null &&
							 | 
						||
| 
								 | 
							
								            !parent.canInsertTextBefore() &&
							 | 
						||
| 
								 | 
							
								            prevSelection.anchor.offset === 0) ||
							 | 
						||
| 
								 | 
							
								            (prevSelection.anchor.key === textNode.__key &&
							 | 
						||
| 
								 | 
							
								              prevSelection.anchor.offset === 0 &&
							 | 
						||
| 
								 | 
							
								              !node.canInsertTextBefore() &&
							 | 
						||
| 
								 | 
							
								              !isComposing) ||
							 | 
						||
| 
								 | 
							
								            (prevSelection.focus.key === textNode.__key &&
							 | 
						||
| 
								 | 
							
								              prevSelection.focus.offset === prevTextContentSize &&
							 | 
						||
| 
								 | 
							
								              !node.canInsertTextAfter() &&
							 | 
						||
| 
								 | 
							
								              !isComposing)))
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        node.markDirty();
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const selection = $getSelection();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        !$isRangeSelection(selection) ||
							 | 
						||
| 
								 | 
							
								        anchorOffset === null ||
							 | 
						||
| 
								 | 
							
								        focusOffset === null
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        node.setTextContent(normalizedTextContent);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      selection.setTextNodeRange(node, anchorOffset, node, focusOffset);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (node.isSegmented()) {
							 | 
						||
| 
								 | 
							
								        const originalTextContent = node.getTextContent();
							 | 
						||
| 
								 | 
							
								        const replacement = $createTextNode(originalTextContent);
							 | 
						||
| 
								 | 
							
								        node.replace(replacement);
							 | 
						||
| 
								 | 
							
								        node = replacement;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      node.setTextContent(normalizedTextContent);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function $previousSiblingDoesNotAcceptText(node: TextNode): boolean {
							 | 
						||
| 
								 | 
							
								  const previousSibling = node.getPreviousSibling();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    ($isTextNode(previousSibling) ||
							 | 
						||
| 
								 | 
							
								      ($isElementNode(previousSibling) && previousSibling.isInline())) &&
							 | 
						||
| 
								 | 
							
								    !previousSibling.canInsertTextAfter()
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This function is connected to $shouldPreventDefaultAndInsertText and determines whether the
							 | 
						||
| 
								 | 
							
								// TextNode boundaries are writable or we should use the previous/next sibling instead. For example,
							 | 
						||
| 
								 | 
							
								// in the case of a LinkNode, boundaries are not writable.
							 | 
						||
| 
								 | 
							
								export function $shouldInsertTextAfterOrBeforeTextNode(
							 | 
						||
| 
								 | 
							
								  selection: RangeSelection,
							 | 
						||
| 
								 | 
							
								  node: TextNode,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  if (node.isSegmented()) {
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (!selection.isCollapsed()) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const offset = selection.anchor.offset;
							 | 
						||
| 
								 | 
							
								  const parent = node.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								  const isToken = node.isToken();
							 | 
						||
| 
								 | 
							
								  if (offset === 0) {
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      !node.canInsertTextBefore() ||
							 | 
						||
| 
								 | 
							
								      (!parent.canInsertTextBefore() && !node.isComposing()) ||
							 | 
						||
| 
								 | 
							
								      isToken ||
							 | 
						||
| 
								 | 
							
								      $previousSiblingDoesNotAcceptText(node)
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  } else if (offset === node.getTextContentSize()) {
							 | 
						||
| 
								 | 
							
								    return (
							 | 
						||
| 
								 | 
							
								      !node.canInsertTextAfter() ||
							 | 
						||
| 
								 | 
							
								      (!parent.canInsertTextAfter() && !node.isComposing()) ||
							 | 
						||
| 
								 | 
							
								      isToken
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isTab(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'Tab' && !altKey && !ctrlKey && !metaKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isBold(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    key.toLowerCase() === 'b' && !altKey && controlOrMeta(metaKey, ctrlKey)
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isItalic(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    key.toLowerCase() === 'i' && !altKey && controlOrMeta(metaKey, ctrlKey)
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isUnderline(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    key.toLowerCase() === 'u' && !altKey && controlOrMeta(metaKey, ctrlKey)
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isParagraph(key: string, shiftKey: boolean): boolean {
							 | 
						||
| 
								 | 
							
								  return isReturn(key) && !shiftKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isLineBreak(key: string, shiftKey: boolean): boolean {
							 | 
						||
| 
								 | 
							
								  return isReturn(key) && shiftKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Inserts a new line after the selection
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isOpenLineBreak(key: string, ctrlKey: boolean): boolean {
							 | 
						||
| 
								 | 
							
								  // 79 = KeyO
							 | 
						||
| 
								 | 
							
								  return IS_APPLE && ctrlKey && key.toLowerCase() === 'o';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDeleteWordBackward(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isBackspace(key) && (IS_APPLE ? altKey : ctrlKey);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDeleteWordForward(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isDelete(key) && (IS_APPLE ? altKey : ctrlKey);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDeleteLineBackward(key: string, metaKey: boolean): boolean {
							 | 
						||
| 
								 | 
							
								  return IS_APPLE && metaKey && isBackspace(key);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDeleteLineForward(key: string, metaKey: boolean): boolean {
							 | 
						||
| 
								 | 
							
								  return IS_APPLE && metaKey && isDelete(key);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDeleteBackward(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  if (IS_APPLE) {
							 | 
						||
| 
								 | 
							
								    if (altKey || metaKey) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return isBackspace(key) || (key.toLowerCase() === 'h' && ctrlKey);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (ctrlKey || altKey || metaKey) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return isBackspace(key);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDeleteForward(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  if (IS_APPLE) {
							 | 
						||
| 
								 | 
							
								    if (shiftKey || altKey || metaKey) {
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return isDelete(key) || (key.toLowerCase() === 'd' && ctrlKey);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (ctrlKey || altKey || metaKey) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return isDelete(key);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isUndo(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    key.toLowerCase() === 'z' && !shiftKey && controlOrMeta(metaKey, ctrlKey)
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isRedo(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  if (IS_APPLE) {
							 | 
						||
| 
								 | 
							
								    return key.toLowerCase() === 'z' && metaKey && shiftKey;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    (key.toLowerCase() === 'y' && ctrlKey) ||
							 | 
						||
| 
								 | 
							
								    (key.toLowerCase() === 'z' && ctrlKey && shiftKey)
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isCopy(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  if (shiftKey) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (key.toLowerCase() === 'c') {
							 | 
						||
| 
								 | 
							
								    return IS_APPLE ? metaKey : ctrlKey;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return false;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isCut(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  if (shiftKey) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (key.toLowerCase() === 'x') {
							 | 
						||
| 
								 | 
							
								    return IS_APPLE ? metaKey : ctrlKey;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return false;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isArrowLeft(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'ArrowLeft';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isArrowRight(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'ArrowRight';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isArrowUp(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'ArrowUp';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isArrowDown(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'ArrowDown';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isMoveBackward(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isArrowLeft(key) && !ctrlKey && !metaKey && !altKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isMoveToStart(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isArrowLeft(key) && !altKey && !shiftKey && (ctrlKey || metaKey);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isMoveForward(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isArrowRight(key) && !ctrlKey && !metaKey && !altKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isMoveToEnd(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isArrowRight(key) && !altKey && !shiftKey && (ctrlKey || metaKey);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isMoveUp(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isArrowUp(key) && !ctrlKey && !metaKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isMoveDown(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return isArrowDown(key) && !ctrlKey && !metaKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isModifier(
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								  shiftKey: boolean,
							 | 
						||
| 
								 | 
							
								  altKey: boolean,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return ctrlKey || shiftKey || altKey || metaKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isSpace(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === ' ';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function controlOrMeta(metaKey: boolean, ctrlKey: boolean): boolean {
							 | 
						||
| 
								 | 
							
								  if (IS_APPLE) {
							 | 
						||
| 
								 | 
							
								    return metaKey;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return ctrlKey;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isReturn(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'Enter';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isBackspace(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'Backspace';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isEscape(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'Escape';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isDelete(key: string): boolean {
							 | 
						||
| 
								 | 
							
								  return key === 'Delete';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isSelectAll(
							 | 
						||
| 
								 | 
							
								  key: string,
							 | 
						||
| 
								 | 
							
								  metaKey: boolean,
							 | 
						||
| 
								 | 
							
								  ctrlKey: boolean,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return key.toLowerCase() === 'a' && controlOrMeta(metaKey, ctrlKey);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $selectAll(): void {
							 | 
						||
| 
								 | 
							
								  const root = $getRoot();
							 | 
						||
| 
								 | 
							
								  const selection = root.select(0, root.getChildrenSize());
							 | 
						||
| 
								 | 
							
								  $setSelection($normalizeSelection(selection));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getCachedClassNameArray(
							 | 
						||
| 
								 | 
							
								  classNamesTheme: EditorThemeClasses,
							 | 
						||
| 
								 | 
							
								  classNameThemeType: string,
							 | 
						||
| 
								 | 
							
								): Array<string> {
							 | 
						||
| 
								 | 
							
								  if (classNamesTheme.__lexicalClassNameCache === undefined) {
							 | 
						||
| 
								 | 
							
								    classNamesTheme.__lexicalClassNameCache = {};
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const classNamesCache = classNamesTheme.__lexicalClassNameCache;
							 | 
						||
| 
								 | 
							
								  const cachedClassNames = classNamesCache[classNameThemeType];
							 | 
						||
| 
								 | 
							
								  if (cachedClassNames !== undefined) {
							 | 
						||
| 
								 | 
							
								    return cachedClassNames;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const classNames = classNamesTheme[classNameThemeType];
							 | 
						||
| 
								 | 
							
								  // As we're using classList, we need
							 | 
						||
| 
								 | 
							
								  // to handle className tokens that have spaces.
							 | 
						||
| 
								 | 
							
								  // The easiest way to do this to convert the
							 | 
						||
| 
								 | 
							
								  // className tokens to an array that can be
							 | 
						||
| 
								 | 
							
								  // applied to classList.add()/remove().
							 | 
						||
| 
								 | 
							
								  if (typeof classNames === 'string') {
							 | 
						||
| 
								 | 
							
								    const classNamesArr = normalizeClassNames(classNames);
							 | 
						||
| 
								 | 
							
								    classNamesCache[classNameThemeType] = classNamesArr;
							 | 
						||
| 
								 | 
							
								    return classNamesArr;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return classNames;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function setMutatedNode(
							 | 
						||
| 
								 | 
							
								  mutatedNodes: MutatedNodes,
							 | 
						||
| 
								 | 
							
								  registeredNodes: RegisteredNodes,
							 | 
						||
| 
								 | 
							
								  mutationListeners: MutationListeners,
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  mutation: NodeMutation,
							 | 
						||
| 
								 | 
							
								) {
							 | 
						||
| 
								 | 
							
								  if (mutationListeners.size === 0) {
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const nodeType = node.__type;
							 | 
						||
| 
								 | 
							
								  const nodeKey = node.__key;
							 | 
						||
| 
								 | 
							
								  const registeredNode = registeredNodes.get(nodeType);
							 | 
						||
| 
								 | 
							
								  if (registeredNode === undefined) {
							 | 
						||
| 
								 | 
							
								    invariant(false, 'Type %s not in registeredNodes', nodeType);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const klass = registeredNode.klass;
							 | 
						||
| 
								 | 
							
								  let mutatedNodesByType = mutatedNodes.get(klass);
							 | 
						||
| 
								 | 
							
								  if (mutatedNodesByType === undefined) {
							 | 
						||
| 
								 | 
							
								    mutatedNodesByType = new Map();
							 | 
						||
| 
								 | 
							
								    mutatedNodes.set(klass, mutatedNodesByType);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const prevMutation = mutatedNodesByType.get(nodeKey);
							 | 
						||
| 
								 | 
							
								  // If the node has already been "destroyed", yet we are
							 | 
						||
| 
								 | 
							
								  // re-making it, then this means a move likely happened.
							 | 
						||
| 
								 | 
							
								  // We should change the mutation to be that of "updated"
							 | 
						||
| 
								 | 
							
								  // instead.
							 | 
						||
| 
								 | 
							
								  const isMove = prevMutation === 'destroyed' && mutation === 'created';
							 | 
						||
| 
								 | 
							
								  if (prevMutation === undefined || isMove) {
							 | 
						||
| 
								 | 
							
								    mutatedNodesByType.set(nodeKey, isMove ? 'updated' : mutation);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $nodesOfType<T extends LexicalNode>(klass: Klass<T>): Array<T> {
							 | 
						||
| 
								 | 
							
								  const klassType = klass.getType();
							 | 
						||
| 
								 | 
							
								  const editorState = getActiveEditorState();
							 | 
						||
| 
								 | 
							
								  if (editorState._readOnly) {
							 | 
						||
| 
								 | 
							
								    const nodes = getCachedTypeToNodeMap(editorState).get(klassType) as
							 | 
						||
| 
								 | 
							
								      | undefined
							 | 
						||
| 
								 | 
							
								      | Map<string, T>;
							 | 
						||
| 
								 | 
							
								    return nodes ? Array.from(nodes.values()) : [];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const nodes = editorState._nodeMap;
							 | 
						||
| 
								 | 
							
								  const nodesOfType: Array<T> = [];
							 | 
						||
| 
								 | 
							
								  for (const [, node] of nodes) {
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      node instanceof klass &&
							 | 
						||
| 
								 | 
							
								      node.__type === klassType &&
							 | 
						||
| 
								 | 
							
								      node.isAttached()
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      nodesOfType.push(node as T);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return nodesOfType;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function resolveElement(
							 | 
						||
| 
								 | 
							
								  element: ElementNode,
							 | 
						||
| 
								 | 
							
								  isBackward: boolean,
							 | 
						||
| 
								 | 
							
								  focusOffset: number,
							 | 
						||
| 
								 | 
							
								): LexicalNode | null {
							 | 
						||
| 
								 | 
							
								  const parent = element.getParent();
							 | 
						||
| 
								 | 
							
								  let offset = focusOffset;
							 | 
						||
| 
								 | 
							
								  let block = element;
							 | 
						||
| 
								 | 
							
								  if (parent !== null) {
							 | 
						||
| 
								 | 
							
								    if (isBackward && focusOffset === 0) {
							 | 
						||
| 
								 | 
							
								      offset = block.getIndexWithinParent();
							 | 
						||
| 
								 | 
							
								      block = parent;
							 | 
						||
| 
								 | 
							
								    } else if (!isBackward && focusOffset === block.getChildrenSize()) {
							 | 
						||
| 
								 | 
							
								      offset = block.getIndexWithinParent() + 1;
							 | 
						||
| 
								 | 
							
								      block = parent;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return block.getChildAtIndex(isBackward ? offset - 1 : offset);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getAdjacentNode(
							 | 
						||
| 
								 | 
							
								  focus: PointType,
							 | 
						||
| 
								 | 
							
								  isBackward: boolean,
							 | 
						||
| 
								 | 
							
								): null | LexicalNode {
							 | 
						||
| 
								 | 
							
								  const focusOffset = focus.offset;
							 | 
						||
| 
								 | 
							
								  if (focus.type === 'element') {
							 | 
						||
| 
								 | 
							
								    const block = focus.getNode();
							 | 
						||
| 
								 | 
							
								    return resolveElement(block, isBackward, focusOffset);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    const focusNode = focus.getNode();
							 | 
						||
| 
								 | 
							
								    if (
							 | 
						||
| 
								 | 
							
								      (isBackward && focusOffset === 0) ||
							 | 
						||
| 
								 | 
							
								      (!isBackward && focusOffset === focusNode.getTextContentSize())
							 | 
						||
| 
								 | 
							
								    ) {
							 | 
						||
| 
								 | 
							
								      const possibleNode = isBackward
							 | 
						||
| 
								 | 
							
								        ? focusNode.getPreviousSibling()
							 | 
						||
| 
								 | 
							
								        : focusNode.getNextSibling();
							 | 
						||
| 
								 | 
							
								      if (possibleNode === null) {
							 | 
						||
| 
								 | 
							
								        return resolveElement(
							 | 
						||
| 
								 | 
							
								          focusNode.getParentOrThrow(),
							 | 
						||
| 
								 | 
							
								          isBackward,
							 | 
						||
| 
								 | 
							
								          focusNode.getIndexWithinParent() + (isBackward ? 0 : 1),
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return possibleNode;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isFirefoxClipboardEvents(editor: LexicalEditor): boolean {
							 | 
						||
| 
								 | 
							
								  const event = getWindow(editor).event;
							 | 
						||
| 
								 | 
							
								  const inputType = event && (event as InputEvent).inputType;
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    inputType === 'insertFromPaste' ||
							 | 
						||
| 
								 | 
							
								    inputType === 'insertFromPasteAsQuotation'
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function dispatchCommand<TCommand extends LexicalCommand<unknown>>(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  command: TCommand,
							 | 
						||
| 
								 | 
							
								  payload: CommandPayloadType<TCommand>,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return triggerCommandListeners(editor, command, payload);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $textContentRequiresDoubleLinebreakAtEnd(
							 | 
						||
| 
								 | 
							
								  node: ElementNode,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  return !$isRootNode(node) && !node.isLastChild() && !node.isInline();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getElementByKeyOrThrow(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  key: NodeKey,
							 | 
						||
| 
								 | 
							
								): HTMLElement {
							 | 
						||
| 
								 | 
							
								  const element = editor._keyToDOMMap.get(key);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (element === undefined) {
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      false,
							 | 
						||
| 
								 | 
							
								      'Reconciliation: could not find DOM element for node key %s',
							 | 
						||
| 
								 | 
							
								      key,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return element;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getParentElement(node: Node): HTMLElement | null {
							 | 
						||
| 
								 | 
							
								  const parentElement =
							 | 
						||
| 
								 | 
							
								    (node as HTMLSlotElement).assignedSlot || node.parentElement;
							 | 
						||
| 
								 | 
							
								  return parentElement !== null && parentElement.nodeType === 11
							 | 
						||
| 
								 | 
							
								    ? ((parentElement as unknown as ShadowRoot).host as HTMLElement)
							 | 
						||
| 
								 | 
							
								    : parentElement;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function scrollIntoViewIfNeeded(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  selectionRect: DOMRect,
							 | 
						||
| 
								 | 
							
								  rootElement: HTMLElement,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const doc = rootElement.ownerDocument;
							 | 
						||
| 
								 | 
							
								  const defaultView = doc.defaultView;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (defaultView === null) {
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  let {top: currentTop, bottom: currentBottom} = selectionRect;
							 | 
						||
| 
								 | 
							
								  let targetTop = 0;
							 | 
						||
| 
								 | 
							
								  let targetBottom = 0;
							 | 
						||
| 
								 | 
							
								  let element: HTMLElement | null = rootElement;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (element !== null) {
							 | 
						||
| 
								 | 
							
								    const isBodyElement = element === doc.body;
							 | 
						||
| 
								 | 
							
								    if (isBodyElement) {
							 | 
						||
| 
								 | 
							
								      targetTop = 0;
							 | 
						||
| 
								 | 
							
								      targetBottom = getWindow(editor).innerHeight;
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const targetRect = element.getBoundingClientRect();
							 | 
						||
| 
								 | 
							
								      targetTop = targetRect.top;
							 | 
						||
| 
								 | 
							
								      targetBottom = targetRect.bottom;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    let diff = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (currentTop < targetTop) {
							 | 
						||
| 
								 | 
							
								      diff = -(targetTop - currentTop);
							 | 
						||
| 
								 | 
							
								    } else if (currentBottom > targetBottom) {
							 | 
						||
| 
								 | 
							
								      diff = currentBottom - targetBottom;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (diff !== 0) {
							 | 
						||
| 
								 | 
							
								      if (isBodyElement) {
							 | 
						||
| 
								 | 
							
								        // Only handles scrolling of Y axis
							 | 
						||
| 
								 | 
							
								        defaultView.scrollBy(0, diff);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        const scrollTop = element.scrollTop;
							 | 
						||
| 
								 | 
							
								        element.scrollTop += diff;
							 | 
						||
| 
								 | 
							
								        const yOffset = element.scrollTop - scrollTop;
							 | 
						||
| 
								 | 
							
								        currentTop -= yOffset;
							 | 
						||
| 
								 | 
							
								        currentBottom -= yOffset;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (isBodyElement) {
							 | 
						||
| 
								 | 
							
								      break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    element = getParentElement(element);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $hasUpdateTag(tag: string): boolean {
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  return editor._updateTags.has(tag);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $addUpdateTag(tag: string): void {
							 | 
						||
| 
								 | 
							
								  errorOnReadOnly();
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  editor._updateTags.add(tag);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $maybeMoveChildrenSelectionToParent(
							 | 
						||
| 
								 | 
							
								  parentNode: LexicalNode,
							 | 
						||
| 
								 | 
							
								): BaseSelection | null {
							 | 
						||
| 
								 | 
							
								  const selection = $getSelection();
							 | 
						||
| 
								 | 
							
								  if (!$isRangeSelection(selection) || !$isElementNode(parentNode)) {
							 | 
						||
| 
								 | 
							
								    return selection;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const {anchor, focus} = selection;
							 | 
						||
| 
								 | 
							
								  const anchorNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								  const focusNode = focus.getNode();
							 | 
						||
| 
								 | 
							
								  if ($hasAncestor(anchorNode, parentNode)) {
							 | 
						||
| 
								 | 
							
								    anchor.set(parentNode.__key, 0, 'element');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if ($hasAncestor(focusNode, parentNode)) {
							 | 
						||
| 
								 | 
							
								    focus.set(parentNode.__key, 0, 'element');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return selection;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $hasAncestor(
							 | 
						||
| 
								 | 
							
								  child: LexicalNode,
							 | 
						||
| 
								 | 
							
								  targetNode: LexicalNode,
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  let parent = child.getParent();
							 | 
						||
| 
								 | 
							
								  while (parent !== null) {
							 | 
						||
| 
								 | 
							
								    if (parent.is(targetNode)) {
							 | 
						||
| 
								 | 
							
								      return true;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    parent = parent.getParent();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return false;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getDefaultView(domElem: HTMLElement): Window | null {
							 | 
						||
| 
								 | 
							
								  const ownerDoc = domElem.ownerDocument;
							 | 
						||
| 
								 | 
							
								  return (ownerDoc && ownerDoc.defaultView) || null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getWindow(editor: LexicalEditor): Window {
							 | 
						||
| 
								 | 
							
								  const windowObj = editor._window;
							 | 
						||
| 
								 | 
							
								  if (windowObj === null) {
							 | 
						||
| 
								 | 
							
								    invariant(false, 'window object not found');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return windowObj;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $isInlineElementOrDecoratorNode(node: LexicalNode): boolean {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    ($isElementNode(node) && node.isInline()) ||
							 | 
						||
| 
								 | 
							
								    ($isDecoratorNode(node) && node.isInline())
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getNearestRootOrShadowRoot(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								): RootNode | ElementNode {
							 | 
						||
| 
								 | 
							
								  let parent = node.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								  while (parent !== null) {
							 | 
						||
| 
								 | 
							
								    if ($isRootOrShadowRoot(parent)) {
							 | 
						||
| 
								 | 
							
								      return parent;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    parent = parent.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return parent;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const ShadowRootNodeBrand: unique symbol = Symbol.for(
							 | 
						||
| 
								 | 
							
								  '@lexical/ShadowRootNodeBrand',
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								type ShadowRootNode = Spread<
							 | 
						||
| 
								 | 
							
								  {isShadowRoot(): true; [ShadowRootNodeBrand]: never},
							 | 
						||
| 
								 | 
							
								  ElementNode
							 | 
						||
| 
								 | 
							
								>;
							 | 
						||
| 
								 | 
							
								export function $isRootOrShadowRoot(
							 | 
						||
| 
								 | 
							
								  node: null | LexicalNode,
							 | 
						||
| 
								 | 
							
								): node is RootNode | ShadowRootNode {
							 | 
						||
| 
								 | 
							
								  return $isRootNode(node) || ($isElementNode(node) && node.isShadowRoot());
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns a shallow clone of node with a new key
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param node - The node to be copied.
							 | 
						||
| 
								 | 
							
								 * @returns The copy of the node.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function $copyNode<T extends LexicalNode>(node: T): T {
							 | 
						||
| 
								 | 
							
								  const copy = node.constructor.clone(node) as T;
							 | 
						||
| 
								 | 
							
								  $setNodeKey(copy, null);
							 | 
						||
| 
								 | 
							
								  return copy;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $applyNodeReplacement<N extends LexicalNode>(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								): N {
							 | 
						||
| 
								 | 
							
								  const editor = getActiveEditor();
							 | 
						||
| 
								 | 
							
								  const nodeType = node.constructor.getType();
							 | 
						||
| 
								 | 
							
								  const registeredNode = editor._nodes.get(nodeType);
							 | 
						||
| 
								 | 
							
								  if (registeredNode === undefined) {
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      false,
							 | 
						||
| 
								 | 
							
								      '$initializeNode failed. Ensure node has been registered to the editor. You can do this by passing the node class via the "nodes" array in the editor config.',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const replaceFunc = registeredNode.replace;
							 | 
						||
| 
								 | 
							
								  if (replaceFunc !== null) {
							 | 
						||
| 
								 | 
							
								    const replacementNode = replaceFunc(node) as N;
							 | 
						||
| 
								 | 
							
								    if (!(replacementNode instanceof node.constructor)) {
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        false,
							 | 
						||
| 
								 | 
							
								        '$initializeNode failed. Ensure replacement node is a subclass of the original node.',
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return replacementNode;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return node as N;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function errorOnInsertTextNodeOnRoot(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  insertNode: LexicalNode,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  const parentNode = node.getParent();
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    $isRootNode(parentNode) &&
							 | 
						||
| 
								 | 
							
								    !$isElementNode(insertNode) &&
							 | 
						||
| 
								 | 
							
								    !$isDecoratorNode(insertNode)
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      false,
							 | 
						||
| 
								 | 
							
								      'Only element or decorator nodes can be inserted in to the root node',
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getNodeByKeyOrThrow<N extends LexicalNode>(key: NodeKey): N {
							 | 
						||
| 
								 | 
							
								  const node = $getNodeByKey<N>(key);
							 | 
						||
| 
								 | 
							
								  if (node === null) {
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      false,
							 | 
						||
| 
								 | 
							
								      "Expected node with key %s to exist but it's not in the nodeMap.",
							 | 
						||
| 
								 | 
							
								      key,
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return node;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function createBlockCursorElement(editorConfig: EditorConfig): HTMLDivElement {
							 | 
						||
| 
								 | 
							
								  const theme = editorConfig.theme;
							 | 
						||
| 
								 | 
							
								  const element = document.createElement('div');
							 | 
						||
| 
								 | 
							
								  element.contentEditable = 'false';
							 | 
						||
| 
								 | 
							
								  element.setAttribute('data-lexical-cursor', 'true');
							 | 
						||
| 
								 | 
							
								  let blockCursorTheme = theme.blockCursor;
							 | 
						||
| 
								 | 
							
								  if (blockCursorTheme !== undefined) {
							 | 
						||
| 
								 | 
							
								    if (typeof blockCursorTheme === 'string') {
							 | 
						||
| 
								 | 
							
								      const classNamesArr = normalizeClassNames(blockCursorTheme);
							 | 
						||
| 
								 | 
							
								      // @ts-expect-error: intentional
							 | 
						||
| 
								 | 
							
								      blockCursorTheme = theme.blockCursor = classNamesArr;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (blockCursorTheme !== undefined) {
							 | 
						||
| 
								 | 
							
								      element.classList.add(...blockCursorTheme);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return element;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function needsBlockCursor(node: null | LexicalNode): boolean {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    ($isDecoratorNode(node) || ($isElementNode(node) && !node.canBeEmpty())) &&
							 | 
						||
| 
								 | 
							
								    !node.isInline()
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function removeDOMBlockCursorElement(
							 | 
						||
| 
								 | 
							
								  blockCursorElement: HTMLElement,
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  rootElement: HTMLElement,
							 | 
						||
| 
								 | 
							
								) {
							 | 
						||
| 
								 | 
							
								  rootElement.style.removeProperty('caret-color');
							 | 
						||
| 
								 | 
							
								  editor._blockCursorElement = null;
							 | 
						||
| 
								 | 
							
								  const parentElement = blockCursorElement.parentElement;
							 | 
						||
| 
								 | 
							
								  if (parentElement !== null) {
							 | 
						||
| 
								 | 
							
								    parentElement.removeChild(blockCursorElement);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function updateDOMBlockCursorElement(
							 | 
						||
| 
								 | 
							
								  editor: LexicalEditor,
							 | 
						||
| 
								 | 
							
								  rootElement: HTMLElement,
							 | 
						||
| 
								 | 
							
								  nextSelection: null | BaseSelection,
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  let blockCursorElement = editor._blockCursorElement;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    $isRangeSelection(nextSelection) &&
							 | 
						||
| 
								 | 
							
								    nextSelection.isCollapsed() &&
							 | 
						||
| 
								 | 
							
								    nextSelection.anchor.type === 'element' &&
							 | 
						||
| 
								 | 
							
								    rootElement.contains(document.activeElement)
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    const anchor = nextSelection.anchor;
							 | 
						||
| 
								 | 
							
								    const elementNode = anchor.getNode();
							 | 
						||
| 
								 | 
							
								    const offset = anchor.offset;
							 | 
						||
| 
								 | 
							
								    const elementNodeSize = elementNode.getChildrenSize();
							 | 
						||
| 
								 | 
							
								    let isBlockCursor = false;
							 | 
						||
| 
								 | 
							
								    let insertBeforeElement: null | HTMLElement = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (offset === elementNodeSize) {
							 | 
						||
| 
								 | 
							
								      const child = elementNode.getChildAtIndex(offset - 1);
							 | 
						||
| 
								 | 
							
								      if (needsBlockCursor(child)) {
							 | 
						||
| 
								 | 
							
								        isBlockCursor = true;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const child = elementNode.getChildAtIndex(offset);
							 | 
						||
| 
								 | 
							
								      if (needsBlockCursor(child)) {
							 | 
						||
| 
								 | 
							
								        const sibling = (child as LexicalNode).getPreviousSibling();
							 | 
						||
| 
								 | 
							
								        if (sibling === null || needsBlockCursor(sibling)) {
							 | 
						||
| 
								 | 
							
								          isBlockCursor = true;
							 | 
						||
| 
								 | 
							
								          insertBeforeElement = editor.getElementByKey(
							 | 
						||
| 
								 | 
							
								            (child as LexicalNode).__key,
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (isBlockCursor) {
							 | 
						||
| 
								 | 
							
								      const elementDOM = editor.getElementByKey(
							 | 
						||
| 
								 | 
							
								        elementNode.__key,
							 | 
						||
| 
								 | 
							
								      ) as HTMLElement;
							 | 
						||
| 
								 | 
							
								      if (blockCursorElement === null) {
							 | 
						||
| 
								 | 
							
								        editor._blockCursorElement = blockCursorElement =
							 | 
						||
| 
								 | 
							
								          createBlockCursorElement(editor._config);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      rootElement.style.caretColor = 'transparent';
							 | 
						||
| 
								 | 
							
								      if (insertBeforeElement === null) {
							 | 
						||
| 
								 | 
							
								        elementDOM.appendChild(blockCursorElement);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        elementDOM.insertBefore(blockCursorElement, insertBeforeElement);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // Remove cursor
							 | 
						||
| 
								 | 
							
								  if (blockCursorElement !== null) {
							 | 
						||
| 
								 | 
							
								    removeDOMBlockCursorElement(blockCursorElement, editor, rootElement);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function getDOMSelection(targetWindow: null | Window): null | Selection {
							 | 
						||
| 
								 | 
							
								  return !CAN_USE_DOM ? null : (targetWindow || window).getSelection();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $splitNode(
							 | 
						||
| 
								 | 
							
								  node: ElementNode,
							 | 
						||
| 
								 | 
							
								  offset: number,
							 | 
						||
| 
								 | 
							
								): [ElementNode | null, ElementNode] {
							 | 
						||
| 
								 | 
							
								  let startNode = node.getChildAtIndex(offset);
							 | 
						||
| 
								 | 
							
								  if (startNode == null) {
							 | 
						||
| 
								 | 
							
								    startNode = node;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  invariant(
							 | 
						||
| 
								 | 
							
								    !$isRootOrShadowRoot(node),
							 | 
						||
| 
								 | 
							
								    'Can not call $splitNode() on root element',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const recurse = <T extends LexicalNode>(
							 | 
						||
| 
								 | 
							
								    currentNode: T,
							 | 
						||
| 
								 | 
							
								  ): [ElementNode, ElementNode, T] => {
							 | 
						||
| 
								 | 
							
								    const parent = currentNode.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								    const isParentRoot = $isRootOrShadowRoot(parent);
							 | 
						||
| 
								 | 
							
								    // The node we start split from (leaf) is moved, but its recursive
							 | 
						||
| 
								 | 
							
								    // parents are copied to create separate tree
							 | 
						||
| 
								 | 
							
								    const nodeToMove =
							 | 
						||
| 
								 | 
							
								      currentNode === startNode && !isParentRoot
							 | 
						||
| 
								 | 
							
								        ? currentNode
							 | 
						||
| 
								 | 
							
								        : $copyNode(currentNode);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (isParentRoot) {
							 | 
						||
| 
								 | 
							
								      invariant(
							 | 
						||
| 
								 | 
							
								        $isElementNode(currentNode) && $isElementNode(nodeToMove),
							 | 
						||
| 
								 | 
							
								        'Children of a root must be ElementNode',
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      currentNode.insertAfter(nodeToMove);
							 | 
						||
| 
								 | 
							
								      return [currentNode, nodeToMove, nodeToMove];
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      const [leftTree, rightTree, newParent] = recurse(parent);
							 | 
						||
| 
								 | 
							
								      const nextSiblings = currentNode.getNextSiblings();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      newParent.append(nodeToMove, ...nextSiblings);
							 | 
						||
| 
								 | 
							
								      return [leftTree, rightTree, nodeToMove];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const [leftTree, rightTree] = recurse(startNode);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return [leftTree, rightTree];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $findMatchingParent(
							 | 
						||
| 
								 | 
							
								  startingNode: LexicalNode,
							 | 
						||
| 
								 | 
							
								  findFn: (node: LexicalNode) => boolean,
							 | 
						||
| 
								 | 
							
								): LexicalNode | null {
							 | 
						||
| 
								 | 
							
								  let curr: ElementNode | LexicalNode | null = startingNode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (curr !== $getRoot() && curr != null) {
							 | 
						||
| 
								 | 
							
								    if (findFn(curr)) {
							 | 
						||
| 
								 | 
							
								      return curr;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    curr = curr.getParent();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @param x - The element being tested
							 | 
						||
| 
								 | 
							
								 * @returns Returns true if x is an HTML anchor tag, false otherwise
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function isHTMLAnchorElement(x: Node): x is HTMLAnchorElement {
							 | 
						||
| 
								 | 
							
								  return isHTMLElement(x) && x.tagName === 'A';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @param x - The element being testing
							 | 
						||
| 
								 | 
							
								 * @returns Returns true if x is an HTML element, false otherwise.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function isHTMLElement(x: Node | EventTarget): x is HTMLElement {
							 | 
						||
| 
								 | 
							
								  // @ts-ignore-next-line - strict check on nodeType here should filter out non-Element EventTarget implementors
							 | 
						||
| 
								 | 
							
								  return x.nodeType === 1;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param node - the Dom Node to check
							 | 
						||
| 
								 | 
							
								 * @returns if the Dom Node is an inline node
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function isInlineDomNode(node: Node) {
							 | 
						||
| 
								 | 
							
								  const inlineNodes = new RegExp(
							 | 
						||
| 
								 | 
							
								    /^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var|#text)$/,
							 | 
						||
| 
								 | 
							
								    'i',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  return node.nodeName.match(inlineNodes) !== null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param node - the Dom Node to check
							 | 
						||
| 
								 | 
							
								 * @returns if the Dom Node is a block node
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function isBlockDomNode(node: Node) {
							 | 
						||
| 
								 | 
							
								  const blockNodes = new RegExp(
							 | 
						||
| 
								 | 
							
								    /^(address|article|aside|blockquote|canvas|dd|div|dl|dt|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hr|li|main|nav|noscript|ol|p|pre|section|table|td|tfoot|ul|video)$/,
							 | 
						||
| 
								 | 
							
								    'i',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  return node.nodeName.match(blockNodes) !== null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * This function is for internal use of the library.
							 | 
						||
| 
								 | 
							
								 * Please do not use it as it may change in the future.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function INTERNAL_$isBlock(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								): node is ElementNode | DecoratorNode<unknown> {
							 | 
						||
| 
								 | 
							
								  if ($isRootNode(node) || ($isDecoratorNode(node) && !node.isInline())) {
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (!$isElementNode(node) || $isRootOrShadowRoot(node)) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const firstChild = node.getFirstChild();
							 | 
						||
| 
								 | 
							
								  const isLeafElement =
							 | 
						||
| 
								 | 
							
								    firstChild === null ||
							 | 
						||
| 
								 | 
							
								    $isLineBreakNode(firstChild) ||
							 | 
						||
| 
								 | 
							
								    $isTextNode(firstChild) ||
							 | 
						||
| 
								 | 
							
								    firstChild.isInline();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return !node.isInline() && node.canBeEmpty() !== false && isLeafElement;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function $getAncestor<NodeType extends LexicalNode = LexicalNode>(
							 | 
						||
| 
								 | 
							
								  node: LexicalNode,
							 | 
						||
| 
								 | 
							
								  predicate: (ancestor: LexicalNode) => ancestor is NodeType,
							 | 
						||
| 
								 | 
							
								) {
							 | 
						||
| 
								 | 
							
								  let parent = node;
							 | 
						||
| 
								 | 
							
								  while (parent !== null && parent.getParent() !== null && !predicate(parent)) {
							 | 
						||
| 
								 | 
							
								    parent = parent.getParentOrThrow();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return predicate(parent) ? parent : null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Utility function for accessing current active editor instance.
							 | 
						||
| 
								 | 
							
								 * @returns Current active editor
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function $getEditor(): LexicalEditor {
							 | 
						||
| 
								 | 
							
								  return getActiveEditor();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/** @internal */
							 | 
						||
| 
								 | 
							
								export type TypeToNodeMap = Map<string, NodeMap>;
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @internal
							 | 
						||
| 
								 | 
							
								 * Compute a cached Map of node type to nodes for a frozen EditorState
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								const cachedNodeMaps = new WeakMap<EditorState, TypeToNodeMap>();
							 | 
						||
| 
								 | 
							
								const EMPTY_TYPE_TO_NODE_MAP: TypeToNodeMap = new Map();
							 | 
						||
| 
								 | 
							
								export function getCachedTypeToNodeMap(
							 | 
						||
| 
								 | 
							
								  editorState: EditorState,
							 | 
						||
| 
								 | 
							
								): TypeToNodeMap {
							 | 
						||
| 
								 | 
							
								  // If this is a new Editor it may have a writable this._editorState
							 | 
						||
| 
								 | 
							
								  // with only a 'root' entry.
							 | 
						||
| 
								 | 
							
								  if (!editorState._readOnly && editorState.isEmpty()) {
							 | 
						||
| 
								 | 
							
								    return EMPTY_TYPE_TO_NODE_MAP;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  invariant(
							 | 
						||
| 
								 | 
							
								    editorState._readOnly,
							 | 
						||
| 
								 | 
							
								    'getCachedTypeToNodeMap called with a writable EditorState',
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  let typeToNodeMap = cachedNodeMaps.get(editorState);
							 | 
						||
| 
								 | 
							
								  if (!typeToNodeMap) {
							 | 
						||
| 
								 | 
							
								    typeToNodeMap = new Map();
							 | 
						||
| 
								 | 
							
								    cachedNodeMaps.set(editorState, typeToNodeMap);
							 | 
						||
| 
								 | 
							
								    for (const [nodeKey, node] of editorState._nodeMap) {
							 | 
						||
| 
								 | 
							
								      const nodeType = node.__type;
							 | 
						||
| 
								 | 
							
								      let nodeMap = typeToNodeMap.get(nodeType);
							 | 
						||
| 
								 | 
							
								      if (!nodeMap) {
							 | 
						||
| 
								 | 
							
								        nodeMap = new Map();
							 | 
						||
| 
								 | 
							
								        typeToNodeMap.set(nodeType, nodeMap);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      nodeMap.set(nodeKey, node);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return typeToNodeMap;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns a clone of a node using `node.constructor.clone()` followed by
							 | 
						||
| 
								 | 
							
								 * `clone.afterCloneFrom(node)`. The resulting clone must have the same key,
							 | 
						||
| 
								 | 
							
								 * parent/next/prev pointers, and other properties that are not set by
							 | 
						||
| 
								 | 
							
								 * `node.constructor.clone` (format, style, etc.). This is primarily used by
							 | 
						||
| 
								 | 
							
								 * {@link LexicalNode.getWritable} to create a writable version of an
							 | 
						||
| 
								 | 
							
								 * existing node. The clone is the same logical node as the original node,
							 | 
						||
| 
								 | 
							
								 * do not try and use this function to duplicate or copy an existing node.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Does not mutate the EditorState.
							 | 
						||
| 
								 | 
							
								 * @param node - The node to be cloned.
							 | 
						||
| 
								 | 
							
								 * @returns The clone of the node.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function $cloneWithProperties<T extends LexicalNode>(latestNode: T): T {
							 | 
						||
| 
								 | 
							
								  const constructor = latestNode.constructor;
							 | 
						||
| 
								 | 
							
								  const mutableNode = constructor.clone(latestNode) as T;
							 | 
						||
| 
								 | 
							
								  mutableNode.afterCloneFrom(latestNode);
							 | 
						||
| 
								 | 
							
								  if (__DEV__) {
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      mutableNode.__key === latestNode.__key,
							 | 
						||
| 
								 | 
							
								      "$cloneWithProperties: %s.clone(node) (with type '%s') did not return a node with the same key, make sure to specify node.__key as the last argument to the constructor",
							 | 
						||
| 
								 | 
							
								      constructor.name,
							 | 
						||
| 
								 | 
							
								      constructor.getType(),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    invariant(
							 | 
						||
| 
								 | 
							
								      mutableNode.__parent === latestNode.__parent &&
							 | 
						||
| 
								 | 
							
								        mutableNode.__next === latestNode.__next &&
							 | 
						||
| 
								 | 
							
								        mutableNode.__prev === latestNode.__prev,
							 | 
						||
| 
								 | 
							
								      "$cloneWithProperties: %s.clone(node) (with type '%s') overrided afterCloneFrom but did not call super.afterCloneFrom(prevNode)",
							 | 
						||
| 
								 | 
							
								      constructor.name,
							 | 
						||
| 
								 | 
							
								      constructor.getType(),
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return mutableNode;
							 | 
						||
| 
								 | 
							
								}
							 |