Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@lexical/utils

Package Overview
Dependencies
Maintainers
6
Versions
604
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lexical/utils - npm Package Compare versions

Comparing version
0.44.1-nightly.20260519.0
to
0.45.0
+317
dist/index.d.ts
/**
* 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 { CAN_USE_BEFORE_INPUT, CAN_USE_DOM, IS_ANDROID, IS_ANDROID_CHROME, IS_APPLE, IS_APPLE_WEBKIT, IS_CHROME, IS_FIREFOX, IS_IOS, IS_SAFARI } from 'lexical';
import { type CaretDirection, type EditorState, ElementNode, type Klass, type LexicalEditor, type LexicalNode, type NodeCaret, PointCaret, type SiblingCaret, SplitAtPointCaretNextOptions, StateConfig, ValueOrUpdater } from 'lexical';
export { default as markSelection } from './markSelection';
export { default as positionNodeOnRange } from './positionNodeOnRange';
export { default as selectionAlwaysOnDisplay } from './selectionAlwaysOnDisplay';
export { $findMatchingParent, $getAdjacentSiblingOrParentSiblingCaret, $splitNode, addClassNamesToElement, isBlockDomNode, isHTMLAnchorElement, isHTMLElement, isInlineDomNode, mergeRegister, removeClassNamesFromElement, } from 'lexical';
export { CAN_USE_BEFORE_INPUT, CAN_USE_DOM, IS_ANDROID, IS_ANDROID_CHROME, IS_APPLE, IS_APPLE_WEBKIT, IS_CHROME, IS_FIREFOX, IS_IOS, IS_SAFARI, };
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
export declare function isMimeType(file: File, acceptableMimeTypes: Array<string>): boolean;
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
export declare function mediaFileReader(files: Array<File>, acceptableMimeTypes: Array<string>): Promise<Array<{
file: File;
result: string;
}>>;
export interface DFSNode {
readonly depth: number;
readonly node: LexicalNode;
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
export declare function $dfs(startNode?: LexicalNode, endNode?: LexicalNode): Array<DFSNode>;
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
export declare function $getAdjacentCaret<D extends CaretDirection>(caret: null | NodeCaret<D>): null | SiblingCaret<LexicalNode, D>;
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export declare function $reverseDfs(startNode?: LexicalNode, endNode?: LexicalNode): Array<DFSNode>;
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export declare function $dfsIterator(startNode?: LexicalNode, endNode?: LexicalNode): IterableIterator<DFSNode>;
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
export declare function $getNextSiblingOrParentSibling(node: LexicalNode): null | [LexicalNode, number];
export declare function $getDepth(node: null | LexicalNode): number;
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
export declare function $getNextRightPreorderNode(startingNode: LexicalNode): LexicalNode | null;
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export declare function $reverseDfsIterator(startNode?: LexicalNode, endNode?: LexicalNode): IterableIterator<DFSNode>;
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
export declare function $getNearestNodeOfType<T extends ElementNode>(node: LexicalNode, klass: Klass<T>): T | null;
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
export declare function $getNearestBlockElementAncestorOrThrow(startNode: LexicalNode): ElementNode;
export type DOMNodeToLexicalConversion = (element: Node) => LexicalNode;
export type DOMNodeToLexicalConversionMap = Record<string, DOMNodeToLexicalConversion>;
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
export declare function registerNestedElementResolver<N extends ElementNode>(editor: LexicalEditor, targetNode: Klass<N>, cloneNode: (from: N) => N, handleOverlap: (from: N, to: N) => void): () => void;
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
export declare function $restoreEditorState(editor: LexicalEditor, editorState: EditorState): void;
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
export declare function $insertNodeToNearestRoot<T extends LexicalNode>(node: T): T;
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
export declare function $insertNodeToNearestRootAtCaret<T extends LexicalNode, D extends CaretDirection>(node: T, caret: PointCaret<D>, options?: SplitAtPointCaretNextOptions): NodeCaret<D>;
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
export declare function $insertNodeIntoLeaf(node: LexicalNode): void;
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
export declare function $wrapNodeInElement(node: LexicalNode, createElementNode: () => ElementNode): ElementNode;
export type ObjectKlass<T> = new (...args: any[]) => T;
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
export declare function objectKlassEquals<T>(object: unknown, objectClass: ObjectKlass<T>): object is T;
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
export declare function $filter<T>(nodes: Array<LexicalNode>, filterFn: (node: LexicalNode) => null | T): Array<T>;
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
export declare function $handleIndentAndOutdent(indentOrOutdent: (block: ElementNode) => void): boolean;
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
export declare function $insertFirst(parent: ElementNode, node: LexicalNode): void;
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
export declare function calculateZoomLevel(element: Element | null, useManualZoom?: boolean): number;
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
export declare function $isEditorIsNestedEditor(editor: LexicalEditor): boolean;
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
export declare function $unwrapAndFilterDescendants(root: ElementNode, $predicate: (node: LexicalNode) => boolean): boolean;
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
export declare function $descendantsMatching<T extends LexicalNode>(children: LexicalNode[], $predicate: (node: LexicalNode) => node is T): T[];
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
export declare function $firstToLastIterator(node: ElementNode): Iterable<LexicalNode>;
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
export declare function $lastToFirstIterator(node: ElementNode): Iterable<LexicalNode>;
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
export declare function $unwrapNode(node: ElementNode): void;
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
export interface StateConfigWrapper<K extends string, V> {
/** A reference to the stateConfig */
readonly stateConfig: StateConfig<K, V>;
/** `(node) => $getState(node, stateConfig)` */
readonly $get: <T extends LexicalNode>(node: T) => V;
/** `(node, valueOrUpdater) => $setState(node, stateConfig, valueOrUpdater)` */
readonly $set: <T extends LexicalNode>(node: T, valueOrUpdater: ValueOrUpdater<V>) => T;
/** `[$get, $set]` */
readonly accessors: readonly [$get: this['$get'], $set: this['$set']];
/**
* `() => function () { return $get(this) }`
*
* Should be called with an explicit `this` type parameter.
*
* @example
* ```ts
* class MyNode {
* // …
* myGetter = myWrapper.makeGetterMethod<this>();
* }
* ```
*/
makeGetterMethod<T extends LexicalNode>(): (this: T) => V;
/**
* `() => function (valueOrUpdater) { return $set(this, valueOrUpdater) }`
*
* Must be called with an explicit `this` type parameter.
*
* @example
* ```ts
* class MyNode {
* // …
* mySetter = myWrapper.makeSetterMethod<this>();
* }
* ```
*/
makeSetterMethod<T extends LexicalNode>(): (this: T, valueOrUpdater: ValueOrUpdater<V>) => T;
}
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
export declare function makeStateWrapper<K extends string, V>(stateConfig: StateConfig<K, V>): StateConfigWrapper<K, V>;
/**
* 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.
*
*/
'use strict';
var lexical = require('lexical');
var selection = require('@lexical/selection');
/**
* 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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
/**
* 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.
*
*/
function px(value) {
return `${value}px`;
}
const mutationObserverConfig = {
attributes: true,
characterData: true,
childList: true,
subtree: true
};
function prependDOMNode(parent, node) {
parent.insertBefore(node, parent.firstChild);
}
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
function mlcPositionNodeOnRange(editor, range, onReposition) {
let rootDOMNode = null;
let parentDOMNode = null;
let observer = null;
let lastNodes = [];
const wrapperNode = document.createElement('div');
wrapperNode.style.position = 'relative';
function position() {
if (!(rootDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null rootDOMNode`);
}
if (!(parentDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null parentDOMNode`);
}
const {
left: parentLeft,
top: parentTop
} = parentDOMNode.getBoundingClientRect();
const rects = selection.createRectsFromDOMRange(editor, range);
if (!wrapperNode.isConnected) {
prependDOMNode(parentDOMNode, wrapperNode);
}
let hasRepositioned = false;
for (let i = 0; i < rects.length; i++) {
const rect = rects[i];
// Try to reuse the previously created Node when possible, no need to
// remove/create on the most common case reposition case
const rectNode = lastNodes[i] || document.createElement('div');
const rectNodeStyle = rectNode.style;
if (rectNodeStyle.position !== 'absolute') {
rectNodeStyle.position = 'absolute';
hasRepositioned = true;
}
const left = px(rect.left - parentLeft);
if (rectNodeStyle.left !== left) {
rectNodeStyle.left = left;
hasRepositioned = true;
}
const top = px(rect.top - parentTop);
if (rectNodeStyle.top !== top) {
rectNode.style.top = top;
hasRepositioned = true;
}
const width = px(rect.width);
if (rectNodeStyle.width !== width) {
rectNode.style.width = width;
hasRepositioned = true;
}
const height = px(rect.height);
if (rectNodeStyle.height !== height) {
rectNode.style.height = height;
hasRepositioned = true;
}
if (rectNode.parentNode !== wrapperNode) {
wrapperNode.append(rectNode);
hasRepositioned = true;
}
lastNodes[i] = rectNode;
}
while (lastNodes.length > rects.length) {
lastNodes.pop();
}
if (hasRepositioned) {
onReposition(lastNodes);
}
}
function stop() {
parentDOMNode = null;
rootDOMNode = null;
if (observer !== null) {
observer.disconnect();
}
observer = null;
wrapperNode.remove();
for (const node of lastNodes) {
node.remove();
}
lastNodes = [];
}
function restart() {
const currentRootDOMNode = editor.getRootElement();
if (currentRootDOMNode === null) {
return stop();
}
const currentParentDOMNode = currentRootDOMNode.parentElement;
if (!lexical.isHTMLElement(currentParentDOMNode)) {
return stop();
}
stop();
rootDOMNode = currentRootDOMNode;
parentDOMNode = currentParentDOMNode;
observer = new MutationObserver(mutations => {
const nextRootDOMNode = editor.getRootElement();
const nextParentDOMNode = nextRootDOMNode && nextRootDOMNode.parentElement;
if (nextRootDOMNode !== rootDOMNode || nextParentDOMNode !== parentDOMNode) {
return restart();
}
for (const mutation of mutations) {
if (!wrapperNode.contains(mutation.target)) {
// TODO throttle
return position();
}
}
});
observer.observe(currentParentDOMNode, mutationObserverConfig);
position();
}
const removeRootListener = editor.registerRootListener(restart);
return () => {
removeRootListener();
stop();
};
}
/**
* 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.
*
*/
function $getOrderedSelectionPoints(selection) {
const points = selection.getStartEndPoints();
return selection.isBackward() ? [points[1], points[0]] : points;
}
function $rangeTargetFromPoint(editor, point, node, dom) {
if (point.type === 'text' || !lexical.$isElementNode(node)) {
const textDOM = (lexical.$isTextNode(node) ? lexical.$getDOMTextNode(node, dom, editor) : lexical.getDOMTextNode(dom)) || dom;
return [textDOM, point.offset];
} else {
const slot = lexical.$getDOMSlot(node, dom, editor);
return [slot.element, slot.getFirstChildOffset() + point.offset];
}
}
function $rangeFromPoints(editor, start, startNode, startDOM, end, endNode, endDOM) {
const editorDocument = editor._window ? editor._window.document : document;
const range = editorDocument.createRange();
range.setStart(...$rangeTargetFromPoint(editor, start, startNode, startDOM));
range.setEnd(...$rangeTargetFromPoint(editor, end, endNode, endDOM));
return range;
}
function defaultOnReposition(domNodes) {
for (const domNode of domNodes) {
const domNodeStyle = domNode.style;
if (domNodeStyle.background !== 'Highlight') {
domNodeStyle.background = 'Highlight';
}
if (domNodeStyle.color !== 'HighlightText') {
domNodeStyle.color = 'HighlightText';
}
if (domNodeStyle.marginTop !== px(-1.5)) {
domNodeStyle.marginTop = px(-1.5);
}
if (domNodeStyle.paddingTop !== px(4)) {
domNodeStyle.paddingTop = px(4);
}
if (domNodeStyle.paddingBottom !== px(0)) {
domNodeStyle.paddingBottom = px(0);
}
}
}
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
function markSelection(editor, onReposition = defaultOnReposition) {
let previousAnchorNode = null;
let previousAnchorNodeDOM = null;
let previousAnchorOffset = null;
let previousFocusNode = null;
let previousFocusNodeDOM = null;
let previousFocusOffset = null;
let removeRangeListener = () => {};
function compute(editorState) {
editorState.read(() => {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
// TODO
previousAnchorNode = null;
previousAnchorOffset = null;
previousFocusNode = null;
previousFocusOffset = null;
removeRangeListener();
removeRangeListener = () => {};
return;
}
const [start, end] = $getOrderedSelectionPoints(selection);
const currentStartNode = start.getNode();
const currentStartNodeKey = currentStartNode.getKey();
const currentStartOffset = start.offset;
const currentEndNode = end.getNode();
const currentEndNodeKey = currentEndNode.getKey();
const currentEndOffset = end.offset;
const currentStartNodeDOM = editor.getElementByKey(currentStartNodeKey);
const currentEndNodeDOM = editor.getElementByKey(currentEndNodeKey);
const differentStartDOM = previousAnchorNode === null || currentStartNodeDOM !== previousAnchorNodeDOM || currentStartOffset !== previousAnchorOffset || currentStartNodeKey !== previousAnchorNode.getKey();
const differentEndDOM = previousFocusNode === null || currentEndNodeDOM !== previousFocusNodeDOM || currentEndOffset !== previousFocusOffset || currentEndNodeKey !== previousFocusNode.getKey();
if ((differentStartDOM || differentEndDOM) && currentStartNodeDOM !== null && currentEndNodeDOM !== null) {
const range = $rangeFromPoints(editor, start, currentStartNode, currentStartNodeDOM, end, currentEndNode, currentEndNodeDOM);
removeRangeListener();
removeRangeListener = mlcPositionNodeOnRange(editor, range, onReposition);
}
previousAnchorNode = currentStartNode;
previousAnchorNodeDOM = currentStartNodeDOM;
previousAnchorOffset = currentStartOffset;
previousFocusNode = currentEndNode;
previousFocusNodeDOM = currentEndNodeDOM;
previousFocusOffset = currentEndOffset;
// Pass {editor} so the active editor is set: $rangeTargetFromPoint reads
// the slot (getFirstChildOffset), which consults the active editor to
// skip the block cursor.
}, {
editor
});
}
compute(editor.getEditorState());
return lexical.mergeRegister(editor.registerUpdateListener(({
editorState
}) => compute(editorState)), () => {
removeRangeListener();
});
}
/**
* 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.
*
*/
function selectionAlwaysOnDisplay(editor, onReposition) {
let removeSelectionMark = null;
const onSelectionChange = () => {
const domSelection = getSelection();
const domAnchorNode = domSelection && domSelection.anchorNode;
const editorRootElement = editor.getRootElement();
const isSelectionInsideEditor = domAnchorNode !== null && editorRootElement !== null && editorRootElement.contains(domAnchorNode);
if (isSelectionInsideEditor) {
if (removeSelectionMark !== null) {
removeSelectionMark();
removeSelectionMark = null;
}
} else {
if (removeSelectionMark === null) {
removeSelectionMark = markSelection(editor, onReposition);
}
}
};
return editor.registerRootListener(rootElement => {
if (rootElement) {
const document = rootElement.ownerDocument;
document.addEventListener('selectionchange', onSelectionChange);
onSelectionChange();
return () => {
if (removeSelectionMark !== null) {
removeSelectionMark();
}
document.removeEventListener('selectionchange', onSelectionChange);
};
}
});
}
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
function isMimeType(file, acceptableMimeTypes) {
for (const acceptableType of acceptableMimeTypes) {
if (file.type.startsWith(acceptableType)) {
return true;
}
}
return false;
}
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
function mediaFileReader(files, acceptableMimeTypes) {
const filesIterator = files[Symbol.iterator]();
return new Promise((resolve, reject) => {
const processed = [];
const handleNextFile = () => {
const {
done,
value: file
} = filesIterator.next();
if (done) {
return resolve(processed);
}
const fileReader = new FileReader();
fileReader.addEventListener('error', reject);
fileReader.addEventListener('load', () => {
const result = fileReader.result;
if (typeof result === 'string') {
processed.push({
file,
result
});
}
handleNextFile();
});
if (isMimeType(file, acceptableMimeTypes)) {
fileReader.readAsDataURL(file);
} else {
handleNextFile();
}
};
handleNextFile();
});
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
function $dfs(startNode, endNode) {
return Array.from($dfsIterator(startNode, endNode));
}
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
function $getAdjacentCaret(caret) {
return caret ? caret.getAdjacentCaret() : null;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfs(startNode, endNode) {
return Array.from($reverseDfsIterator(startNode, endNode));
}
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $dfsIterator(startNode, endNode) {
return $dfsCaretIterator('next', startNode, endNode);
}
function $getEndCaret(startNode, direction) {
const rval = lexical.$getAdjacentSiblingOrParentSiblingCaret(lexical.$getSiblingCaret(startNode, direction));
return rval && rval[0];
}
function $dfsCaretIterator(direction, startNode, endNode) {
const root = lexical.$getRoot();
const start = startNode || root;
const startCaret = lexical.$isElementNode(start) ? lexical.$getChildCaret(start, direction) : lexical.$getSiblingCaret(start, direction);
const startDepth = $getDepth(start);
const endCaret = endNode ? lexical.$getAdjacentChildCaret(lexical.$getChildCaretOrSelf(lexical.$getSiblingCaret(endNode, direction))) || $getEndCaret(endNode, direction) : $getEndCaret(start, direction);
let depth = startDepth;
return lexical.makeStepwiseIterator({
hasNext: state => state !== null,
initial: startCaret,
map: state => ({
depth,
node: state.origin
}),
step: state => {
if (state.isSameNodeCaret(endCaret)) {
return null;
}
if (lexical.$isChildCaret(state)) {
depth++;
}
const rval = lexical.$getAdjacentSiblingOrParentSiblingCaret(state);
if (!rval || rval[0].isSameNodeCaret(endCaret)) {
return null;
}
depth += rval[1];
return rval[0];
}
});
}
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
function $getNextSiblingOrParentSibling(node) {
const rval = lexical.$getAdjacentSiblingOrParentSiblingCaret(lexical.$getSiblingCaret(node, 'next'));
return rval && [rval[0].origin, rval[1]];
}
function $getDepth(node) {
let depth = -1;
for (let innerNode = node; innerNode !== null; innerNode = innerNode.getParent()) {
depth++;
}
return depth;
}
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
function $getNextRightPreorderNode(startingNode) {
const startCaret = lexical.$getChildCaretOrSelf(lexical.$getSiblingCaret(startingNode, 'previous'));
const next = lexical.$getAdjacentSiblingOrParentSiblingCaret(startCaret, 'root');
return next && next[0].origin;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfsIterator(startNode, endNode) {
return $dfsCaretIterator('previous', startNode, endNode);
}
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
function $getNearestNodeOfType(node, klass) {
let parent = node;
while (parent != null) {
if (parent instanceof klass) {
return parent;
}
parent = parent.getParent();
}
return null;
}
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
function $getNearestBlockElementAncestorOrThrow(startNode) {
const blockNode = lexical.$findMatchingParent(startNode, node => lexical.$isElementNode(node) && !node.isInline());
if (!lexical.$isElementNode(blockNode)) {
{
formatDevErrorMessage(`Expected node ${startNode.__key} to have closest block element node.`);
}
}
return blockNode;
}
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
function registerNestedElementResolver(editor, targetNode, cloneNode, handleOverlap) {
const $isTargetNode = node => {
return node instanceof targetNode;
};
const $findMatch = node => {
// First validate we don't have any children that are of the target,
// as we need to handle them first.
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if ($isTargetNode(child)) {
return null;
}
}
let parentNode = node;
let childNode = node;
while (parentNode !== null) {
childNode = parentNode;
parentNode = parentNode.getParent();
if ($isTargetNode(parentNode)) {
return {
child: childNode,
parent: parentNode
};
}
}
return null;
};
const $elementNodeTransform = node => {
const match = $findMatch(node);
if (match !== null) {
const {
child,
parent
} = match;
// Simple path, we can move child out and siblings into a new parent.
if (child.is(node)) {
handleOverlap(parent, node);
const nextSiblings = child.getNextSiblings();
const nextSiblingsLength = nextSiblings.length;
parent.insertAfter(child);
if (nextSiblingsLength !== 0) {
const newParent = cloneNode(parent);
child.insertAfter(newParent);
for (let i = 0; i < nextSiblingsLength; i++) {
newParent.append(nextSiblings[i]);
}
}
if (!parent.canBeEmpty() && parent.getChildrenSize() === 0) {
parent.remove();
}
}
}
};
return editor.registerNodeTransform(targetNode, $elementNodeTransform);
}
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
function $restoreEditorState(editor, editorState) {
const nodeMap = new Map();
const activeEditorState = editor._pendingEditorState;
for (const [key, node] of editorState._nodeMap) {
nodeMap.set(key, lexical.$cloneWithProperties(node));
}
if (activeEditorState) {
activeEditorState._nodeMap = nodeMap;
}
lexical.$fullReconcile();
const selection = editorState._selection;
lexical.$setSelection(selection === null ? null : selection.clone());
}
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
function $insertNodeToNearestRoot(node) {
const selection = lexical.$getSelection() || lexical.$getPreviousSelection();
let initialCaret;
if (lexical.$isRangeSelection(selection)) {
initialCaret = lexical.$caretFromPoint(selection.focus, 'next');
} else {
if (selection != null) {
const nodes = selection.getNodes();
const lastNode = nodes[nodes.length - 1];
if (lastNode) {
initialCaret = lexical.$getSiblingCaret(lastNode, 'next');
}
}
initialCaret = initialCaret || lexical.$getChildCaret(lexical.$getRoot(), 'previous').getFlipped().insert(lexical.$createParagraphNode());
}
const insertCaret = $insertNodeToNearestRootAtCaret(node, initialCaret);
const adjacent = lexical.$getAdjacentChildCaret(insertCaret);
const selectionCaret = lexical.$isChildCaret(adjacent) ? lexical.$normalizeCaret(adjacent) : insertCaret;
lexical.$setSelectionFromCaretRange(lexical.$getCollapsedCaretRange(selectionCaret));
return node.getLatest();
}
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
function $insertNodeToNearestRootAtCaret(node, caret, options) {
let insertCaret = lexical.$getCaretInDirection(caret, 'next');
// Normalize boundary cases for TextPointCaret
if (lexical.$isTextPointCaret(insertCaret)) {
if (insertCaret.offset === 0) {
insertCaret = lexical.$getSiblingCaret(insertCaret.origin, 'previous').getFlipped();
} else if (insertCaret.offset === insertCaret.origin.getTextContentSize()) {
insertCaret = lexical.$getSiblingCaret(insertCaret.origin, 'next');
}
}
// Make sure we have a distinct node as the origin
if (insertCaret.origin.is(node)) {
if (!lexical.$isSiblingCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret node ${node.getKey()} of type ${node.getType()} can not be inserted into itself`);
}
insertCaret = lexical.$rewindSiblingCaret(insertCaret);
}
// Handle split boundary conditions where node is being inserted adjacent to itself
if (node.is(insertCaret.getNodeAtCaret()) || node.is(insertCaret.getFlipped().getNodeAtCaret())) {
node.remove(true);
}
for (let nextCaret = insertCaret; nextCaret; nextCaret = lexical.$splitAtPointCaretNext(nextCaret, options)) {
insertCaret = nextCaret;
}
if (!!lexical.$isTextPointCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret: An unattached TextNode can not be split`);
}
insertCaret.insert(node.isInline() ? lexical.$createParagraphNode().append(node) : node);
return lexical.$getCaretInDirection(lexical.$getSiblingCaret(node.getLatest(), 'next'), caret.direction);
}
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
function $insertNodeIntoLeaf(node) {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
if (selection) {
selection.insertNodes([node]);
}
return;
}
const caretRange = lexical.$caretRangeFromSelection(selection);
let insertCaret = lexical.$getCaretRangeInDirection(lexical.$removeTextFromCaretRange(caretRange), 'next').anchor;
if (lexical.$isTextPointCaret(insertCaret)) {
const nextAnchor = lexical.$splitAtPointCaretNext(insertCaret);
if (!nextAnchor) {
return;
}
insertCaret = nextAnchor;
}
const focus = insertCaret.getFlipped();
focus.insert(node);
lexical.$setSelectionFromCaretRange(lexical.$getCaretRange(focus, focus));
}
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
function $wrapNodeInElement(node, createElementNode) {
const elementNode = createElementNode();
node.replace(elementNode);
elementNode.append(node);
return elementNode;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
function objectKlassEquals(object, objectClass) {
return object !== null ? Object.getPrototypeOf(object).constructor.name === objectClass.name : false;
}
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
function $filter(nodes, filterFn) {
const result = [];
for (let i = 0; i < nodes.length; i++) {
const node = filterFn(nodes[i]);
if (node !== null) {
result.push(node);
}
}
return result;
}
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
function $handleIndentAndOutdent(indentOrOutdent) {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
return false;
}
const alreadyHandled = new Set();
const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getKey();
if (alreadyHandled.has(key)) {
continue;
}
const parentBlock = lexical.$findMatchingParent(node, parentNode => lexical.$isElementNode(parentNode) && !parentNode.isInline());
if (parentBlock === null) {
continue;
}
const parentKey = parentBlock.getKey();
if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
alreadyHandled.add(parentKey);
indentOrOutdent(parentBlock);
}
}
return alreadyHandled.size > 0;
}
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
function $insertFirst(parent, node) {
lexical.$getChildCaret(parent, 'next').insert(node);
}
let NEEDS_MANUAL_ZOOM = lexical.IS_FIREFOX || !lexical.CAN_USE_DOM ? false : undefined;
function needsManualZoom() {
if (NEEDS_MANUAL_ZOOM === undefined) {
// If the browser implements standardized CSS zoom, then the client rect
// will be wider after zoom is applied
// https://chromestatus.com/feature/5198254868529152
// https://github.com/facebook/lexical/issues/6863
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.opacity = '0';
div.style.width = '100px';
div.style.left = '-1000px';
document.body.appendChild(div);
const noZoom = div.getBoundingClientRect();
div.style.setProperty('zoom', '2');
NEEDS_MANUAL_ZOOM = div.getBoundingClientRect().width === noZoom.width;
document.body.removeChild(div);
}
return NEEDS_MANUAL_ZOOM;
}
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
function calculateZoomLevel(element, useManualZoom = false) {
let zoom = 1;
if (needsManualZoom() || useManualZoom) {
while (element) {
zoom *= Number(window.getComputedStyle(element).getPropertyValue('zoom'));
element = element.parentElement;
}
}
return zoom;
}
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
function $isEditorIsNestedEditor(editor) {
return editor._parentEditor !== null;
}
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
function $unwrapAndFilterDescendants(root, $predicate) {
return $unwrapAndFilterDescendantsImpl(root, $predicate, null);
}
function $unwrapAndFilterDescendantsImpl(root, $predicate, $onSuccess) {
let didMutate = false;
for (const node of $lastToFirstIterator(root)) {
if ($predicate(node)) {
if ($onSuccess !== null) {
$onSuccess(node);
}
continue;
}
didMutate = true;
if (lexical.$isElementNode(node)) {
$unwrapAndFilterDescendantsImpl(node, $predicate, $onSuccess || (child => node.insertAfter(child)));
}
node.remove();
}
return didMutate;
}
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
function $descendantsMatching(children, $predicate) {
const result = [];
const stack = Array.from(children).reverse();
for (let child = stack.pop(); child !== undefined; child = stack.pop()) {
if ($predicate(child)) {
result.push(child);
} else if (lexical.$isElementNode(child)) {
for (const grandchild of $lastToFirstIterator(child)) {
stack.push(grandchild);
}
}
}
return result;
}
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $firstToLastIterator(node) {
return $childIterator(lexical.$getChildCaret(node, 'next'));
}
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $lastToFirstIterator(node) {
return $childIterator(lexical.$getChildCaret(node, 'previous'));
}
function $childIterator(startCaret) {
const seen = new Set() ;
return lexical.makeStepwiseIterator({
hasNext: lexical.$isSiblingCaret,
initial: startCaret.getAdjacentCaret(),
map: caret => {
const origin = caret.origin.getLatest();
if (seen !== null) {
const key = origin.getKey();
if (!!seen.has(key)) {
formatDevErrorMessage(`$childIterator: Cycle detected, node with key ${String(key)} has already been traversed`);
}
seen.add(key);
}
return origin;
},
step: caret => caret.getAdjacentCaret()
});
}
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
function $unwrapNode(node) {
lexical.$rewindSiblingCaret(lexical.$getSiblingCaret(node, 'next')).splice(1, node.getChildren());
}
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
function makeStateWrapper(stateConfig) {
const $get = node => lexical.$getState(node, stateConfig);
const $set = (node, valueOrUpdater) => lexical.$setState(node, stateConfig, valueOrUpdater);
return {
$get,
$set,
accessors: [$get, $set],
makeGetterMethod: () => function $getter() {
return $get(this);
},
makeSetterMethod: () => function $setter(valueOrUpdater) {
return $set(this, valueOrUpdater);
},
stateConfig
};
}
exports.$findMatchingParent = lexical.$findMatchingParent;
exports.$getAdjacentSiblingOrParentSiblingCaret = lexical.$getAdjacentSiblingOrParentSiblingCaret;
exports.$splitNode = lexical.$splitNode;
exports.CAN_USE_BEFORE_INPUT = lexical.CAN_USE_BEFORE_INPUT;
exports.CAN_USE_DOM = lexical.CAN_USE_DOM;
exports.IS_ANDROID = lexical.IS_ANDROID;
exports.IS_ANDROID_CHROME = lexical.IS_ANDROID_CHROME;
exports.IS_APPLE = lexical.IS_APPLE;
exports.IS_APPLE_WEBKIT = lexical.IS_APPLE_WEBKIT;
exports.IS_CHROME = lexical.IS_CHROME;
exports.IS_FIREFOX = lexical.IS_FIREFOX;
exports.IS_IOS = lexical.IS_IOS;
exports.IS_SAFARI = lexical.IS_SAFARI;
exports.addClassNamesToElement = lexical.addClassNamesToElement;
exports.isBlockDomNode = lexical.isBlockDomNode;
exports.isHTMLAnchorElement = lexical.isHTMLAnchorElement;
exports.isHTMLElement = lexical.isHTMLElement;
exports.isInlineDomNode = lexical.isInlineDomNode;
exports.mergeRegister = lexical.mergeRegister;
exports.removeClassNamesFromElement = lexical.removeClassNamesFromElement;
exports.$descendantsMatching = $descendantsMatching;
exports.$dfs = $dfs;
exports.$dfsIterator = $dfsIterator;
exports.$filter = $filter;
exports.$firstToLastIterator = $firstToLastIterator;
exports.$getAdjacentCaret = $getAdjacentCaret;
exports.$getDepth = $getDepth;
exports.$getNearestBlockElementAncestorOrThrow = $getNearestBlockElementAncestorOrThrow;
exports.$getNearestNodeOfType = $getNearestNodeOfType;
exports.$getNextRightPreorderNode = $getNextRightPreorderNode;
exports.$getNextSiblingOrParentSibling = $getNextSiblingOrParentSibling;
exports.$handleIndentAndOutdent = $handleIndentAndOutdent;
exports.$insertFirst = $insertFirst;
exports.$insertNodeIntoLeaf = $insertNodeIntoLeaf;
exports.$insertNodeToNearestRoot = $insertNodeToNearestRoot;
exports.$insertNodeToNearestRootAtCaret = $insertNodeToNearestRootAtCaret;
exports.$isEditorIsNestedEditor = $isEditorIsNestedEditor;
exports.$lastToFirstIterator = $lastToFirstIterator;
exports.$restoreEditorState = $restoreEditorState;
exports.$reverseDfs = $reverseDfs;
exports.$reverseDfsIterator = $reverseDfsIterator;
exports.$unwrapAndFilterDescendants = $unwrapAndFilterDescendants;
exports.$unwrapNode = $unwrapNode;
exports.$wrapNodeInElement = $wrapNodeInElement;
exports.calculateZoomLevel = calculateZoomLevel;
exports.isMimeType = isMimeType;
exports.makeStateWrapper = makeStateWrapper;
exports.markSelection = markSelection;
exports.mediaFileReader = mediaFileReader;
exports.objectKlassEquals = objectKlassEquals;
exports.positionNodeOnRange = mlcPositionNodeOnRange;
exports.registerNestedElementResolver = registerNestedElementResolver;
exports.selectionAlwaysOnDisplay = selectionAlwaysOnDisplay;
/**
* 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 { isHTMLElement, mergeRegister, $getSelection, $isRangeSelection, $isElementNode, $isTextNode, $getDOMTextNode, getDOMTextNode, $getDOMSlot, $getChildCaret, $findMatchingParent, $getChildCaretOrSelf, $getSiblingCaret, $getAdjacentSiblingOrParentSiblingCaret, $caretRangeFromSelection, $getCaretRangeInDirection, $removeTextFromCaretRange, $isTextPointCaret, $splitAtPointCaretNext, $setSelectionFromCaretRange, $getCaretRange, $getPreviousSelection, $caretFromPoint, $getRoot, $createParagraphNode, $getAdjacentChildCaret, $isChildCaret, $normalizeCaret, $getCollapsedCaretRange, $getCaretInDirection, $isSiblingCaret, $rewindSiblingCaret, $cloneWithProperties, $fullReconcile, $setSelection, makeStepwiseIterator, $getState, $setState, IS_FIREFOX, CAN_USE_DOM } from 'lexical';
export { $findMatchingParent, $getAdjacentSiblingOrParentSiblingCaret, $splitNode, CAN_USE_BEFORE_INPUT, CAN_USE_DOM, IS_ANDROID, IS_ANDROID_CHROME, IS_APPLE, IS_APPLE_WEBKIT, IS_CHROME, IS_FIREFOX, IS_IOS, IS_SAFARI, addClassNamesToElement, isBlockDomNode, isHTMLAnchorElement, isHTMLElement, isInlineDomNode, mergeRegister, removeClassNamesFromElement } from 'lexical';
import { createRectsFromDOMRange } from '@lexical/selection';
/**
* 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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
/**
* 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.
*
*/
function px(value) {
return `${value}px`;
}
const mutationObserverConfig = {
attributes: true,
characterData: true,
childList: true,
subtree: true
};
function prependDOMNode(parent, node) {
parent.insertBefore(node, parent.firstChild);
}
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
function mlcPositionNodeOnRange(editor, range, onReposition) {
let rootDOMNode = null;
let parentDOMNode = null;
let observer = null;
let lastNodes = [];
const wrapperNode = document.createElement('div');
wrapperNode.style.position = 'relative';
function position() {
if (!(rootDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null rootDOMNode`);
}
if (!(parentDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null parentDOMNode`);
}
const {
left: parentLeft,
top: parentTop
} = parentDOMNode.getBoundingClientRect();
const rects = createRectsFromDOMRange(editor, range);
if (!wrapperNode.isConnected) {
prependDOMNode(parentDOMNode, wrapperNode);
}
let hasRepositioned = false;
for (let i = 0; i < rects.length; i++) {
const rect = rects[i];
// Try to reuse the previously created Node when possible, no need to
// remove/create on the most common case reposition case
const rectNode = lastNodes[i] || document.createElement('div');
const rectNodeStyle = rectNode.style;
if (rectNodeStyle.position !== 'absolute') {
rectNodeStyle.position = 'absolute';
hasRepositioned = true;
}
const left = px(rect.left - parentLeft);
if (rectNodeStyle.left !== left) {
rectNodeStyle.left = left;
hasRepositioned = true;
}
const top = px(rect.top - parentTop);
if (rectNodeStyle.top !== top) {
rectNode.style.top = top;
hasRepositioned = true;
}
const width = px(rect.width);
if (rectNodeStyle.width !== width) {
rectNode.style.width = width;
hasRepositioned = true;
}
const height = px(rect.height);
if (rectNodeStyle.height !== height) {
rectNode.style.height = height;
hasRepositioned = true;
}
if (rectNode.parentNode !== wrapperNode) {
wrapperNode.append(rectNode);
hasRepositioned = true;
}
lastNodes[i] = rectNode;
}
while (lastNodes.length > rects.length) {
lastNodes.pop();
}
if (hasRepositioned) {
onReposition(lastNodes);
}
}
function stop() {
parentDOMNode = null;
rootDOMNode = null;
if (observer !== null) {
observer.disconnect();
}
observer = null;
wrapperNode.remove();
for (const node of lastNodes) {
node.remove();
}
lastNodes = [];
}
function restart() {
const currentRootDOMNode = editor.getRootElement();
if (currentRootDOMNode === null) {
return stop();
}
const currentParentDOMNode = currentRootDOMNode.parentElement;
if (!isHTMLElement(currentParentDOMNode)) {
return stop();
}
stop();
rootDOMNode = currentRootDOMNode;
parentDOMNode = currentParentDOMNode;
observer = new MutationObserver(mutations => {
const nextRootDOMNode = editor.getRootElement();
const nextParentDOMNode = nextRootDOMNode && nextRootDOMNode.parentElement;
if (nextRootDOMNode !== rootDOMNode || nextParentDOMNode !== parentDOMNode) {
return restart();
}
for (const mutation of mutations) {
if (!wrapperNode.contains(mutation.target)) {
// TODO throttle
return position();
}
}
});
observer.observe(currentParentDOMNode, mutationObserverConfig);
position();
}
const removeRootListener = editor.registerRootListener(restart);
return () => {
removeRootListener();
stop();
};
}
/**
* 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.
*
*/
function $getOrderedSelectionPoints(selection) {
const points = selection.getStartEndPoints();
return selection.isBackward() ? [points[1], points[0]] : points;
}
function $rangeTargetFromPoint(editor, point, node, dom) {
if (point.type === 'text' || !$isElementNode(node)) {
const textDOM = ($isTextNode(node) ? $getDOMTextNode(node, dom, editor) : getDOMTextNode(dom)) || dom;
return [textDOM, point.offset];
} else {
const slot = $getDOMSlot(node, dom, editor);
return [slot.element, slot.getFirstChildOffset() + point.offset];
}
}
function $rangeFromPoints(editor, start, startNode, startDOM, end, endNode, endDOM) {
const editorDocument = editor._window ? editor._window.document : document;
const range = editorDocument.createRange();
range.setStart(...$rangeTargetFromPoint(editor, start, startNode, startDOM));
range.setEnd(...$rangeTargetFromPoint(editor, end, endNode, endDOM));
return range;
}
function defaultOnReposition(domNodes) {
for (const domNode of domNodes) {
const domNodeStyle = domNode.style;
if (domNodeStyle.background !== 'Highlight') {
domNodeStyle.background = 'Highlight';
}
if (domNodeStyle.color !== 'HighlightText') {
domNodeStyle.color = 'HighlightText';
}
if (domNodeStyle.marginTop !== px(-1.5)) {
domNodeStyle.marginTop = px(-1.5);
}
if (domNodeStyle.paddingTop !== px(4)) {
domNodeStyle.paddingTop = px(4);
}
if (domNodeStyle.paddingBottom !== px(0)) {
domNodeStyle.paddingBottom = px(0);
}
}
}
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
function markSelection(editor, onReposition = defaultOnReposition) {
let previousAnchorNode = null;
let previousAnchorNodeDOM = null;
let previousAnchorOffset = null;
let previousFocusNode = null;
let previousFocusNodeDOM = null;
let previousFocusOffset = null;
let removeRangeListener = () => {};
function compute(editorState) {
editorState.read(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
// TODO
previousAnchorNode = null;
previousAnchorOffset = null;
previousFocusNode = null;
previousFocusOffset = null;
removeRangeListener();
removeRangeListener = () => {};
return;
}
const [start, end] = $getOrderedSelectionPoints(selection);
const currentStartNode = start.getNode();
const currentStartNodeKey = currentStartNode.getKey();
const currentStartOffset = start.offset;
const currentEndNode = end.getNode();
const currentEndNodeKey = currentEndNode.getKey();
const currentEndOffset = end.offset;
const currentStartNodeDOM = editor.getElementByKey(currentStartNodeKey);
const currentEndNodeDOM = editor.getElementByKey(currentEndNodeKey);
const differentStartDOM = previousAnchorNode === null || currentStartNodeDOM !== previousAnchorNodeDOM || currentStartOffset !== previousAnchorOffset || currentStartNodeKey !== previousAnchorNode.getKey();
const differentEndDOM = previousFocusNode === null || currentEndNodeDOM !== previousFocusNodeDOM || currentEndOffset !== previousFocusOffset || currentEndNodeKey !== previousFocusNode.getKey();
if ((differentStartDOM || differentEndDOM) && currentStartNodeDOM !== null && currentEndNodeDOM !== null) {
const range = $rangeFromPoints(editor, start, currentStartNode, currentStartNodeDOM, end, currentEndNode, currentEndNodeDOM);
removeRangeListener();
removeRangeListener = mlcPositionNodeOnRange(editor, range, onReposition);
}
previousAnchorNode = currentStartNode;
previousAnchorNodeDOM = currentStartNodeDOM;
previousAnchorOffset = currentStartOffset;
previousFocusNode = currentEndNode;
previousFocusNodeDOM = currentEndNodeDOM;
previousFocusOffset = currentEndOffset;
// Pass {editor} so the active editor is set: $rangeTargetFromPoint reads
// the slot (getFirstChildOffset), which consults the active editor to
// skip the block cursor.
}, {
editor
});
}
compute(editor.getEditorState());
return mergeRegister(editor.registerUpdateListener(({
editorState
}) => compute(editorState)), () => {
removeRangeListener();
});
}
/**
* 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.
*
*/
function selectionAlwaysOnDisplay(editor, onReposition) {
let removeSelectionMark = null;
const onSelectionChange = () => {
const domSelection = getSelection();
const domAnchorNode = domSelection && domSelection.anchorNode;
const editorRootElement = editor.getRootElement();
const isSelectionInsideEditor = domAnchorNode !== null && editorRootElement !== null && editorRootElement.contains(domAnchorNode);
if (isSelectionInsideEditor) {
if (removeSelectionMark !== null) {
removeSelectionMark();
removeSelectionMark = null;
}
} else {
if (removeSelectionMark === null) {
removeSelectionMark = markSelection(editor, onReposition);
}
}
};
return editor.registerRootListener(rootElement => {
if (rootElement) {
const document = rootElement.ownerDocument;
document.addEventListener('selectionchange', onSelectionChange);
onSelectionChange();
return () => {
if (removeSelectionMark !== null) {
removeSelectionMark();
}
document.removeEventListener('selectionchange', onSelectionChange);
};
}
});
}
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
function isMimeType(file, acceptableMimeTypes) {
for (const acceptableType of acceptableMimeTypes) {
if (file.type.startsWith(acceptableType)) {
return true;
}
}
return false;
}
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
function mediaFileReader(files, acceptableMimeTypes) {
const filesIterator = files[Symbol.iterator]();
return new Promise((resolve, reject) => {
const processed = [];
const handleNextFile = () => {
const {
done,
value: file
} = filesIterator.next();
if (done) {
return resolve(processed);
}
const fileReader = new FileReader();
fileReader.addEventListener('error', reject);
fileReader.addEventListener('load', () => {
const result = fileReader.result;
if (typeof result === 'string') {
processed.push({
file,
result
});
}
handleNextFile();
});
if (isMimeType(file, acceptableMimeTypes)) {
fileReader.readAsDataURL(file);
} else {
handleNextFile();
}
};
handleNextFile();
});
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
function $dfs(startNode, endNode) {
return Array.from($dfsIterator(startNode, endNode));
}
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
function $getAdjacentCaret(caret) {
return caret ? caret.getAdjacentCaret() : null;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfs(startNode, endNode) {
return Array.from($reverseDfsIterator(startNode, endNode));
}
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $dfsIterator(startNode, endNode) {
return $dfsCaretIterator('next', startNode, endNode);
}
function $getEndCaret(startNode, direction) {
const rval = $getAdjacentSiblingOrParentSiblingCaret($getSiblingCaret(startNode, direction));
return rval && rval[0];
}
function $dfsCaretIterator(direction, startNode, endNode) {
const root = $getRoot();
const start = startNode || root;
const startCaret = $isElementNode(start) ? $getChildCaret(start, direction) : $getSiblingCaret(start, direction);
const startDepth = $getDepth(start);
const endCaret = endNode ? $getAdjacentChildCaret($getChildCaretOrSelf($getSiblingCaret(endNode, direction))) || $getEndCaret(endNode, direction) : $getEndCaret(start, direction);
let depth = startDepth;
return makeStepwiseIterator({
hasNext: state => state !== null,
initial: startCaret,
map: state => ({
depth,
node: state.origin
}),
step: state => {
if (state.isSameNodeCaret(endCaret)) {
return null;
}
if ($isChildCaret(state)) {
depth++;
}
const rval = $getAdjacentSiblingOrParentSiblingCaret(state);
if (!rval || rval[0].isSameNodeCaret(endCaret)) {
return null;
}
depth += rval[1];
return rval[0];
}
});
}
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
function $getNextSiblingOrParentSibling(node) {
const rval = $getAdjacentSiblingOrParentSiblingCaret($getSiblingCaret(node, 'next'));
return rval && [rval[0].origin, rval[1]];
}
function $getDepth(node) {
let depth = -1;
for (let innerNode = node; innerNode !== null; innerNode = innerNode.getParent()) {
depth++;
}
return depth;
}
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
function $getNextRightPreorderNode(startingNode) {
const startCaret = $getChildCaretOrSelf($getSiblingCaret(startingNode, 'previous'));
const next = $getAdjacentSiblingOrParentSiblingCaret(startCaret, 'root');
return next && next[0].origin;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfsIterator(startNode, endNode) {
return $dfsCaretIterator('previous', startNode, endNode);
}
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
function $getNearestNodeOfType(node, klass) {
let parent = node;
while (parent != null) {
if (parent instanceof klass) {
return parent;
}
parent = parent.getParent();
}
return null;
}
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
function $getNearestBlockElementAncestorOrThrow(startNode) {
const blockNode = $findMatchingParent(startNode, node => $isElementNode(node) && !node.isInline());
if (!$isElementNode(blockNode)) {
{
formatDevErrorMessage(`Expected node ${startNode.__key} to have closest block element node.`);
}
}
return blockNode;
}
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
function registerNestedElementResolver(editor, targetNode, cloneNode, handleOverlap) {
const $isTargetNode = node => {
return node instanceof targetNode;
};
const $findMatch = node => {
// First validate we don't have any children that are of the target,
// as we need to handle them first.
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if ($isTargetNode(child)) {
return null;
}
}
let parentNode = node;
let childNode = node;
while (parentNode !== null) {
childNode = parentNode;
parentNode = parentNode.getParent();
if ($isTargetNode(parentNode)) {
return {
child: childNode,
parent: parentNode
};
}
}
return null;
};
const $elementNodeTransform = node => {
const match = $findMatch(node);
if (match !== null) {
const {
child,
parent
} = match;
// Simple path, we can move child out and siblings into a new parent.
if (child.is(node)) {
handleOverlap(parent, node);
const nextSiblings = child.getNextSiblings();
const nextSiblingsLength = nextSiblings.length;
parent.insertAfter(child);
if (nextSiblingsLength !== 0) {
const newParent = cloneNode(parent);
child.insertAfter(newParent);
for (let i = 0; i < nextSiblingsLength; i++) {
newParent.append(nextSiblings[i]);
}
}
if (!parent.canBeEmpty() && parent.getChildrenSize() === 0) {
parent.remove();
}
}
}
};
return editor.registerNodeTransform(targetNode, $elementNodeTransform);
}
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
function $restoreEditorState(editor, editorState) {
const nodeMap = new Map();
const activeEditorState = editor._pendingEditorState;
for (const [key, node] of editorState._nodeMap) {
nodeMap.set(key, $cloneWithProperties(node));
}
if (activeEditorState) {
activeEditorState._nodeMap = nodeMap;
}
$fullReconcile();
const selection = editorState._selection;
$setSelection(selection === null ? null : selection.clone());
}
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
function $insertNodeToNearestRoot(node) {
const selection = $getSelection() || $getPreviousSelection();
let initialCaret;
if ($isRangeSelection(selection)) {
initialCaret = $caretFromPoint(selection.focus, 'next');
} else {
if (selection != null) {
const nodes = selection.getNodes();
const lastNode = nodes[nodes.length - 1];
if (lastNode) {
initialCaret = $getSiblingCaret(lastNode, 'next');
}
}
initialCaret = initialCaret || $getChildCaret($getRoot(), 'previous').getFlipped().insert($createParagraphNode());
}
const insertCaret = $insertNodeToNearestRootAtCaret(node, initialCaret);
const adjacent = $getAdjacentChildCaret(insertCaret);
const selectionCaret = $isChildCaret(adjacent) ? $normalizeCaret(adjacent) : insertCaret;
$setSelectionFromCaretRange($getCollapsedCaretRange(selectionCaret));
return node.getLatest();
}
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
function $insertNodeToNearestRootAtCaret(node, caret, options) {
let insertCaret = $getCaretInDirection(caret, 'next');
// Normalize boundary cases for TextPointCaret
if ($isTextPointCaret(insertCaret)) {
if (insertCaret.offset === 0) {
insertCaret = $getSiblingCaret(insertCaret.origin, 'previous').getFlipped();
} else if (insertCaret.offset === insertCaret.origin.getTextContentSize()) {
insertCaret = $getSiblingCaret(insertCaret.origin, 'next');
}
}
// Make sure we have a distinct node as the origin
if (insertCaret.origin.is(node)) {
if (!$isSiblingCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret node ${node.getKey()} of type ${node.getType()} can not be inserted into itself`);
}
insertCaret = $rewindSiblingCaret(insertCaret);
}
// Handle split boundary conditions where node is being inserted adjacent to itself
if (node.is(insertCaret.getNodeAtCaret()) || node.is(insertCaret.getFlipped().getNodeAtCaret())) {
node.remove(true);
}
for (let nextCaret = insertCaret; nextCaret; nextCaret = $splitAtPointCaretNext(nextCaret, options)) {
insertCaret = nextCaret;
}
if (!!$isTextPointCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret: An unattached TextNode can not be split`);
}
insertCaret.insert(node.isInline() ? $createParagraphNode().append(node) : node);
return $getCaretInDirection($getSiblingCaret(node.getLatest(), 'next'), caret.direction);
}
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
function $insertNodeIntoLeaf(node) {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
if (selection) {
selection.insertNodes([node]);
}
return;
}
const caretRange = $caretRangeFromSelection(selection);
let insertCaret = $getCaretRangeInDirection($removeTextFromCaretRange(caretRange), 'next').anchor;
if ($isTextPointCaret(insertCaret)) {
const nextAnchor = $splitAtPointCaretNext(insertCaret);
if (!nextAnchor) {
return;
}
insertCaret = nextAnchor;
}
const focus = insertCaret.getFlipped();
focus.insert(node);
$setSelectionFromCaretRange($getCaretRange(focus, focus));
}
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
function $wrapNodeInElement(node, createElementNode) {
const elementNode = createElementNode();
node.replace(elementNode);
elementNode.append(node);
return elementNode;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
function objectKlassEquals(object, objectClass) {
return object !== null ? Object.getPrototypeOf(object).constructor.name === objectClass.name : false;
}
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
function $filter(nodes, filterFn) {
const result = [];
for (let i = 0; i < nodes.length; i++) {
const node = filterFn(nodes[i]);
if (node !== null) {
result.push(node);
}
}
return result;
}
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
function $handleIndentAndOutdent(indentOrOutdent) {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const alreadyHandled = new Set();
const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getKey();
if (alreadyHandled.has(key)) {
continue;
}
const parentBlock = $findMatchingParent(node, parentNode => $isElementNode(parentNode) && !parentNode.isInline());
if (parentBlock === null) {
continue;
}
const parentKey = parentBlock.getKey();
if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
alreadyHandled.add(parentKey);
indentOrOutdent(parentBlock);
}
}
return alreadyHandled.size > 0;
}
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
function $insertFirst(parent, node) {
$getChildCaret(parent, 'next').insert(node);
}
let NEEDS_MANUAL_ZOOM = IS_FIREFOX || !CAN_USE_DOM ? false : undefined;
function needsManualZoom() {
if (NEEDS_MANUAL_ZOOM === undefined) {
// If the browser implements standardized CSS zoom, then the client rect
// will be wider after zoom is applied
// https://chromestatus.com/feature/5198254868529152
// https://github.com/facebook/lexical/issues/6863
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.opacity = '0';
div.style.width = '100px';
div.style.left = '-1000px';
document.body.appendChild(div);
const noZoom = div.getBoundingClientRect();
div.style.setProperty('zoom', '2');
NEEDS_MANUAL_ZOOM = div.getBoundingClientRect().width === noZoom.width;
document.body.removeChild(div);
}
return NEEDS_MANUAL_ZOOM;
}
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
function calculateZoomLevel(element, useManualZoom = false) {
let zoom = 1;
if (needsManualZoom() || useManualZoom) {
while (element) {
zoom *= Number(window.getComputedStyle(element).getPropertyValue('zoom'));
element = element.parentElement;
}
}
return zoom;
}
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
function $isEditorIsNestedEditor(editor) {
return editor._parentEditor !== null;
}
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
function $unwrapAndFilterDescendants(root, $predicate) {
return $unwrapAndFilterDescendantsImpl(root, $predicate, null);
}
function $unwrapAndFilterDescendantsImpl(root, $predicate, $onSuccess) {
let didMutate = false;
for (const node of $lastToFirstIterator(root)) {
if ($predicate(node)) {
if ($onSuccess !== null) {
$onSuccess(node);
}
continue;
}
didMutate = true;
if ($isElementNode(node)) {
$unwrapAndFilterDescendantsImpl(node, $predicate, $onSuccess || (child => node.insertAfter(child)));
}
node.remove();
}
return didMutate;
}
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
function $descendantsMatching(children, $predicate) {
const result = [];
const stack = Array.from(children).reverse();
for (let child = stack.pop(); child !== undefined; child = stack.pop()) {
if ($predicate(child)) {
result.push(child);
} else if ($isElementNode(child)) {
for (const grandchild of $lastToFirstIterator(child)) {
stack.push(grandchild);
}
}
}
return result;
}
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $firstToLastIterator(node) {
return $childIterator($getChildCaret(node, 'next'));
}
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $lastToFirstIterator(node) {
return $childIterator($getChildCaret(node, 'previous'));
}
function $childIterator(startCaret) {
const seen = new Set() ;
return makeStepwiseIterator({
hasNext: $isSiblingCaret,
initial: startCaret.getAdjacentCaret(),
map: caret => {
const origin = caret.origin.getLatest();
if (seen !== null) {
const key = origin.getKey();
if (!!seen.has(key)) {
formatDevErrorMessage(`$childIterator: Cycle detected, node with key ${String(key)} has already been traversed`);
}
seen.add(key);
}
return origin;
},
step: caret => caret.getAdjacentCaret()
});
}
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
function $unwrapNode(node) {
$rewindSiblingCaret($getSiblingCaret(node, 'next')).splice(1, node.getChildren());
}
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
function makeStateWrapper(stateConfig) {
const $get = node => $getState(node, stateConfig);
const $set = (node, valueOrUpdater) => $setState(node, stateConfig, valueOrUpdater);
return {
$get,
$set,
accessors: [$get, $set],
makeGetterMethod: () => function $getter() {
return $get(this);
},
makeSetterMethod: () => function $setter(valueOrUpdater) {
return $set(this, valueOrUpdater);
},
stateConfig
};
}
export { $descendantsMatching, $dfs, $dfsIterator, $filter, $firstToLastIterator, $getAdjacentCaret, $getDepth, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $getNextRightPreorderNode, $getNextSiblingOrParentSibling, $handleIndentAndOutdent, $insertFirst, $insertNodeIntoLeaf, $insertNodeToNearestRoot, $insertNodeToNearestRootAtCaret, $isEditorIsNestedEditor, $lastToFirstIterator, $restoreEditorState, $reverseDfs, $reverseDfsIterator, $unwrapAndFilterDescendants, $unwrapNode, $wrapNodeInElement, calculateZoomLevel, isMimeType, makeStateWrapper, markSelection, mediaFileReader, objectKlassEquals, mlcPositionNodeOnRange as positionNodeOnRange, registerNestedElementResolver, selectionAlwaysOnDisplay };
/**
* 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.
*
*/
'use strict'
const LexicalUtils = process.env.NODE_ENV !== 'production' ? require('./LexicalUtils.dev.js') : require('./LexicalUtils.prod.js');
module.exports = LexicalUtils;

Sorry, the diff of this file is not supported yet

/**
* 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 * as modDev from './LexicalUtils.dev.mjs';
import * as modProd from './LexicalUtils.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const $descendantsMatching = mod.$descendantsMatching;
export const $dfs = mod.$dfs;
export const $dfsIterator = mod.$dfsIterator;
export const $filter = mod.$filter;
export const $findMatchingParent = mod.$findMatchingParent;
export const $firstToLastIterator = mod.$firstToLastIterator;
export const $getAdjacentCaret = mod.$getAdjacentCaret;
export const $getAdjacentSiblingOrParentSiblingCaret = mod.$getAdjacentSiblingOrParentSiblingCaret;
export const $getDepth = mod.$getDepth;
export const $getNearestBlockElementAncestorOrThrow = mod.$getNearestBlockElementAncestorOrThrow;
export const $getNearestNodeOfType = mod.$getNearestNodeOfType;
export const $getNextRightPreorderNode = mod.$getNextRightPreorderNode;
export const $getNextSiblingOrParentSibling = mod.$getNextSiblingOrParentSibling;
export const $handleIndentAndOutdent = mod.$handleIndentAndOutdent;
export const $insertFirst = mod.$insertFirst;
export const $insertNodeIntoLeaf = mod.$insertNodeIntoLeaf;
export const $insertNodeToNearestRoot = mod.$insertNodeToNearestRoot;
export const $insertNodeToNearestRootAtCaret = mod.$insertNodeToNearestRootAtCaret;
export const $isEditorIsNestedEditor = mod.$isEditorIsNestedEditor;
export const $lastToFirstIterator = mod.$lastToFirstIterator;
export const $restoreEditorState = mod.$restoreEditorState;
export const $reverseDfs = mod.$reverseDfs;
export const $reverseDfsIterator = mod.$reverseDfsIterator;
export const $splitNode = mod.$splitNode;
export const $unwrapAndFilterDescendants = mod.$unwrapAndFilterDescendants;
export const $unwrapNode = mod.$unwrapNode;
export const $wrapNodeInElement = mod.$wrapNodeInElement;
export const CAN_USE_BEFORE_INPUT = mod.CAN_USE_BEFORE_INPUT;
export const CAN_USE_DOM = mod.CAN_USE_DOM;
export const IS_ANDROID = mod.IS_ANDROID;
export const IS_ANDROID_CHROME = mod.IS_ANDROID_CHROME;
export const IS_APPLE = mod.IS_APPLE;
export const IS_APPLE_WEBKIT = mod.IS_APPLE_WEBKIT;
export const IS_CHROME = mod.IS_CHROME;
export const IS_FIREFOX = mod.IS_FIREFOX;
export const IS_IOS = mod.IS_IOS;
export const IS_SAFARI = mod.IS_SAFARI;
export const addClassNamesToElement = mod.addClassNamesToElement;
export const calculateZoomLevel = mod.calculateZoomLevel;
export const isBlockDomNode = mod.isBlockDomNode;
export const isHTMLAnchorElement = mod.isHTMLAnchorElement;
export const isHTMLElement = mod.isHTMLElement;
export const isInlineDomNode = mod.isInlineDomNode;
export const isMimeType = mod.isMimeType;
export const makeStateWrapper = mod.makeStateWrapper;
export const markSelection = mod.markSelection;
export const mediaFileReader = mod.mediaFileReader;
export const mergeRegister = mod.mergeRegister;
export const objectKlassEquals = mod.objectKlassEquals;
export const positionNodeOnRange = mod.positionNodeOnRange;
export const registerNestedElementResolver = mod.registerNestedElementResolver;
export const removeClassNamesFromElement = mod.removeClassNamesFromElement;
export const selectionAlwaysOnDisplay = mod.selectionAlwaysOnDisplay;
/**
* 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.
*
*/
const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalUtils.dev.mjs') : import('./LexicalUtils.prod.mjs'));
export const $descendantsMatching = mod.$descendantsMatching;
export const $dfs = mod.$dfs;
export const $dfsIterator = mod.$dfsIterator;
export const $filter = mod.$filter;
export const $findMatchingParent = mod.$findMatchingParent;
export const $firstToLastIterator = mod.$firstToLastIterator;
export const $getAdjacentCaret = mod.$getAdjacentCaret;
export const $getAdjacentSiblingOrParentSiblingCaret = mod.$getAdjacentSiblingOrParentSiblingCaret;
export const $getDepth = mod.$getDepth;
export const $getNearestBlockElementAncestorOrThrow = mod.$getNearestBlockElementAncestorOrThrow;
export const $getNearestNodeOfType = mod.$getNearestNodeOfType;
export const $getNextRightPreorderNode = mod.$getNextRightPreorderNode;
export const $getNextSiblingOrParentSibling = mod.$getNextSiblingOrParentSibling;
export const $handleIndentAndOutdent = mod.$handleIndentAndOutdent;
export const $insertFirst = mod.$insertFirst;
export const $insertNodeIntoLeaf = mod.$insertNodeIntoLeaf;
export const $insertNodeToNearestRoot = mod.$insertNodeToNearestRoot;
export const $insertNodeToNearestRootAtCaret = mod.$insertNodeToNearestRootAtCaret;
export const $isEditorIsNestedEditor = mod.$isEditorIsNestedEditor;
export const $lastToFirstIterator = mod.$lastToFirstIterator;
export const $restoreEditorState = mod.$restoreEditorState;
export const $reverseDfs = mod.$reverseDfs;
export const $reverseDfsIterator = mod.$reverseDfsIterator;
export const $splitNode = mod.$splitNode;
export const $unwrapAndFilterDescendants = mod.$unwrapAndFilterDescendants;
export const $unwrapNode = mod.$unwrapNode;
export const $wrapNodeInElement = mod.$wrapNodeInElement;
export const CAN_USE_BEFORE_INPUT = mod.CAN_USE_BEFORE_INPUT;
export const CAN_USE_DOM = mod.CAN_USE_DOM;
export const IS_ANDROID = mod.IS_ANDROID;
export const IS_ANDROID_CHROME = mod.IS_ANDROID_CHROME;
export const IS_APPLE = mod.IS_APPLE;
export const IS_APPLE_WEBKIT = mod.IS_APPLE_WEBKIT;
export const IS_CHROME = mod.IS_CHROME;
export const IS_FIREFOX = mod.IS_FIREFOX;
export const IS_IOS = mod.IS_IOS;
export const IS_SAFARI = mod.IS_SAFARI;
export const addClassNamesToElement = mod.addClassNamesToElement;
export const calculateZoomLevel = mod.calculateZoomLevel;
export const isBlockDomNode = mod.isBlockDomNode;
export const isHTMLAnchorElement = mod.isHTMLAnchorElement;
export const isHTMLElement = mod.isHTMLElement;
export const isInlineDomNode = mod.isInlineDomNode;
export const isMimeType = mod.isMimeType;
export const makeStateWrapper = mod.makeStateWrapper;
export const markSelection = mod.markSelection;
export const mediaFileReader = mod.mediaFileReader;
export const mergeRegister = mod.mergeRegister;
export const objectKlassEquals = mod.objectKlassEquals;
export const positionNodeOnRange = mod.positionNodeOnRange;
export const registerNestedElementResolver = mod.registerNestedElementResolver;
export const removeClassNamesFromElement = mod.removeClassNamesFromElement;
export const selectionAlwaysOnDisplay = mod.selectionAlwaysOnDisplay;
/**
* 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.
*
*/
"use strict";var e=require("lexical"),t=require("@lexical/selection");function n(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function r(e){return`${e}px`}const o={attributes:!0,characterData:!0,childList:!0,subtree:!0};function i(i,s,l){let a=null,c=null,u=null,g=[];const d=document.createElement("div");function f(){null===a&&n(182),null===c&&n(183);const{left:e,top:o}=c.getBoundingClientRect(),u=t.createRectsFromDOMRange(i,s);var f,p;d.isConnected||(p=d,(f=c).insertBefore(p,f.firstChild));let $=!1;for(let t=0;t<u.length;t++){const n=u[t],i=g[t]||document.createElement("div"),s=i.style;"absolute"!==s.position&&(s.position="absolute",$=!0);const l=r(n.left-e);s.left!==l&&(s.left=l,$=!0);const a=r(n.top-o);s.top!==a&&(i.style.top=a,$=!0);const c=r(n.width);s.width!==c&&(i.style.width=c,$=!0);const f=r(n.height);s.height!==f&&(i.style.height=f,$=!0),i.parentNode!==d&&(d.append(i),$=!0),g[t]=i}for(;g.length>u.length;)g.pop();$&&l(g)}function p(){c=null,a=null,null!==u&&u.disconnect(),u=null,d.remove();for(const e of g)e.remove();g=[]}d.style.position="relative";const $=i.registerRootListener(function t(){const n=i.getRootElement();if(null===n)return p();const r=n.parentElement;if(!e.isHTMLElement(r))return p();p(),a=n,c=r,u=new MutationObserver(e=>{const n=i.getRootElement(),r=n&&n.parentElement;if(n!==a||r!==c)return t();for(const t of e)if(!d.contains(t.target))return f()}),u.observe(r,o),f()});return()=>{$(),p()}}function s(t,n,r,o){if("text"!==n.type&&e.$isElementNode(r)){const i=e.$getDOMSlot(r,o,t);return[i.element,i.getFirstChildOffset()+n.offset]}return[(e.$isTextNode(r)?e.$getDOMTextNode(r,o,t):e.getDOMTextNode(o))||o,n.offset]}function l(e){for(const t of e){const e=t.style;"Highlight"!==e.background&&(e.background="Highlight"),"HighlightText"!==e.color&&(e.color="HighlightText"),e.marginTop!==r(-1.5)&&(e.marginTop=r(-1.5)),e.paddingTop!==r(4)&&(e.paddingTop=r(4)),e.paddingBottom!==r(0)&&(e.paddingBottom=r(0))}}function a(t,n=l){let r=null,o=null,a=null,c=null,u=null,g=null,d=()=>{};function f(l){l.read(()=>{const l=e.$getSelection();if(!e.$isRangeSelection(l))return r=null,a=null,c=null,g=null,d(),void(d=()=>{});const[f,p]=function(e){const t=e.getStartEndPoints();return e.isBackward()?[t[1],t[0]]:t}(l),$=f.getNode(),m=$.getKey(),S=f.offset,C=p.getNode(),x=C.getKey(),h=p.offset,E=t.getElementByKey(m),N=t.getElementByKey(x),R=null===r||E!==o||S!==a||m!==r.getKey(),I=null===c||N!==u||h!==g||x!==c.getKey();if((R||I)&&null!==E&&null!==N){const e=function(e,t,n,r,o,i,l){const a=(e._window?e._window.document:document).createRange();return a.setStart(...s(e,t,n,r)),a.setEnd(...s(e,o,i,l)),a}(t,f,$,E,p,C,N);d(),d=i(t,e,n)}r=$,o=E,a=S,c=C,u=N,g=h},{editor:t})}return f(t.getEditorState()),e.mergeRegister(t.registerUpdateListener(({editorState:e})=>f(e)),()=>{d()})}function c(e,t){for(const n of t)if(e.type.startsWith(n))return!0;return!1}function u(e,t){return d("next",e,t)}function g(t,n){const r=e.$getAdjacentSiblingOrParentSiblingCaret(e.$getSiblingCaret(t,n));return r&&r[0]}function d(t,n,r){const o=e.$getRoot(),i=n||o,s=e.$isElementNode(i)?e.$getChildCaret(i,t):e.$getSiblingCaret(i,t),l=f(i),a=r?e.$getAdjacentChildCaret(e.$getChildCaretOrSelf(e.$getSiblingCaret(r,t)))||g(r,t):g(i,t);let c=l;return e.makeStepwiseIterator({hasNext:e=>null!==e,initial:s,map:e=>({depth:c,node:e.origin}),step:t=>{if(t.isSameNodeCaret(a))return null;e.$isChildCaret(t)&&c++;const n=e.$getAdjacentSiblingOrParentSiblingCaret(t);return!n||n[0].isSameNodeCaret(a)?null:(c+=n[1],n[0])}})}function f(e){let t=-1;for(let n=e;null!==n;n=n.getParent())t++;return t}function p(e,t){return d("previous",e,t)}function $(t,r,o){let i=e.$getCaretInDirection(r,"next");e.$isTextPointCaret(i)&&(0===i.offset?i=e.$getSiblingCaret(i.origin,"previous").getFlipped():i.offset===i.origin.getTextContentSize()&&(i=e.$getSiblingCaret(i.origin,"next"))),i.origin.is(t)&&(e.$isSiblingCaret(i)||n(342,t.getKey(),t.getType()),i=e.$rewindSiblingCaret(i)),(t.is(i.getNodeAtCaret())||t.is(i.getFlipped().getNodeAtCaret()))&&t.remove(!0);for(let t=i;t;t=e.$splitAtPointCaretNext(t,o))i=t;return e.$isTextPointCaret(i)&&n(283),i.insert(t.isInline()?e.$createParagraphNode().append(t):t),e.$getCaretInDirection(e.$getSiblingCaret(t.getLatest(),"next"),r.direction)}let m=!(e.IS_FIREFOX||!e.CAN_USE_DOM)&&void 0;function S(t,n,r){let o=!1;for(const i of C(t))n(i)?null!==r&&r(i):(o=!0,e.$isElementNode(i)&&S(i,n,r||(e=>i.insertAfter(e))),i.remove());return o}function C(t){return x(e.$getChildCaret(t,"previous"))}function x(t){return e.makeStepwiseIterator({hasNext:e.$isSiblingCaret,initial:t.getAdjacentCaret(),map:e=>e.origin.getLatest(),step:e=>e.getAdjacentCaret()})}exports.$findMatchingParent=e.$findMatchingParent,exports.$getAdjacentSiblingOrParentSiblingCaret=e.$getAdjacentSiblingOrParentSiblingCaret,exports.$splitNode=e.$splitNode,exports.CAN_USE_BEFORE_INPUT=e.CAN_USE_BEFORE_INPUT,exports.CAN_USE_DOM=e.CAN_USE_DOM,exports.IS_ANDROID=e.IS_ANDROID,exports.IS_ANDROID_CHROME=e.IS_ANDROID_CHROME,exports.IS_APPLE=e.IS_APPLE,exports.IS_APPLE_WEBKIT=e.IS_APPLE_WEBKIT,exports.IS_CHROME=e.IS_CHROME,exports.IS_FIREFOX=e.IS_FIREFOX,exports.IS_IOS=e.IS_IOS,exports.IS_SAFARI=e.IS_SAFARI,exports.addClassNamesToElement=e.addClassNamesToElement,exports.isBlockDomNode=e.isBlockDomNode,exports.isHTMLAnchorElement=e.isHTMLAnchorElement,exports.isHTMLElement=e.isHTMLElement,exports.isInlineDomNode=e.isInlineDomNode,exports.mergeRegister=e.mergeRegister,exports.removeClassNamesFromElement=e.removeClassNamesFromElement,exports.$descendantsMatching=function(t,n){const r=[],o=Array.from(t).reverse();for(let t=o.pop();void 0!==t;t=o.pop())if(n(t))r.push(t);else if(e.$isElementNode(t))for(const e of C(t))o.push(e);return r},exports.$dfs=function(e,t){return Array.from(u(e,t))},exports.$dfsIterator=u,exports.$filter=function(e,t){const n=[];for(let r=0;r<e.length;r++){const o=t(e[r]);null!==o&&n.push(o)}return n},exports.$firstToLastIterator=function(t){return x(e.$getChildCaret(t,"next"))},exports.$getAdjacentCaret=function(e){return e?e.getAdjacentCaret():null},exports.$getDepth=f,exports.$getNearestBlockElementAncestorOrThrow=function(t){const r=e.$findMatchingParent(t,t=>e.$isElementNode(t)&&!t.isInline());return e.$isElementNode(r)||n(4,t.__key),r},exports.$getNearestNodeOfType=function(e,t){let n=e;for(;null!=n;){if(n instanceof t)return n;n=n.getParent()}return null},exports.$getNextRightPreorderNode=function(t){const n=e.$getChildCaretOrSelf(e.$getSiblingCaret(t,"previous")),r=e.$getAdjacentSiblingOrParentSiblingCaret(n,"root");return r&&r[0].origin},exports.$getNextSiblingOrParentSibling=function(t){const n=e.$getAdjacentSiblingOrParentSiblingCaret(e.$getSiblingCaret(t,"next"));return n&&[n[0].origin,n[1]]},exports.$handleIndentAndOutdent=function(t){const n=e.$getSelection();if(!e.$isRangeSelection(n))return!1;const r=new Set,o=n.getNodes();for(let n=0;n<o.length;n++){const i=o[n],s=i.getKey();if(r.has(s))continue;const l=e.$findMatchingParent(i,t=>e.$isElementNode(t)&&!t.isInline());if(null===l)continue;const a=l.getKey();l.canIndent()&&!r.has(a)&&(r.add(a),t(l))}return r.size>0},exports.$insertFirst=function(t,n){e.$getChildCaret(t,"next").insert(n)},exports.$insertNodeIntoLeaf=function(t){const n=e.$getSelection();if(!e.$isRangeSelection(n))return void(n&&n.insertNodes([t]));const r=e.$caretRangeFromSelection(n);let o=e.$getCaretRangeInDirection(e.$removeTextFromCaretRange(r),"next").anchor;if(e.$isTextPointCaret(o)){const t=e.$splitAtPointCaretNext(o);if(!t)return;o=t}const i=o.getFlipped();i.insert(t),e.$setSelectionFromCaretRange(e.$getCaretRange(i,i))},exports.$insertNodeToNearestRoot=function(t){const n=e.$getSelection()||e.$getPreviousSelection();let r;if(e.$isRangeSelection(n))r=e.$caretFromPoint(n.focus,"next");else{if(null!=n){const t=n.getNodes(),o=t[t.length-1];o&&(r=e.$getSiblingCaret(o,"next"))}r=r||e.$getChildCaret(e.$getRoot(),"previous").getFlipped().insert(e.$createParagraphNode())}const o=$(t,r),i=e.$getAdjacentChildCaret(o),s=e.$isChildCaret(i)?e.$normalizeCaret(i):o;return e.$setSelectionFromCaretRange(e.$getCollapsedCaretRange(s)),t.getLatest()},exports.$insertNodeToNearestRootAtCaret=$,exports.$isEditorIsNestedEditor=function(e){return null!==e._parentEditor},exports.$lastToFirstIterator=C,exports.$restoreEditorState=function(t,n){const r=new Map,o=t._pendingEditorState;for(const[t,o]of n._nodeMap)r.set(t,e.$cloneWithProperties(o));o&&(o._nodeMap=r),e.$fullReconcile();const i=n._selection;e.$setSelection(null===i?null:i.clone())},exports.$reverseDfs=function(e,t){return Array.from(p(e,t))},exports.$reverseDfsIterator=p,exports.$unwrapAndFilterDescendants=function(e,t){return S(e,t,null)},exports.$unwrapNode=function(t){e.$rewindSiblingCaret(e.$getSiblingCaret(t,"next")).splice(1,t.getChildren())},exports.$wrapNodeInElement=function(e,t){const n=t();return e.replace(n),n.append(e),n},exports.calculateZoomLevel=function(e,t=!1){let n=1;if(function(){if(void 0===m){const e=document.createElement("div");e.style.position="absolute",e.style.opacity="0",e.style.width="100px",e.style.left="-1000px",document.body.appendChild(e);const t=e.getBoundingClientRect();e.style.setProperty("zoom","2"),m=e.getBoundingClientRect().width===t.width,document.body.removeChild(e)}return m}()||t)for(;e;)n*=Number(window.getComputedStyle(e).getPropertyValue("zoom")),e=e.parentElement;return n},exports.isMimeType=c,exports.makeStateWrapper=function(t){const n=n=>e.$getState(n,t),r=(n,r)=>e.$setState(n,t,r);return{$get:n,$set:r,accessors:[n,r],makeGetterMethod:()=>function(){return n(this)},makeSetterMethod:()=>function(e){return r(this,e)},stateConfig:t}},exports.markSelection=a,exports.mediaFileReader=function(e,t){const n=e[Symbol.iterator]();return new Promise((e,r)=>{const o=[],i=()=>{const{done:s,value:l}=n.next();if(s)return e(o);const a=new FileReader;a.addEventListener("error",r),a.addEventListener("load",()=>{const e=a.result;"string"==typeof e&&o.push({file:l,result:e}),i()}),c(l,t)?a.readAsDataURL(l):i()};i()})},exports.objectKlassEquals=function(e,t){return null!==e&&Object.getPrototypeOf(e).constructor.name===t.name},exports.positionNodeOnRange=i,exports.registerNestedElementResolver=function(e,t,n,r){const o=e=>e instanceof t;return e.registerNodeTransform(t,e=>{const t=(e=>{const t=e.getChildren();for(let e=0;e<t.length;e++){const n=t[e];if(o(n))return null}let n=e,r=e;for(;null!==n;)if(r=n,n=n.getParent(),o(n))return{child:r,parent:n};return null})(e);if(null!==t){const{child:o,parent:i}=t;if(o.is(e)){r(i,e);const t=o.getNextSiblings(),s=t.length;if(i.insertAfter(o),0!==s){const e=n(i);o.insertAfter(e);for(let n=0;n<s;n++)e.append(t[n])}i.canBeEmpty()||0!==i.getChildrenSize()||i.remove()}}})},exports.selectionAlwaysOnDisplay=function(e,t){let n=null;const r=()=>{const r=getSelection(),o=r&&r.anchorNode,i=e.getRootElement();null!==o&&null!==i&&i.contains(o)?null!==n&&(n(),n=null):null===n&&(n=a(e,t))};return e.registerRootListener(e=>{if(e){const t=e.ownerDocument;return t.addEventListener("selectionchange",r),r(),()=>{null!==n&&n(),t.removeEventListener("selectionchange",r)}}})};
/**
* 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{isHTMLElement as t,mergeRegister as e,$getSelection as n,$isRangeSelection as o,$isElementNode as r,$isTextNode as i,$getDOMTextNode as l,getDOMTextNode as s,$getDOMSlot as u,$getChildCaret as c,$findMatchingParent as f,$getChildCaretOrSelf as a,$getSiblingCaret as d,$getAdjacentSiblingOrParentSiblingCaret as g,$caretRangeFromSelection as p,$getCaretRangeInDirection as m,$removeTextFromCaretRange as h,$isTextPointCaret as y,$splitAtPointCaretNext as v,$setSelectionFromCaretRange as E,$getCaretRange as S,$getPreviousSelection as x,$caretFromPoint as C,$getRoot as N,$createParagraphNode as _,$getAdjacentChildCaret as w,$isChildCaret as A,$normalizeCaret as R,$getCollapsedCaretRange as I,$getCaretInDirection as L,$isSiblingCaret as b,$rewindSiblingCaret as P,$cloneWithProperties as O,$fullReconcile as T,$setSelection as B,makeStepwiseIterator as M,$getState as F,$setState as D,IS_FIREFOX as K,CAN_USE_DOM as H}from"lexical";export{$findMatchingParent,$getAdjacentSiblingOrParentSiblingCaret,$splitNode,CAN_USE_BEFORE_INPUT,CAN_USE_DOM,IS_ANDROID,IS_ANDROID_CHROME,IS_APPLE,IS_APPLE_WEBKIT,IS_CHROME,IS_FIREFOX,IS_IOS,IS_SAFARI,addClassNamesToElement,isBlockDomNode,isHTMLAnchorElement,isHTMLElement,isInlineDomNode,mergeRegister,removeClassNamesFromElement}from"lexical";import{createRectsFromDOMRange as $}from"@lexical/selection";function k(t,...e){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const t of e)o.append("v",t);throw n.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function U(t){return`${t}px`}const j={attributes:!0,characterData:!0,childList:!0,subtree:!0};function z(e,n,o){let r=null,i=null,l=null,s=[];const u=document.createElement("div");function c(){null===r&&k(182),null===i&&k(183);const{left:t,top:l}=i.getBoundingClientRect(),c=$(e,n);var f,a;u.isConnected||(a=u,(f=i).insertBefore(a,f.firstChild));let d=!1;for(let e=0;e<c.length;e++){const n=c[e],o=s[e]||document.createElement("div"),r=o.style;"absolute"!==r.position&&(r.position="absolute",d=!0);const i=U(n.left-t);r.left!==i&&(r.left=i,d=!0);const f=U(n.top-l);r.top!==f&&(o.style.top=f,d=!0);const a=U(n.width);r.width!==a&&(o.style.width=a,d=!0);const g=U(n.height);r.height!==g&&(o.style.height=g,d=!0),o.parentNode!==u&&(u.append(o),d=!0),s[e]=o}for(;s.length>c.length;)s.pop();d&&o(s)}function f(){i=null,r=null,null!==l&&l.disconnect(),l=null,u.remove();for(const t of s)t.remove();s=[]}u.style.position="relative";const a=e.registerRootListener(function n(){const o=e.getRootElement();if(null===o)return f();const s=o.parentElement;if(!t(s))return f();f(),r=o,i=s,l=new MutationObserver(t=>{const o=e.getRootElement(),l=o&&o.parentElement;if(o!==r||l!==i)return n();for(const e of t)if(!u.contains(e.target))return c()}),l.observe(s,j),c()});return()=>{a(),f()}}function W(t,e,n,o){if("text"!==e.type&&r(n)){const r=u(n,o,t);return[r.element,r.getFirstChildOffset()+e.offset]}return[(i(n)?l(n,o,t):s(o))||o,e.offset]}function G(t){for(const e of t){const t=e.style;"Highlight"!==t.background&&(t.background="Highlight"),"HighlightText"!==t.color&&(t.color="HighlightText"),t.marginTop!==U(-1.5)&&(t.marginTop=U(-1.5)),t.paddingTop!==U(4)&&(t.paddingTop=U(4)),t.paddingBottom!==U(0)&&(t.paddingBottom=U(0))}}function V(t,r=G){let i=null,l=null,s=null,u=null,c=null,f=null,a=()=>{};function d(e){e.read(()=>{const e=n();if(!o(e))return i=null,s=null,u=null,f=null,a(),void(a=()=>{});const[d,g]=function(t){const e=t.getStartEndPoints();return t.isBackward()?[e[1],e[0]]:e}(e),p=d.getNode(),m=p.getKey(),h=d.offset,y=g.getNode(),v=y.getKey(),E=g.offset,S=t.getElementByKey(m),x=t.getElementByKey(v),C=null===i||S!==l||h!==s||m!==i.getKey(),N=null===u||x!==c||E!==f||v!==u.getKey();if((C||N)&&null!==S&&null!==x){const e=function(t,e,n,o,r,i,l){const s=(t._window?t._window.document:document).createRange();return s.setStart(...W(t,e,n,o)),s.setEnd(...W(t,r,i,l)),s}(t,d,p,S,g,y,x);a(),a=z(t,e,r)}i=p,l=S,s=h,u=y,c=x,f=E},{editor:t})}return d(t.getEditorState()),e(t.registerUpdateListener(({editorState:t})=>d(t)),()=>{a()})}function X(t,e){let n=null;const o=()=>{const o=getSelection(),r=o&&o.anchorNode,i=t.getRootElement();null!==r&&null!==i&&i.contains(r)?null!==n&&(n(),n=null):null===n&&(n=V(t,e))};return t.registerRootListener(t=>{if(t){const e=t.ownerDocument;return e.addEventListener("selectionchange",o),o(),()=>{null!==n&&n(),e.removeEventListener("selectionchange",o)}}})}function q(t,e){for(const n of e)if(t.type.startsWith(n))return!0;return!1}function J(t,e){const n=t[Symbol.iterator]();return new Promise((t,o)=>{const r=[],i=()=>{const{done:l,value:s}=n.next();if(l)return t(r);const u=new FileReader;u.addEventListener("error",o),u.addEventListener("load",()=>{const t=u.result;"string"==typeof t&&r.push({file:s,result:t}),i()}),q(s,e)?u.readAsDataURL(s):i()};i()})}function Q(t,e){return Array.from(tt(t,e))}function Y(t){return t?t.getAdjacentCaret():null}function Z(t,e){return Array.from(lt(t,e))}function tt(t,e){return nt("next",t,e)}function et(t,e){const n=g(d(t,e));return n&&n[0]}function nt(t,e,n){const o=N(),i=e||o,l=r(i)?c(i,t):d(i,t),s=rt(i),u=n?w(a(d(n,t)))||et(n,t):et(i,t);let f=s;return M({hasNext:t=>null!==t,initial:l,map:t=>({depth:f,node:t.origin}),step:t=>{if(t.isSameNodeCaret(u))return null;A(t)&&f++;const e=g(t);return!e||e[0].isSameNodeCaret(u)?null:(f+=e[1],e[0])}})}function ot(t){const e=g(d(t,"next"));return e&&[e[0].origin,e[1]]}function rt(t){let e=-1;for(let n=t;null!==n;n=n.getParent())e++;return e}function it(t){const e=a(d(t,"previous")),n=g(e,"root");return n&&n[0].origin}function lt(t,e){return nt("previous",t,e)}function st(t,e){let n=t;for(;null!=n;){if(n instanceof e)return n;n=n.getParent()}return null}function ut(t){const e=f(t,t=>r(t)&&!t.isInline());return r(e)||k(4,t.__key),e}function ct(t,e,n,o){const r=t=>t instanceof e;return t.registerNodeTransform(e,t=>{const e=(t=>{const e=t.getChildren();for(let t=0;t<e.length;t++){const n=e[t];if(r(n))return null}let n=t,o=t;for(;null!==n;)if(o=n,n=n.getParent(),r(n))return{child:o,parent:n};return null})(t);if(null!==e){const{child:r,parent:i}=e;if(r.is(t)){o(i,t);const e=r.getNextSiblings(),l=e.length;if(i.insertAfter(r),0!==l){const t=n(i);r.insertAfter(t);for(let n=0;n<l;n++)t.append(e[n])}i.canBeEmpty()||0!==i.getChildrenSize()||i.remove()}}})}function ft(t,e){const n=new Map,o=t._pendingEditorState;for(const[t,o]of e._nodeMap)n.set(t,O(o));o&&(o._nodeMap=n),T();const r=e._selection;B(null===r?null:r.clone())}function at(t){const e=n()||x();let r;if(o(e))r=C(e.focus,"next");else{if(null!=e){const t=e.getNodes(),n=t[t.length-1];n&&(r=d(n,"next"))}r=r||c(N(),"previous").getFlipped().insert(_())}const i=dt(t,r),l=w(i),s=A(l)?R(l):i;return E(I(s)),t.getLatest()}function dt(t,e,n){let o=L(e,"next");y(o)&&(0===o.offset?o=d(o.origin,"previous").getFlipped():o.offset===o.origin.getTextContentSize()&&(o=d(o.origin,"next"))),o.origin.is(t)&&(b(o)||k(342,t.getKey(),t.getType()),o=P(o)),(t.is(o.getNodeAtCaret())||t.is(o.getFlipped().getNodeAtCaret()))&&t.remove(!0);for(let t=o;t;t=v(t,n))o=t;return y(o)&&k(283),o.insert(t.isInline()?_().append(t):t),L(d(t.getLatest(),"next"),e.direction)}function gt(t){const e=n();if(!o(e))return void(e&&e.insertNodes([t]));const r=p(e);let i=m(h(r),"next").anchor;if(y(i)){const t=v(i);if(!t)return;i=t}const l=i.getFlipped();l.insert(t),E(S(l,l))}function pt(t,e){const n=e();return t.replace(n),n.append(t),n}function mt(t,e){return null!==t&&Object.getPrototypeOf(t).constructor.name===e.name}function ht(t,e){const n=[];for(let o=0;o<t.length;o++){const r=e(t[o]);null!==r&&n.push(r)}return n}function yt(t){const e=n();if(!o(e))return!1;const i=new Set,l=e.getNodes();for(let e=0;e<l.length;e++){const n=l[e],o=n.getKey();if(i.has(o))continue;const s=f(n,t=>r(t)&&!t.isInline());if(null===s)continue;const u=s.getKey();s.canIndent()&&!i.has(u)&&(i.add(u),t(s))}return i.size>0}function vt(t,e){c(t,"next").insert(e)}let Et=!(K||!H)&&void 0;function St(t,e=!1){let n=1;if(function(){if(void 0===Et){const t=document.createElement("div");t.style.position="absolute",t.style.opacity="0",t.style.width="100px",t.style.left="-1000px",document.body.appendChild(t);const e=t.getBoundingClientRect();t.style.setProperty("zoom","2"),Et=t.getBoundingClientRect().width===e.width,document.body.removeChild(t)}return Et}()||e)for(;t;)n*=Number(window.getComputedStyle(t).getPropertyValue("zoom")),t=t.parentElement;return n}function xt(t){return null!==t._parentEditor}function Ct(t,e){return Nt(t,e,null)}function Nt(t,e,n){let o=!1;for(const i of At(t))e(i)?null!==n&&n(i):(o=!0,r(i)&&Nt(i,e,n||(t=>i.insertAfter(t))),i.remove());return o}function _t(t,e){const n=[],o=Array.from(t).reverse();for(let t=o.pop();void 0!==t;t=o.pop())if(e(t))n.push(t);else if(r(t))for(const e of At(t))o.push(e);return n}function wt(t){return Rt(c(t,"next"))}function At(t){return Rt(c(t,"previous"))}function Rt(t){return M({hasNext:b,initial:t.getAdjacentCaret(),map:t=>t.origin.getLatest(),step:t=>t.getAdjacentCaret()})}function It(t){P(d(t,"next")).splice(1,t.getChildren())}function Lt(t){const e=e=>F(e,t),n=(e,n)=>D(e,t,n);return{$get:e,$set:n,accessors:[e,n],makeGetterMethod:()=>function(){return e(this)},makeSetterMethod:()=>function(t){return n(this,t)},stateConfig:t}}export{_t as $descendantsMatching,Q as $dfs,tt as $dfsIterator,ht as $filter,wt as $firstToLastIterator,Y as $getAdjacentCaret,rt as $getDepth,ut as $getNearestBlockElementAncestorOrThrow,st as $getNearestNodeOfType,it as $getNextRightPreorderNode,ot as $getNextSiblingOrParentSibling,yt as $handleIndentAndOutdent,vt as $insertFirst,gt as $insertNodeIntoLeaf,at as $insertNodeToNearestRoot,dt as $insertNodeToNearestRootAtCaret,xt as $isEditorIsNestedEditor,At as $lastToFirstIterator,ft as $restoreEditorState,Z as $reverseDfs,lt as $reverseDfsIterator,Ct as $unwrapAndFilterDescendants,It as $unwrapNode,pt as $wrapNodeInElement,St as calculateZoomLevel,q as isMimeType,Lt as makeStateWrapper,V as markSelection,J as mediaFileReader,mt as objectKlassEquals,z as positionNodeOnRange,ct as registerNestedElementResolver,X as selectionAlwaysOnDisplay};
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { type LexicalEditor } from 'lexical';
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
export default function markSelection(editor: LexicalEditor, onReposition?: (node: readonly HTMLElement[]) => void): () => void;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { type LexicalEditor } from 'lexical';
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
export default function mlcPositionNodeOnRange(editor: LexicalEditor, range: Range, onReposition: (node: Array<HTMLElement>) => void): () => void;
/**
* 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.
*
*/
export default function px(value: number): string;
/**
* 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 { LexicalEditor } from 'lexical';
export default function selectionAlwaysOnDisplay(editor: LexicalEditor, onReposition?: (node: readonly HTMLElement[]) => void): () => void;
/**
* 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 invariant from '@lexical/internal/invariant';
import {
CAN_USE_BEFORE_INPUT,
CAN_USE_DOM,
IS_ANDROID,
IS_ANDROID_CHROME,
IS_APPLE,
IS_APPLE_WEBKIT,
IS_CHROME,
IS_FIREFOX,
IS_IOS,
IS_SAFARI,
} from 'lexical';
import {
$caretFromPoint,
$caretRangeFromSelection,
$cloneWithProperties,
$createParagraphNode,
$findMatchingParent,
$fullReconcile,
$getAdjacentChildCaret,
$getAdjacentSiblingOrParentSiblingCaret,
$getCaretInDirection,
$getCaretRange,
$getCaretRangeInDirection,
$getChildCaret,
$getChildCaretOrSelf,
$getCollapsedCaretRange,
$getPreviousSelection,
$getRoot,
$getSelection,
$getSiblingCaret,
$getState,
$isChildCaret,
$isElementNode,
$isRangeSelection,
$isSiblingCaret,
$isTextPointCaret,
$normalizeCaret,
$removeTextFromCaretRange,
$rewindSiblingCaret,
$setSelection,
$setSelectionFromCaretRange,
$setState,
$splitAtPointCaretNext,
type CaretDirection,
type EditorState,
ElementNode,
type Klass,
type LexicalEditor,
type LexicalNode,
makeStepwiseIterator,
type NodeCaret,
type NodeKey,
PointCaret,
type SiblingCaret,
SplitAtPointCaretNextOptions,
StateConfig,
ValueOrUpdater,
} from 'lexical';
export {default as markSelection} from './markSelection';
export {default as positionNodeOnRange} from './positionNodeOnRange';
export {default as selectionAlwaysOnDisplay} from './selectionAlwaysOnDisplay';
export {
$findMatchingParent,
$getAdjacentSiblingOrParentSiblingCaret,
$splitNode,
addClassNamesToElement,
isBlockDomNode,
isHTMLAnchorElement,
isHTMLElement,
isInlineDomNode,
mergeRegister,
removeClassNamesFromElement,
} from 'lexical';
const __DEV__ = process.env.NODE_ENV !== 'production';
export {
CAN_USE_BEFORE_INPUT,
CAN_USE_DOM,
IS_ANDROID,
IS_ANDROID_CHROME,
IS_APPLE,
IS_APPLE_WEBKIT,
IS_CHROME,
IS_FIREFOX,
IS_IOS,
IS_SAFARI,
};
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
export function isMimeType(
file: File,
acceptableMimeTypes: Array<string>,
): boolean {
for (const acceptableType of acceptableMimeTypes) {
if (file.type.startsWith(acceptableType)) {
return true;
}
}
return false;
}
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
export function mediaFileReader(
files: Array<File>,
acceptableMimeTypes: Array<string>,
): Promise<Array<{file: File; result: string}>> {
const filesIterator = files[Symbol.iterator]();
return new Promise((resolve, reject) => {
const processed: Array<{file: File; result: string}> = [];
const handleNextFile = () => {
const {done, value: file} = filesIterator.next();
if (done) {
return resolve(processed);
}
const fileReader = new FileReader();
fileReader.addEventListener('error', reject);
fileReader.addEventListener('load', () => {
const result = fileReader.result;
if (typeof result === 'string') {
processed.push({file, result});
}
handleNextFile();
});
if (isMimeType(file, acceptableMimeTypes)) {
fileReader.readAsDataURL(file);
} else {
handleNextFile();
}
};
handleNextFile();
});
}
export interface DFSNode {
readonly depth: number;
readonly node: LexicalNode;
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
export function $dfs(
startNode?: LexicalNode,
endNode?: LexicalNode,
): Array<DFSNode> {
return Array.from($dfsIterator(startNode, endNode));
}
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
export function $getAdjacentCaret<D extends CaretDirection>(
caret: null | NodeCaret<D>,
): null | SiblingCaret<LexicalNode, D> {
return caret ? caret.getAdjacentCaret() : null;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export function $reverseDfs(
startNode?: LexicalNode,
endNode?: LexicalNode,
): Array<DFSNode> {
return Array.from($reverseDfsIterator(startNode, endNode));
}
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export function $dfsIterator(
startNode?: LexicalNode,
endNode?: LexicalNode,
): IterableIterator<DFSNode> {
return $dfsCaretIterator('next', startNode, endNode);
}
function $getEndCaret<D extends CaretDirection>(
startNode: LexicalNode,
direction: D,
): null | NodeCaret<D> {
const rval = $getAdjacentSiblingOrParentSiblingCaret(
$getSiblingCaret(startNode, direction),
);
return rval && rval[0];
}
function $dfsCaretIterator<D extends CaretDirection>(
direction: D,
startNode?: LexicalNode,
endNode?: LexicalNode,
): IterableIterator<DFSNode> {
const root = $getRoot();
const start = startNode || root;
const startCaret = $isElementNode(start)
? $getChildCaret(start, direction)
: $getSiblingCaret(start, direction);
const startDepth = $getDepth(start);
const endCaret = endNode
? $getAdjacentChildCaret(
$getChildCaretOrSelf($getSiblingCaret(endNode, direction)),
) || $getEndCaret(endNode, direction)
: $getEndCaret(start, direction);
let depth = startDepth;
return makeStepwiseIterator({
hasNext: (state): state is NodeCaret<'next'> => state !== null,
initial: startCaret,
map: state => ({depth, node: state.origin}),
step: (state: NodeCaret<'next'>) => {
if (state.isSameNodeCaret(endCaret)) {
return null;
}
if ($isChildCaret(state)) {
depth++;
}
const rval = $getAdjacentSiblingOrParentSiblingCaret(state);
if (!rval || rval[0].isSameNodeCaret(endCaret)) {
return null;
}
depth += rval[1];
return rval[0];
},
});
}
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
export function $getNextSiblingOrParentSibling(
node: LexicalNode,
): null | [LexicalNode, number] {
const rval = $getAdjacentSiblingOrParentSiblingCaret(
$getSiblingCaret(node, 'next'),
);
return rval && [rval[0].origin, rval[1]];
}
export function $getDepth(node: null | LexicalNode): number {
let depth = -1;
for (
let innerNode = node;
innerNode !== null;
innerNode = innerNode.getParent()
) {
depth++;
}
return depth;
}
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
export function $getNextRightPreorderNode(
startingNode: LexicalNode,
): LexicalNode | null {
const startCaret = $getChildCaretOrSelf(
$getSiblingCaret(startingNode, 'previous'),
);
const next = $getAdjacentSiblingOrParentSiblingCaret(startCaret, 'root');
return next && next[0].origin;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export function $reverseDfsIterator(
startNode?: LexicalNode,
endNode?: LexicalNode,
): IterableIterator<DFSNode> {
return $dfsCaretIterator('previous', startNode, endNode);
}
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
export function $getNearestNodeOfType<T extends ElementNode>(
node: LexicalNode,
klass: Klass<T>,
): T | null {
let parent: ElementNode | LexicalNode | null = node;
while (parent != null) {
if (parent instanceof klass) {
return parent as T;
}
parent = parent.getParent();
}
return null;
}
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
export function $getNearestBlockElementAncestorOrThrow(
startNode: LexicalNode,
): ElementNode {
const blockNode = $findMatchingParent(
startNode,
node => $isElementNode(node) && !node.isInline(),
);
if (!$isElementNode(blockNode)) {
invariant(
false,
'Expected node %s to have closest block element node.',
startNode.__key,
);
}
return blockNode;
}
export type DOMNodeToLexicalConversion = (element: Node) => LexicalNode;
export type DOMNodeToLexicalConversionMap = Record<
string,
DOMNodeToLexicalConversion
>;
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
export function registerNestedElementResolver<N extends ElementNode>(
editor: LexicalEditor,
targetNode: Klass<N>,
cloneNode: (from: N) => N,
handleOverlap: (from: N, to: N) => void,
): () => void {
const $isTargetNode = (node: LexicalNode | null | undefined): node is N => {
return node instanceof targetNode;
};
const $findMatch = (node: N): {child: ElementNode; parent: N} | null => {
// First validate we don't have any children that are of the target,
// as we need to handle them first.
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if ($isTargetNode(child)) {
return null;
}
}
let parentNode: N | null = node;
let childNode = node;
while (parentNode !== null) {
childNode = parentNode;
parentNode = parentNode.getParent();
if ($isTargetNode(parentNode)) {
return {child: childNode, parent: parentNode};
}
}
return null;
};
const $elementNodeTransform = (node: N) => {
const match = $findMatch(node);
if (match !== null) {
const {child, parent} = match;
// Simple path, we can move child out and siblings into a new parent.
if (child.is(node)) {
handleOverlap(parent, node);
const nextSiblings = child.getNextSiblings();
const nextSiblingsLength = nextSiblings.length;
parent.insertAfter(child);
if (nextSiblingsLength !== 0) {
const newParent = cloneNode(parent);
child.insertAfter(newParent);
for (let i = 0; i < nextSiblingsLength; i++) {
newParent.append(nextSiblings[i]);
}
}
if (!parent.canBeEmpty() && parent.getChildrenSize() === 0) {
parent.remove();
}
} else {
// Complex path, we have a deep node that isn't a child of the
// target parent.
// TODO: implement this functionality
}
}
};
return editor.registerNodeTransform(targetNode, $elementNodeTransform);
}
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
export function $restoreEditorState(
editor: LexicalEditor,
editorState: EditorState,
): void {
const nodeMap = new Map();
const activeEditorState = editor._pendingEditorState;
for (const [key, node] of editorState._nodeMap) {
nodeMap.set(key, $cloneWithProperties(node));
}
if (activeEditorState) {
activeEditorState._nodeMap = nodeMap;
}
$fullReconcile();
const selection = editorState._selection;
$setSelection(selection === null ? null : selection.clone());
}
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
export function $insertNodeToNearestRoot<T extends LexicalNode>(node: T): T {
const selection = $getSelection() || $getPreviousSelection();
let initialCaret: undefined | PointCaret<'next'>;
if ($isRangeSelection(selection)) {
initialCaret = $caretFromPoint(selection.focus, 'next');
} else {
if (selection != null) {
const nodes = selection.getNodes();
const lastNode = nodes[nodes.length - 1];
if (lastNode) {
initialCaret = $getSiblingCaret(lastNode, 'next');
}
}
initialCaret =
initialCaret ||
$getChildCaret($getRoot(), 'previous')
.getFlipped()
.insert($createParagraphNode());
}
const insertCaret = $insertNodeToNearestRootAtCaret(node, initialCaret);
const adjacent = $getAdjacentChildCaret(insertCaret);
const selectionCaret = $isChildCaret(adjacent)
? $normalizeCaret(adjacent)
: insertCaret;
$setSelectionFromCaretRange($getCollapsedCaretRange(selectionCaret));
return node.getLatest();
}
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
export function $insertNodeToNearestRootAtCaret<
T extends LexicalNode,
D extends CaretDirection,
>(
node: T,
caret: PointCaret<D>,
options?: SplitAtPointCaretNextOptions,
): NodeCaret<D> {
let insertCaret: PointCaret<'next'> = $getCaretInDirection(caret, 'next');
// Normalize boundary cases for TextPointCaret
if ($isTextPointCaret(insertCaret)) {
if (insertCaret.offset === 0) {
insertCaret = $getSiblingCaret(
insertCaret.origin,
'previous',
).getFlipped();
} else if (insertCaret.offset === insertCaret.origin.getTextContentSize()) {
insertCaret = $getSiblingCaret(insertCaret.origin, 'next');
}
}
// Make sure we have a distinct node as the origin
if (insertCaret.origin.is(node)) {
invariant(
$isSiblingCaret(insertCaret),
'$insertNodeToNearestRootAtCaret node %s of type %s can not be inserted into itself',
node.getKey(),
node.getType(),
);
insertCaret = $rewindSiblingCaret(insertCaret);
}
// Handle split boundary conditions where node is being inserted adjacent to itself
if (
node.is(insertCaret.getNodeAtCaret()) ||
node.is(insertCaret.getFlipped().getNodeAtCaret())
) {
node.remove(true);
}
for (
let nextCaret: null | PointCaret<'next'> = insertCaret;
nextCaret;
nextCaret = $splitAtPointCaretNext(nextCaret, options)
) {
insertCaret = nextCaret;
}
invariant(
!$isTextPointCaret(insertCaret),
'$insertNodeToNearestRootAtCaret: An unattached TextNode can not be split',
);
insertCaret.insert(
node.isInline() ? $createParagraphNode().append(node) : node,
);
return $getCaretInDirection(
$getSiblingCaret(node.getLatest(), 'next'),
caret.direction,
);
}
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
export function $insertNodeIntoLeaf(node: LexicalNode): void {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
if (selection) {
selection.insertNodes([node]);
}
return;
}
const caretRange = $caretRangeFromSelection(selection);
let insertCaret = $getCaretRangeInDirection(
$removeTextFromCaretRange(caretRange),
'next',
).anchor;
if ($isTextPointCaret(insertCaret)) {
const nextAnchor = $splitAtPointCaretNext(insertCaret);
if (!nextAnchor) {
return;
}
insertCaret = nextAnchor;
}
const focus = insertCaret.getFlipped();
focus.insert(node);
$setSelectionFromCaretRange($getCaretRange(focus, focus));
}
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
export function $wrapNodeInElement(
node: LexicalNode,
createElementNode: () => ElementNode,
): ElementNode {
const elementNode = createElementNode();
node.replace(elementNode);
elementNode.append(node);
return elementNode;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ObjectKlass<T> = new (...args: any[]) => T;
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
export function objectKlassEquals<T>(
object: unknown,
objectClass: ObjectKlass<T>,
): object is T {
return object !== null
? Object.getPrototypeOf(object).constructor.name === objectClass.name
: false;
}
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
export function $filter<T>(
nodes: Array<LexicalNode>,
filterFn: (node: LexicalNode) => null | T,
): Array<T> {
const result: T[] = [];
for (let i = 0; i < nodes.length; i++) {
const node = filterFn(nodes[i]);
if (node !== null) {
result.push(node);
}
}
return result;
}
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
export function $handleIndentAndOutdent(
indentOrOutdent: (block: ElementNode) => void,
): boolean {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const alreadyHandled = new Set();
const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getKey();
if (alreadyHandled.has(key)) {
continue;
}
const parentBlock = $findMatchingParent(
node,
(parentNode): parentNode is ElementNode =>
$isElementNode(parentNode) && !parentNode.isInline(),
);
if (parentBlock === null) {
continue;
}
const parentKey = parentBlock.getKey();
if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
alreadyHandled.add(parentKey);
indentOrOutdent(parentBlock);
}
}
return alreadyHandled.size > 0;
}
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
export function $insertFirst(parent: ElementNode, node: LexicalNode): void {
$getChildCaret(parent, 'next').insert(node);
}
let NEEDS_MANUAL_ZOOM = IS_FIREFOX || !CAN_USE_DOM ? false : undefined;
function needsManualZoom(): boolean {
if (NEEDS_MANUAL_ZOOM === undefined) {
// If the browser implements standardized CSS zoom, then the client rect
// will be wider after zoom is applied
// https://chromestatus.com/feature/5198254868529152
// https://github.com/facebook/lexical/issues/6863
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.opacity = '0';
div.style.width = '100px';
div.style.left = '-1000px';
document.body.appendChild(div);
const noZoom = div.getBoundingClientRect();
div.style.setProperty('zoom', '2');
NEEDS_MANUAL_ZOOM = div.getBoundingClientRect().width === noZoom.width;
document.body.removeChild(div);
}
return NEEDS_MANUAL_ZOOM;
}
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
export function calculateZoomLevel(
element: Element | null,
useManualZoom: boolean = false,
): number {
let zoom = 1;
if (needsManualZoom() || useManualZoom) {
while (element) {
zoom *= Number(window.getComputedStyle(element).getPropertyValue('zoom'));
element = element.parentElement;
}
}
return zoom;
}
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
export function $isEditorIsNestedEditor(editor: LexicalEditor): boolean {
return editor._parentEditor !== null;
}
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
export function $unwrapAndFilterDescendants(
root: ElementNode,
$predicate: (node: LexicalNode) => boolean,
): boolean {
return $unwrapAndFilterDescendantsImpl(root, $predicate, null);
}
function $unwrapAndFilterDescendantsImpl(
root: ElementNode,
$predicate: (node: LexicalNode) => boolean,
$onSuccess: null | ((node: LexicalNode) => void),
): boolean {
let didMutate = false;
for (const node of $lastToFirstIterator(root)) {
if ($predicate(node)) {
if ($onSuccess !== null) {
$onSuccess(node);
}
continue;
}
didMutate = true;
if ($isElementNode(node)) {
$unwrapAndFilterDescendantsImpl(
node,
$predicate,
$onSuccess || (child => node.insertAfter(child)),
);
}
node.remove();
}
return didMutate;
}
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
export function $descendantsMatching<T extends LexicalNode>(
children: LexicalNode[],
$predicate: (node: LexicalNode) => node is T,
): T[];
export function $descendantsMatching(
children: LexicalNode[],
$predicate: (node: LexicalNode) => boolean,
): LexicalNode[] {
const result: LexicalNode[] = [];
const stack = Array.from(children).reverse();
for (let child = stack.pop(); child !== undefined; child = stack.pop()) {
if ($predicate(child)) {
result.push(child);
} else if ($isElementNode(child)) {
for (const grandchild of $lastToFirstIterator(child)) {
stack.push(grandchild);
}
}
}
return result;
}
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
export function $firstToLastIterator(node: ElementNode): Iterable<LexicalNode> {
return $childIterator($getChildCaret(node, 'next'));
}
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
export function $lastToFirstIterator(node: ElementNode): Iterable<LexicalNode> {
return $childIterator($getChildCaret(node, 'previous'));
}
function $childIterator<D extends CaretDirection>(
startCaret: NodeCaret<D>,
): IterableIterator<LexicalNode> {
const seen = __DEV__ ? new Set<NodeKey>() : null;
return makeStepwiseIterator({
hasNext: $isSiblingCaret,
initial: startCaret.getAdjacentCaret(),
map: caret => {
const origin = caret.origin.getLatest();
if (__DEV__ && seen !== null) {
const key = origin.getKey();
invariant(
!seen.has(key),
'$childIterator: Cycle detected, node with key %s has already been traversed',
String(key),
);
seen.add(key);
}
return origin;
},
step: (caret: SiblingCaret<LexicalNode, D>) => caret.getAdjacentCaret(),
});
}
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
export function $unwrapNode(node: ElementNode): void {
$rewindSiblingCaret($getSiblingCaret(node, 'next')).splice(
1,
node.getChildren(),
);
}
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
export interface StateConfigWrapper<K extends string, V> {
/** A reference to the stateConfig */
readonly stateConfig: StateConfig<K, V>;
/** `(node) => $getState(node, stateConfig)` */
readonly $get: <T extends LexicalNode>(node: T) => V;
/** `(node, valueOrUpdater) => $setState(node, stateConfig, valueOrUpdater)` */
readonly $set: <T extends LexicalNode>(
node: T,
valueOrUpdater: ValueOrUpdater<V>,
) => T;
/** `[$get, $set]` */
readonly accessors: readonly [$get: this['$get'], $set: this['$set']];
/**
* `() => function () { return $get(this) }`
*
* Should be called with an explicit `this` type parameter.
*
* @example
* ```ts
* class MyNode {
* // …
* myGetter = myWrapper.makeGetterMethod<this>();
* }
* ```
*/
makeGetterMethod<T extends LexicalNode>(): (this: T) => V;
/**
* `() => function (valueOrUpdater) { return $set(this, valueOrUpdater) }`
*
* Must be called with an explicit `this` type parameter.
*
* @example
* ```ts
* class MyNode {
* // …
* mySetter = myWrapper.makeSetterMethod<this>();
* }
* ```
*/
makeSetterMethod<T extends LexicalNode>(): (
this: T,
valueOrUpdater: ValueOrUpdater<V>,
) => T;
}
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
export function makeStateWrapper<K extends string, V>(
stateConfig: StateConfig<K, V>,
): StateConfigWrapper<K, V> {
const $get: StateConfigWrapper<K, V>['$get'] = node =>
$getState(node, stateConfig);
const $set: StateConfigWrapper<K, V>['$set'] = (node, valueOrUpdater) =>
$setState(node, stateConfig, valueOrUpdater);
return {
$get,
$set,
accessors: [$get, $set],
makeGetterMethod: () =>
function $getter() {
return $get(this);
},
makeSetterMethod: () =>
function $setter(valueOrUpdater) {
return $set(this, valueOrUpdater);
},
stateConfig,
};
}
/**
* 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 {
$getDOMSlot,
$getDOMTextNode,
$getSelection,
$isElementNode,
$isRangeSelection,
$isTextNode,
type EditorState,
ElementNode,
getDOMTextNode,
type LexicalEditor,
mergeRegister,
Point,
type RangeSelection,
TextNode,
} from 'lexical';
import positionNodeOnRange from './positionNodeOnRange';
import px from './px';
function $getOrderedSelectionPoints(selection: RangeSelection): [Point, Point] {
const points = selection.getStartEndPoints();
return selection.isBackward() ? [points[1], points[0]] : points;
}
function $rangeTargetFromPoint(
editor: LexicalEditor,
point: Point,
node: ElementNode | TextNode,
dom: HTMLElement,
): [HTMLElement | Text, number] {
if (point.type === 'text' || !$isElementNode(node)) {
const textDOM =
($isTextNode(node)
? $getDOMTextNode(node, dom, editor)
: getDOMTextNode(dom)) || dom;
return [textDOM, point.offset];
} else {
const slot = $getDOMSlot(node, dom, editor);
return [slot.element, slot.getFirstChildOffset() + point.offset];
}
}
function $rangeFromPoints(
editor: LexicalEditor,
start: Point,
startNode: ElementNode | TextNode,
startDOM: HTMLElement,
end: Point,
endNode: ElementNode | TextNode,
endDOM: HTMLElement,
): Range {
const editorDocument = editor._window ? editor._window.document : document;
const range = editorDocument.createRange();
range.setStart(...$rangeTargetFromPoint(editor, start, startNode, startDOM));
range.setEnd(...$rangeTargetFromPoint(editor, end, endNode, endDOM));
return range;
}
function defaultOnReposition(domNodes: readonly HTMLElement[]): void {
for (const domNode of domNodes) {
const domNodeStyle = domNode.style;
if (domNodeStyle.background !== 'Highlight') {
domNodeStyle.background = 'Highlight';
}
if (domNodeStyle.color !== 'HighlightText') {
domNodeStyle.color = 'HighlightText';
}
if (domNodeStyle.marginTop !== px(-1.5)) {
domNodeStyle.marginTop = px(-1.5);
}
if (domNodeStyle.paddingTop !== px(4)) {
domNodeStyle.paddingTop = px(4);
}
if (domNodeStyle.paddingBottom !== px(0)) {
domNodeStyle.paddingBottom = px(0);
}
}
}
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
export default function markSelection(
editor: LexicalEditor,
onReposition: (node: readonly HTMLElement[]) => void = defaultOnReposition,
): () => void {
let previousAnchorNode: null | TextNode | ElementNode = null;
let previousAnchorNodeDOM: null | HTMLElement = null;
let previousAnchorOffset: null | number = null;
let previousFocusNode: null | TextNode | ElementNode = null;
let previousFocusNodeDOM: null | HTMLElement = null;
let previousFocusOffset: null | number = null;
let removeRangeListener: () => void = () => {};
function compute(editorState: EditorState) {
editorState.read(
() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
// TODO
previousAnchorNode = null;
previousAnchorOffset = null;
previousFocusNode = null;
previousFocusOffset = null;
removeRangeListener();
removeRangeListener = () => {};
return;
}
const [start, end] = $getOrderedSelectionPoints(selection);
const currentStartNode = start.getNode() as TextNode | ElementNode;
const currentStartNodeKey = currentStartNode.getKey();
const currentStartOffset = start.offset;
const currentEndNode = end.getNode() as TextNode | ElementNode;
const currentEndNodeKey = currentEndNode.getKey();
const currentEndOffset = end.offset;
const currentStartNodeDOM = editor.getElementByKey(currentStartNodeKey);
const currentEndNodeDOM = editor.getElementByKey(currentEndNodeKey);
const differentStartDOM =
previousAnchorNode === null ||
currentStartNodeDOM !== previousAnchorNodeDOM ||
currentStartOffset !== previousAnchorOffset ||
currentStartNodeKey !== previousAnchorNode.getKey();
const differentEndDOM =
previousFocusNode === null ||
currentEndNodeDOM !== previousFocusNodeDOM ||
currentEndOffset !== previousFocusOffset ||
currentEndNodeKey !== previousFocusNode.getKey();
if (
(differentStartDOM || differentEndDOM) &&
currentStartNodeDOM !== null &&
currentEndNodeDOM !== null
) {
const range = $rangeFromPoints(
editor,
start,
currentStartNode,
currentStartNodeDOM,
end,
currentEndNode,
currentEndNodeDOM,
);
removeRangeListener();
removeRangeListener = positionNodeOnRange(
editor,
range,
onReposition,
);
}
previousAnchorNode = currentStartNode;
previousAnchorNodeDOM = currentStartNodeDOM;
previousAnchorOffset = currentStartOffset;
previousFocusNode = currentEndNode;
previousFocusNodeDOM = currentEndNodeDOM;
previousFocusOffset = currentEndOffset;
// Pass {editor} so the active editor is set: $rangeTargetFromPoint reads
// the slot (getFirstChildOffset), which consults the active editor to
// skip the block cursor.
},
{editor},
);
}
compute(editor.getEditorState());
return mergeRegister(
editor.registerUpdateListener(({editorState}) => compute(editorState)),
() => {
removeRangeListener();
},
);
}
/**
* 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 invariant from '@lexical/internal/invariant';
import {createRectsFromDOMRange} from '@lexical/selection';
import {isHTMLElement, type LexicalEditor} from 'lexical';
import px from './px';
const mutationObserverConfig = {
attributes: true,
characterData: true,
childList: true,
subtree: true,
};
function prependDOMNode(parent: HTMLElement, node: HTMLElement) {
parent.insertBefore(node, parent.firstChild);
}
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
export default function mlcPositionNodeOnRange(
editor: LexicalEditor,
range: Range,
onReposition: (node: Array<HTMLElement>) => void,
): () => void {
let rootDOMNode: null | HTMLElement = null;
let parentDOMNode: null | HTMLElement = null;
let observer: null | MutationObserver = null;
let lastNodes: Array<HTMLElement> = [];
const wrapperNode = document.createElement('div');
wrapperNode.style.position = 'relative';
function position(): void {
invariant(rootDOMNode !== null, 'Unexpected null rootDOMNode');
invariant(parentDOMNode !== null, 'Unexpected null parentDOMNode');
const {left: parentLeft, top: parentTop} =
parentDOMNode.getBoundingClientRect();
const rects = createRectsFromDOMRange(editor, range);
if (!wrapperNode.isConnected) {
prependDOMNode(parentDOMNode, wrapperNode);
}
let hasRepositioned = false;
for (let i = 0; i < rects.length; i++) {
const rect = rects[i];
// Try to reuse the previously created Node when possible, no need to
// remove/create on the most common case reposition case
const rectNode = lastNodes[i] || document.createElement('div');
const rectNodeStyle = rectNode.style;
if (rectNodeStyle.position !== 'absolute') {
rectNodeStyle.position = 'absolute';
hasRepositioned = true;
}
const left = px(rect.left - parentLeft);
if (rectNodeStyle.left !== left) {
rectNodeStyle.left = left;
hasRepositioned = true;
}
const top = px(rect.top - parentTop);
if (rectNodeStyle.top !== top) {
rectNode.style.top = top;
hasRepositioned = true;
}
const width = px(rect.width);
if (rectNodeStyle.width !== width) {
rectNode.style.width = width;
hasRepositioned = true;
}
const height = px(rect.height);
if (rectNodeStyle.height !== height) {
rectNode.style.height = height;
hasRepositioned = true;
}
if (rectNode.parentNode !== wrapperNode) {
wrapperNode.append(rectNode);
hasRepositioned = true;
}
lastNodes[i] = rectNode;
}
while (lastNodes.length > rects.length) {
lastNodes.pop();
}
if (hasRepositioned) {
onReposition(lastNodes);
}
}
function stop(): void {
parentDOMNode = null;
rootDOMNode = null;
if (observer !== null) {
observer.disconnect();
}
observer = null;
wrapperNode.remove();
for (const node of lastNodes) {
node.remove();
}
lastNodes = [];
}
function restart(): void {
const currentRootDOMNode = editor.getRootElement();
if (currentRootDOMNode === null) {
return stop();
}
const currentParentDOMNode = currentRootDOMNode.parentElement;
if (!isHTMLElement(currentParentDOMNode)) {
return stop();
}
stop();
rootDOMNode = currentRootDOMNode;
parentDOMNode = currentParentDOMNode;
observer = new MutationObserver(mutations => {
const nextRootDOMNode = editor.getRootElement();
const nextParentDOMNode =
nextRootDOMNode && nextRootDOMNode.parentElement;
if (
nextRootDOMNode !== rootDOMNode ||
nextParentDOMNode !== parentDOMNode
) {
return restart();
}
for (const mutation of mutations) {
if (!wrapperNode.contains(mutation.target)) {
// TODO throttle
return position();
}
}
});
observer.observe(currentParentDOMNode, mutationObserverConfig);
position();
}
const removeRootListener = editor.registerRootListener(restart);
return () => {
removeRootListener();
stop();
};
}
/**
* 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.
*
*/
export default function px(value: number) {
return `${value}px`;
}
/**
* 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 {LexicalEditor} from 'lexical';
import markSelection from './markSelection';
export default function selectionAlwaysOnDisplay(
editor: LexicalEditor,
onReposition?: (node: readonly HTMLElement[]) => void,
): () => void {
let removeSelectionMark: (() => void) | null = null;
const onSelectionChange = () => {
const domSelection = getSelection();
const domAnchorNode = domSelection && domSelection.anchorNode;
const editorRootElement = editor.getRootElement();
const isSelectionInsideEditor =
domAnchorNode !== null &&
editorRootElement !== null &&
editorRootElement.contains(domAnchorNode);
if (isSelectionInsideEditor) {
if (removeSelectionMark !== null) {
removeSelectionMark();
removeSelectionMark = null;
}
} else {
if (removeSelectionMark === null) {
removeSelectionMark = markSelection(editor, onReposition);
}
}
};
return editor.registerRootListener(rootElement => {
if (rootElement) {
const document = rootElement.ownerDocument;
document.addEventListener('selectionchange', onSelectionChange);
onSelectionChange();
return () => {
if (removeSelectionMark !== null) {
removeSelectionMark();
}
document.removeEventListener('selectionchange', onSelectionChange);
};
}
});
}
+31
-16

@@ -11,8 +11,9 @@ {

"license": "MIT",
"version": "0.44.1-nightly.20260519.0",
"main": "LexicalUtils.js",
"types": "index.d.ts",
"version": "0.45.0",
"main": "./dist/LexicalUtils.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@lexical/selection": "0.44.1-nightly.20260519.0",
"lexical": "0.44.1-nightly.20260519.0"
"@lexical/internal": "0.45.0",
"@lexical/selection": "0.45.0",
"lexical": "0.45.0"
},

@@ -24,21 +25,35 @@ "repository": {

},
"module": "LexicalUtils.mjs",
"module": "./dist/LexicalUtils.mjs",
"sideEffects": false,
"exports": {
".": {
"source": "./src/index.ts",
"import": {
"types": "./index.d.ts",
"development": "./LexicalUtils.dev.mjs",
"production": "./LexicalUtils.prod.mjs",
"node": "./LexicalUtils.node.mjs",
"default": "./LexicalUtils.mjs"
"types": "./dist/index.d.ts",
"development": "./dist/LexicalUtils.dev.mjs",
"production": "./dist/LexicalUtils.prod.mjs",
"node": "./dist/LexicalUtils.node.mjs",
"default": "./dist/LexicalUtils.mjs"
},
"require": {
"types": "./index.d.ts",
"development": "./LexicalUtils.dev.js",
"production": "./LexicalUtils.prod.js",
"default": "./LexicalUtils.js"
"types": "./dist/index.d.ts",
"development": "./dist/LexicalUtils.dev.js",
"production": "./dist/LexicalUtils.prod.js",
"default": "./dist/LexicalUtils.js"
}
}
}
},
"files": [
"dist",
"src",
"!src/__tests__",
"!src/__bench__",
"!src/__mocks__",
"!src/**/*.test.ts",
"!src/**/*.test.tsx",
"!src/**/*.bench.ts",
"!src/**/*.bench.tsx",
"README.md",
"LICENSE"
]
}
/**
* 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 CaretDirection, type EditorState, ElementNode, type Klass, type LexicalEditor, type LexicalNode, type NodeCaret, PointCaret, type SiblingCaret, SplitAtPointCaretNextOptions, StateConfig, ValueOrUpdater } from 'lexical';
export { default as markSelection } from './markSelection';
export { default as positionNodeOnRange } from './positionNodeOnRange';
export { default as selectionAlwaysOnDisplay } from './selectionAlwaysOnDisplay';
export { $findMatchingParent, $getAdjacentSiblingOrParentSiblingCaret, $splitNode, addClassNamesToElement, isBlockDomNode, isHTMLAnchorElement, isHTMLElement, isInlineDomNode, mergeRegister, removeClassNamesFromElement, } from 'lexical';
export declare const CAN_USE_BEFORE_INPUT: boolean;
export declare const CAN_USE_DOM: boolean;
export declare const IS_ANDROID: boolean;
export declare const IS_ANDROID_CHROME: boolean;
export declare const IS_APPLE: boolean;
export declare const IS_APPLE_WEBKIT: boolean;
export declare const IS_CHROME: boolean;
export declare const IS_FIREFOX: boolean;
export declare const IS_IOS: boolean;
export declare const IS_SAFARI: boolean;
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
export declare function isMimeType(file: File, acceptableMimeTypes: Array<string>): boolean;
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
export declare function mediaFileReader(files: Array<File>, acceptableMimeTypes: Array<string>): Promise<Array<{
file: File;
result: string;
}>>;
export interface DFSNode {
readonly depth: number;
readonly node: LexicalNode;
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
export declare function $dfs(startNode?: LexicalNode, endNode?: LexicalNode): Array<DFSNode>;
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
export declare function $getAdjacentCaret<D extends CaretDirection>(caret: null | NodeCaret<D>): null | SiblingCaret<LexicalNode, D>;
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export declare function $reverseDfs(startNode?: LexicalNode, endNode?: LexicalNode): Array<DFSNode>;
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export declare function $dfsIterator(startNode?: LexicalNode, endNode?: LexicalNode): IterableIterator<DFSNode>;
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
export declare function $getNextSiblingOrParentSibling(node: LexicalNode): null | [LexicalNode, number];
export declare function $getDepth(node: null | LexicalNode): number;
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
export declare function $getNextRightPreorderNode(startingNode: LexicalNode): LexicalNode | null;
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
export declare function $reverseDfsIterator(startNode?: LexicalNode, endNode?: LexicalNode): IterableIterator<DFSNode>;
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
export declare function $getNearestNodeOfType<T extends ElementNode>(node: LexicalNode, klass: Klass<T>): T | null;
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
export declare function $getNearestBlockElementAncestorOrThrow(startNode: LexicalNode): ElementNode;
export type DOMNodeToLexicalConversion = (element: Node) => LexicalNode;
export type DOMNodeToLexicalConversionMap = Record<string, DOMNodeToLexicalConversion>;
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
export declare function registerNestedElementResolver<N extends ElementNode>(editor: LexicalEditor, targetNode: Klass<N>, cloneNode: (from: N) => N, handleOverlap: (from: N, to: N) => void): () => void;
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
export declare function $restoreEditorState(editor: LexicalEditor, editorState: EditorState): void;
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
export declare function $insertNodeToNearestRoot<T extends LexicalNode>(node: T): T;
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
export declare function $insertNodeToNearestRootAtCaret<T extends LexicalNode, D extends CaretDirection>(node: T, caret: PointCaret<D>, options?: SplitAtPointCaretNextOptions): NodeCaret<D>;
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
export declare function $insertNodeIntoLeaf(node: LexicalNode): void;
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
export declare function $wrapNodeInElement(node: LexicalNode, createElementNode: () => ElementNode): ElementNode;
export type ObjectKlass<T> = new (...args: any[]) => T;
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
export declare function objectKlassEquals<T>(object: unknown, objectClass: ObjectKlass<T>): object is T;
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
export declare function $filter<T>(nodes: Array<LexicalNode>, filterFn: (node: LexicalNode) => null | T): Array<T>;
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
export declare function $handleIndentAndOutdent(indentOrOutdent: (block: ElementNode) => void): boolean;
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
export declare function $insertFirst(parent: ElementNode, node: LexicalNode): void;
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
export declare function calculateZoomLevel(element: Element | null, useManualZoom?: boolean): number;
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
export declare function $isEditorIsNestedEditor(editor: LexicalEditor): boolean;
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
export declare function $unwrapAndFilterDescendants(root: ElementNode, $predicate: (node: LexicalNode) => boolean): boolean;
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
export declare function $descendantsMatching<T extends LexicalNode>(children: LexicalNode[], $predicate: (node: LexicalNode) => node is T): T[];
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
export declare function $firstToLastIterator(node: ElementNode): Iterable<LexicalNode>;
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
export declare function $lastToFirstIterator(node: ElementNode): Iterable<LexicalNode>;
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
export declare function $unwrapNode(node: ElementNode): void;
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
export interface StateConfigWrapper<K extends string, V> {
/** A reference to the stateConfig */
readonly stateConfig: StateConfig<K, V>;
/** `(node) => $getState(node, stateConfig)` */
readonly $get: <T extends LexicalNode>(node: T) => V;
/** `(node, valueOrUpdater) => $setState(node, stateConfig, valueOrUpdater)` */
readonly $set: <T extends LexicalNode>(node: T, valueOrUpdater: ValueOrUpdater<V>) => T;
/** `[$get, $set]` */
readonly accessors: readonly [$get: this['$get'], $set: this['$set']];
/**
* `() => function () { return $get(this) }`
*
* Should be called with an explicit `this` type parameter.
*
* @example
* ```ts
* class MyNode {
* // …
* myGetter = myWrapper.makeGetterMethod<this>();
* }
* ```
*/
makeGetterMethod<T extends LexicalNode>(): (this: T) => V;
/**
* `() => function (valueOrUpdater) { return $set(this, valueOrUpdater) }`
*
* Must be called with an explicit `this` type parameter.
*
* @example
* ```ts
* class MyNode {
* // …
* mySetter = myWrapper.makeSetterMethod<this>();
* }
* ```
*/
makeSetterMethod<T extends LexicalNode>(): (this: T, valueOrUpdater: ValueOrUpdater<V>) => T;
}
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
export declare function makeStateWrapper<K extends string, V>(stateConfig: StateConfig<K, V>): StateConfigWrapper<K, V>;
/**
* 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.
*
*/
'use strict';
var lexical = require('lexical');
var selection = require('@lexical/selection');
/**
* 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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
/**
* 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.
*
*/
const CAN_USE_DOM$1 = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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.
*
*/
const documentMode = CAN_USE_DOM$1 && 'documentMode' in document ? document.documentMode : null;
const IS_APPLE$1 = CAN_USE_DOM$1 && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
const IS_FIREFOX$1 = CAN_USE_DOM$1 && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
const CAN_USE_BEFORE_INPUT$1 = CAN_USE_DOM$1 && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
const IS_IOS$1 = CAN_USE_DOM$1 && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const IS_ANDROID$1 = CAN_USE_DOM$1 && /Android/.test(navigator.userAgent);
// Exclude Android — Android WebView's UA contains "Version/X.X ... Safari/537.36"
// which falsely matches the Safari regex, activating wrong composition code paths.
const IS_SAFARI$1 = CAN_USE_DOM$1 && /Version\/[\d.]+.*Safari/.test(navigator.userAgent) && !IS_ANDROID$1;
// Keep these in case we need to use them in the future.
// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
const IS_CHROME$1 = CAN_USE_DOM$1 && /^(?=.*Chrome).*/i.test(navigator.userAgent);
// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
const IS_ANDROID_CHROME$1 = CAN_USE_DOM$1 && IS_ANDROID$1 && IS_CHROME$1;
const IS_APPLE_WEBKIT$1 = CAN_USE_DOM$1 && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && IS_APPLE$1 && !IS_CHROME$1;
/**
* 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.
*
*/
function px(value) {
return `${value}px`;
}
const mutationObserverConfig = {
attributes: true,
characterData: true,
childList: true,
subtree: true
};
function prependDOMNode(parent, node) {
parent.insertBefore(node, parent.firstChild);
}
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
function mlcPositionNodeOnRange(editor, range, onReposition) {
let rootDOMNode = null;
let parentDOMNode = null;
let observer = null;
let lastNodes = [];
const wrapperNode = document.createElement('div');
wrapperNode.style.position = 'relative';
function position() {
if (!(rootDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null rootDOMNode`);
}
if (!(parentDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null parentDOMNode`);
}
const {
left: parentLeft,
top: parentTop
} = parentDOMNode.getBoundingClientRect();
const rects = selection.createRectsFromDOMRange(editor, range);
if (!wrapperNode.isConnected) {
prependDOMNode(parentDOMNode, wrapperNode);
}
let hasRepositioned = false;
for (let i = 0; i < rects.length; i++) {
const rect = rects[i];
// Try to reuse the previously created Node when possible, no need to
// remove/create on the most common case reposition case
const rectNode = lastNodes[i] || document.createElement('div');
const rectNodeStyle = rectNode.style;
if (rectNodeStyle.position !== 'absolute') {
rectNodeStyle.position = 'absolute';
hasRepositioned = true;
}
const left = px(rect.left - parentLeft);
if (rectNodeStyle.left !== left) {
rectNodeStyle.left = left;
hasRepositioned = true;
}
const top = px(rect.top - parentTop);
if (rectNodeStyle.top !== top) {
rectNode.style.top = top;
hasRepositioned = true;
}
const width = px(rect.width);
if (rectNodeStyle.width !== width) {
rectNode.style.width = width;
hasRepositioned = true;
}
const height = px(rect.height);
if (rectNodeStyle.height !== height) {
rectNode.style.height = height;
hasRepositioned = true;
}
if (rectNode.parentNode !== wrapperNode) {
wrapperNode.append(rectNode);
hasRepositioned = true;
}
lastNodes[i] = rectNode;
}
while (lastNodes.length > rects.length) {
lastNodes.pop();
}
if (hasRepositioned) {
onReposition(lastNodes);
}
}
function stop() {
parentDOMNode = null;
rootDOMNode = null;
if (observer !== null) {
observer.disconnect();
}
observer = null;
wrapperNode.remove();
for (const node of lastNodes) {
node.remove();
}
lastNodes = [];
}
function restart() {
const currentRootDOMNode = editor.getRootElement();
if (currentRootDOMNode === null) {
return stop();
}
const currentParentDOMNode = currentRootDOMNode.parentElement;
if (!lexical.isHTMLElement(currentParentDOMNode)) {
return stop();
}
stop();
rootDOMNode = currentRootDOMNode;
parentDOMNode = currentParentDOMNode;
observer = new MutationObserver(mutations => {
const nextRootDOMNode = editor.getRootElement();
const nextParentDOMNode = nextRootDOMNode && nextRootDOMNode.parentElement;
if (nextRootDOMNode !== rootDOMNode || nextParentDOMNode !== parentDOMNode) {
return restart();
}
for (const mutation of mutations) {
if (!wrapperNode.contains(mutation.target)) {
// TODO throttle
return position();
}
}
});
observer.observe(currentParentDOMNode, mutationObserverConfig);
position();
}
const removeRootListener = editor.registerRootListener(restart);
return () => {
removeRootListener();
stop();
};
}
/**
* 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.
*
*/
function $getOrderedSelectionPoints(selection) {
const points = selection.getStartEndPoints();
return selection.isBackward() ? [points[1], points[0]] : points;
}
function $rangeTargetFromPoint(editor, point, node, dom) {
if (point.type === 'text' || !lexical.$isElementNode(node)) {
const textDOM = lexical.getDOMTextNode(dom) || dom;
return [textDOM, point.offset];
} else {
const slot = lexical.$getEditorDOMRenderConfig(editor).$getDOMSlot(node, dom, editor);
return [slot.element, slot.getFirstChildOffset() + point.offset];
}
}
function $rangeFromPoints(editor, start, startNode, startDOM, end, endNode, endDOM) {
const editorDocument = editor._window ? editor._window.document : document;
const range = editorDocument.createRange();
range.setStart(...$rangeTargetFromPoint(editor, start, startNode, startDOM));
range.setEnd(...$rangeTargetFromPoint(editor, end, endNode, endDOM));
return range;
}
function defaultOnReposition(domNodes) {
for (const domNode of domNodes) {
const domNodeStyle = domNode.style;
if (domNodeStyle.background !== 'Highlight') {
domNodeStyle.background = 'Highlight';
}
if (domNodeStyle.color !== 'HighlightText') {
domNodeStyle.color = 'HighlightText';
}
if (domNodeStyle.marginTop !== px(-1.5)) {
domNodeStyle.marginTop = px(-1.5);
}
if (domNodeStyle.paddingTop !== px(4)) {
domNodeStyle.paddingTop = px(4);
}
if (domNodeStyle.paddingBottom !== px(0)) {
domNodeStyle.paddingBottom = px(0);
}
}
}
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
function markSelection(editor, onReposition = defaultOnReposition) {
let previousAnchorNode = null;
let previousAnchorNodeDOM = null;
let previousAnchorOffset = null;
let previousFocusNode = null;
let previousFocusNodeDOM = null;
let previousFocusOffset = null;
let removeRangeListener = () => {};
function compute(editorState) {
editorState.read(() => {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
// TODO
previousAnchorNode = null;
previousAnchorOffset = null;
previousFocusNode = null;
previousFocusOffset = null;
removeRangeListener();
removeRangeListener = () => {};
return;
}
const [start, end] = $getOrderedSelectionPoints(selection);
const currentStartNode = start.getNode();
const currentStartNodeKey = currentStartNode.getKey();
const currentStartOffset = start.offset;
const currentEndNode = end.getNode();
const currentEndNodeKey = currentEndNode.getKey();
const currentEndOffset = end.offset;
const currentStartNodeDOM = editor.getElementByKey(currentStartNodeKey);
const currentEndNodeDOM = editor.getElementByKey(currentEndNodeKey);
const differentStartDOM = previousAnchorNode === null || currentStartNodeDOM !== previousAnchorNodeDOM || currentStartOffset !== previousAnchorOffset || currentStartNodeKey !== previousAnchorNode.getKey();
const differentEndDOM = previousFocusNode === null || currentEndNodeDOM !== previousFocusNodeDOM || currentEndOffset !== previousFocusOffset || currentEndNodeKey !== previousFocusNode.getKey();
if ((differentStartDOM || differentEndDOM) && currentStartNodeDOM !== null && currentEndNodeDOM !== null) {
const range = $rangeFromPoints(editor, start, currentStartNode, currentStartNodeDOM, end, currentEndNode, currentEndNodeDOM);
removeRangeListener();
removeRangeListener = mlcPositionNodeOnRange(editor, range, onReposition);
}
previousAnchorNode = currentStartNode;
previousAnchorNodeDOM = currentStartNodeDOM;
previousAnchorOffset = currentStartOffset;
previousFocusNode = currentEndNode;
previousFocusNodeDOM = currentEndNodeDOM;
previousFocusOffset = currentEndOffset;
});
}
compute(editor.getEditorState());
return lexical.mergeRegister(editor.registerUpdateListener(({
editorState
}) => compute(editorState)), () => {
removeRangeListener();
});
}
/**
* 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.
*
*/
function selectionAlwaysOnDisplay(editor, onReposition) {
let removeSelectionMark = null;
const onSelectionChange = () => {
const domSelection = getSelection();
const domAnchorNode = domSelection && domSelection.anchorNode;
const editorRootElement = editor.getRootElement();
const isSelectionInsideEditor = domAnchorNode !== null && editorRootElement !== null && editorRootElement.contains(domAnchorNode);
if (isSelectionInsideEditor) {
if (removeSelectionMark !== null) {
removeSelectionMark();
removeSelectionMark = null;
}
} else {
if (removeSelectionMark === null) {
removeSelectionMark = markSelection(editor, onReposition);
}
}
};
return editor.registerRootListener(rootElement => {
if (rootElement) {
const document = rootElement.ownerDocument;
document.addEventListener('selectionchange', onSelectionChange);
onSelectionChange();
return () => {
if (removeSelectionMark !== null) {
removeSelectionMark();
}
document.removeEventListener('selectionchange', onSelectionChange);
};
}
});
}
// Hotfix to export these with inlined types #5918
const CAN_USE_BEFORE_INPUT = CAN_USE_BEFORE_INPUT$1;
const CAN_USE_DOM = CAN_USE_DOM$1;
const IS_ANDROID = IS_ANDROID$1;
const IS_ANDROID_CHROME = IS_ANDROID_CHROME$1;
const IS_APPLE = IS_APPLE$1;
const IS_APPLE_WEBKIT = IS_APPLE_WEBKIT$1;
const IS_CHROME = IS_CHROME$1;
const IS_FIREFOX = IS_FIREFOX$1;
const IS_IOS = IS_IOS$1;
const IS_SAFARI = IS_SAFARI$1;
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
function isMimeType(file, acceptableMimeTypes) {
for (const acceptableType of acceptableMimeTypes) {
if (file.type.startsWith(acceptableType)) {
return true;
}
}
return false;
}
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
function mediaFileReader(files, acceptableMimeTypes) {
const filesIterator = files[Symbol.iterator]();
return new Promise((resolve, reject) => {
const processed = [];
const handleNextFile = () => {
const {
done,
value: file
} = filesIterator.next();
if (done) {
return resolve(processed);
}
const fileReader = new FileReader();
fileReader.addEventListener('error', reject);
fileReader.addEventListener('load', () => {
const result = fileReader.result;
if (typeof result === 'string') {
processed.push({
file,
result
});
}
handleNextFile();
});
if (isMimeType(file, acceptableMimeTypes)) {
fileReader.readAsDataURL(file);
} else {
handleNextFile();
}
};
handleNextFile();
});
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
function $dfs(startNode, endNode) {
return Array.from($dfsIterator(startNode, endNode));
}
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
function $getAdjacentCaret(caret) {
return caret ? caret.getAdjacentCaret() : null;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfs(startNode, endNode) {
return Array.from($reverseDfsIterator(startNode, endNode));
}
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $dfsIterator(startNode, endNode) {
return $dfsCaretIterator('next', startNode, endNode);
}
function $getEndCaret(startNode, direction) {
const rval = lexical.$getAdjacentSiblingOrParentSiblingCaret(lexical.$getSiblingCaret(startNode, direction));
return rval && rval[0];
}
function $dfsCaretIterator(direction, startNode, endNode) {
const root = lexical.$getRoot();
const start = startNode || root;
const startCaret = lexical.$isElementNode(start) ? lexical.$getChildCaret(start, direction) : lexical.$getSiblingCaret(start, direction);
const startDepth = $getDepth(start);
const endCaret = endNode ? lexical.$getAdjacentChildCaret(lexical.$getChildCaretOrSelf(lexical.$getSiblingCaret(endNode, direction))) || $getEndCaret(endNode, direction) : $getEndCaret(start, direction);
let depth = startDepth;
return lexical.makeStepwiseIterator({
hasNext: state => state !== null,
initial: startCaret,
map: state => ({
depth,
node: state.origin
}),
step: state => {
if (state.isSameNodeCaret(endCaret)) {
return null;
}
if (lexical.$isChildCaret(state)) {
depth++;
}
const rval = lexical.$getAdjacentSiblingOrParentSiblingCaret(state);
if (!rval || rval[0].isSameNodeCaret(endCaret)) {
return null;
}
depth += rval[1];
return rval[0];
}
});
}
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
function $getNextSiblingOrParentSibling(node) {
const rval = lexical.$getAdjacentSiblingOrParentSiblingCaret(lexical.$getSiblingCaret(node, 'next'));
return rval && [rval[0].origin, rval[1]];
}
function $getDepth(node) {
let depth = -1;
for (let innerNode = node; innerNode !== null; innerNode = innerNode.getParent()) {
depth++;
}
return depth;
}
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
function $getNextRightPreorderNode(startingNode) {
const startCaret = lexical.$getChildCaretOrSelf(lexical.$getSiblingCaret(startingNode, 'previous'));
const next = lexical.$getAdjacentSiblingOrParentSiblingCaret(startCaret, 'root');
return next && next[0].origin;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfsIterator(startNode, endNode) {
return $dfsCaretIterator('previous', startNode, endNode);
}
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
function $getNearestNodeOfType(node, klass) {
let parent = node;
while (parent != null) {
if (parent instanceof klass) {
return parent;
}
parent = parent.getParent();
}
return null;
}
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
function $getNearestBlockElementAncestorOrThrow(startNode) {
const blockNode = lexical.$findMatchingParent(startNode, node => lexical.$isElementNode(node) && !node.isInline());
if (!lexical.$isElementNode(blockNode)) {
{
formatDevErrorMessage(`Expected node ${startNode.__key} to have closest block element node.`);
}
}
return blockNode;
}
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
function registerNestedElementResolver(editor, targetNode, cloneNode, handleOverlap) {
const $isTargetNode = node => {
return node instanceof targetNode;
};
const $findMatch = node => {
// First validate we don't have any children that are of the target,
// as we need to handle them first.
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if ($isTargetNode(child)) {
return null;
}
}
let parentNode = node;
let childNode = node;
while (parentNode !== null) {
childNode = parentNode;
parentNode = parentNode.getParent();
if ($isTargetNode(parentNode)) {
return {
child: childNode,
parent: parentNode
};
}
}
return null;
};
const $elementNodeTransform = node => {
const match = $findMatch(node);
if (match !== null) {
const {
child,
parent
} = match;
// Simple path, we can move child out and siblings into a new parent.
if (child.is(node)) {
handleOverlap(parent, node);
const nextSiblings = child.getNextSiblings();
const nextSiblingsLength = nextSiblings.length;
parent.insertAfter(child);
if (nextSiblingsLength !== 0) {
const newParent = cloneNode(parent);
child.insertAfter(newParent);
for (let i = 0; i < nextSiblingsLength; i++) {
newParent.append(nextSiblings[i]);
}
}
if (!parent.canBeEmpty() && parent.getChildrenSize() === 0) {
parent.remove();
}
}
}
};
return editor.registerNodeTransform(targetNode, $elementNodeTransform);
}
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
function $restoreEditorState(editor, editorState) {
const FULL_RECONCILE = 2;
const nodeMap = new Map();
const activeEditorState = editor._pendingEditorState;
for (const [key, node] of editorState._nodeMap) {
nodeMap.set(key, lexical.$cloneWithProperties(node));
}
if (activeEditorState) {
activeEditorState._nodeMap = nodeMap;
}
editor._dirtyType = FULL_RECONCILE;
const selection = editorState._selection;
lexical.$setSelection(selection === null ? null : selection.clone());
}
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
function $insertNodeToNearestRoot(node) {
const selection = lexical.$getSelection() || lexical.$getPreviousSelection();
let initialCaret;
if (lexical.$isRangeSelection(selection)) {
initialCaret = lexical.$caretFromPoint(selection.focus, 'next');
} else {
if (selection != null) {
const nodes = selection.getNodes();
const lastNode = nodes[nodes.length - 1];
if (lastNode) {
initialCaret = lexical.$getSiblingCaret(lastNode, 'next');
}
}
initialCaret = initialCaret || lexical.$getChildCaret(lexical.$getRoot(), 'previous').getFlipped().insert(lexical.$createParagraphNode());
}
const insertCaret = $insertNodeToNearestRootAtCaret(node, initialCaret);
const adjacent = lexical.$getAdjacentChildCaret(insertCaret);
const selectionCaret = lexical.$isChildCaret(adjacent) ? lexical.$normalizeCaret(adjacent) : insertCaret;
lexical.$setSelectionFromCaretRange(lexical.$getCollapsedCaretRange(selectionCaret));
return node.getLatest();
}
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
function $insertNodeToNearestRootAtCaret(node, caret, options) {
let insertCaret = lexical.$getCaretInDirection(caret, 'next');
// Normalize boundary cases for TextPointCaret
if (lexical.$isTextPointCaret(insertCaret)) {
if (insertCaret.offset === 0) {
insertCaret = lexical.$getSiblingCaret(insertCaret.origin, 'previous').getFlipped();
} else if (insertCaret.offset === insertCaret.origin.getTextContentSize()) {
insertCaret = lexical.$getSiblingCaret(insertCaret.origin, 'next');
}
}
// Make sure we have a distinct node as the origin
if (insertCaret.origin.is(node)) {
if (!lexical.$isSiblingCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret node ${node.getKey()} of type ${node.getType()} can not be inserted into itself`);
}
insertCaret = lexical.$rewindSiblingCaret(insertCaret);
}
// Handle split boundary conditions where node is being inserted adjacent to itself
if (node.is(insertCaret.getNodeAtCaret()) || node.is(insertCaret.getFlipped().getNodeAtCaret())) {
node.remove(true);
}
for (let nextCaret = insertCaret; nextCaret; nextCaret = lexical.$splitAtPointCaretNext(nextCaret, options)) {
insertCaret = nextCaret;
}
if (!!lexical.$isTextPointCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret: An unattached TextNode can not be split`);
}
insertCaret.insert(node.isInline() ? lexical.$createParagraphNode().append(node) : node);
return lexical.$getCaretInDirection(lexical.$getSiblingCaret(node.getLatest(), 'next'), caret.direction);
}
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
function $insertNodeIntoLeaf(node) {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
if (selection) {
selection.insertNodes([node]);
}
return;
}
const caretRange = lexical.$caretRangeFromSelection(selection);
let insertCaret = lexical.$getCaretRangeInDirection(lexical.$removeTextFromCaretRange(caretRange), 'next').anchor;
if (lexical.$isTextPointCaret(insertCaret)) {
const nextAnchor = lexical.$splitAtPointCaretNext(insertCaret);
if (!nextAnchor) {
return;
}
insertCaret = nextAnchor;
}
const focus = insertCaret.getFlipped();
focus.insert(node);
lexical.$setSelectionFromCaretRange(lexical.$getCaretRange(focus, focus));
}
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
function $wrapNodeInElement(node, createElementNode) {
const elementNode = createElementNode();
node.replace(elementNode);
elementNode.append(node);
return elementNode;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
function objectKlassEquals(object, objectClass) {
return object !== null ? Object.getPrototypeOf(object).constructor.name === objectClass.name : false;
}
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
function $filter(nodes, filterFn) {
const result = [];
for (let i = 0; i < nodes.length; i++) {
const node = filterFn(nodes[i]);
if (node !== null) {
result.push(node);
}
}
return result;
}
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
function $handleIndentAndOutdent(indentOrOutdent) {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
return false;
}
const alreadyHandled = new Set();
const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getKey();
if (alreadyHandled.has(key)) {
continue;
}
const parentBlock = lexical.$findMatchingParent(node, parentNode => lexical.$isElementNode(parentNode) && !parentNode.isInline());
if (parentBlock === null) {
continue;
}
const parentKey = parentBlock.getKey();
if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
alreadyHandled.add(parentKey);
indentOrOutdent(parentBlock);
}
}
return alreadyHandled.size > 0;
}
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
function $insertFirst(parent, node) {
lexical.$getChildCaret(parent, 'next').insert(node);
}
let NEEDS_MANUAL_ZOOM = IS_FIREFOX || !CAN_USE_DOM ? false : undefined;
function needsManualZoom() {
if (NEEDS_MANUAL_ZOOM === undefined) {
// If the browser implements standardized CSS zoom, then the client rect
// will be wider after zoom is applied
// https://chromestatus.com/feature/5198254868529152
// https://github.com/facebook/lexical/issues/6863
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.opacity = '0';
div.style.width = '100px';
div.style.left = '-1000px';
document.body.appendChild(div);
const noZoom = div.getBoundingClientRect();
div.style.setProperty('zoom', '2');
NEEDS_MANUAL_ZOOM = div.getBoundingClientRect().width === noZoom.width;
document.body.removeChild(div);
}
return NEEDS_MANUAL_ZOOM;
}
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
function calculateZoomLevel(element, useManualZoom = false) {
let zoom = 1;
if (needsManualZoom() || useManualZoom) {
while (element) {
zoom *= Number(window.getComputedStyle(element).getPropertyValue('zoom'));
element = element.parentElement;
}
}
return zoom;
}
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
function $isEditorIsNestedEditor(editor) {
return editor._parentEditor !== null;
}
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
function $unwrapAndFilterDescendants(root, $predicate) {
return $unwrapAndFilterDescendantsImpl(root, $predicate, null);
}
function $unwrapAndFilterDescendantsImpl(root, $predicate, $onSuccess) {
let didMutate = false;
for (const node of $lastToFirstIterator(root)) {
if ($predicate(node)) {
if ($onSuccess !== null) {
$onSuccess(node);
}
continue;
}
didMutate = true;
if (lexical.$isElementNode(node)) {
$unwrapAndFilterDescendantsImpl(node, $predicate, $onSuccess || (child => node.insertAfter(child)));
}
node.remove();
}
return didMutate;
}
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
function $descendantsMatching(children, $predicate) {
const result = [];
const stack = Array.from(children).reverse();
for (let child = stack.pop(); child !== undefined; child = stack.pop()) {
if ($predicate(child)) {
result.push(child);
} else if (lexical.$isElementNode(child)) {
for (const grandchild of $lastToFirstIterator(child)) {
stack.push(grandchild);
}
}
}
return result;
}
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $firstToLastIterator(node) {
return $childIterator(lexical.$getChildCaret(node, 'next'));
}
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $lastToFirstIterator(node) {
return $childIterator(lexical.$getChildCaret(node, 'previous'));
}
function $childIterator(startCaret) {
const seen = new Set() ;
return lexical.makeStepwiseIterator({
hasNext: lexical.$isSiblingCaret,
initial: startCaret.getAdjacentCaret(),
map: caret => {
const origin = caret.origin.getLatest();
if (seen !== null) {
const key = origin.getKey();
if (!!seen.has(key)) {
formatDevErrorMessage(`$childIterator: Cycle detected, node with key ${String(key)} has already been traversed`);
}
seen.add(key);
}
return origin;
},
step: caret => caret.getAdjacentCaret()
});
}
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
function $unwrapNode(node) {
lexical.$rewindSiblingCaret(lexical.$getSiblingCaret(node, 'next')).splice(1, node.getChildren());
}
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
function makeStateWrapper(stateConfig) {
const $get = node => lexical.$getState(node, stateConfig);
const $set = (node, valueOrUpdater) => lexical.$setState(node, stateConfig, valueOrUpdater);
return {
$get,
$set,
accessors: [$get, $set],
makeGetterMethod: () => function $getter() {
return $get(this);
},
makeSetterMethod: () => function $setter(valueOrUpdater) {
return $set(this, valueOrUpdater);
},
stateConfig
};
}
exports.$findMatchingParent = lexical.$findMatchingParent;
exports.$getAdjacentSiblingOrParentSiblingCaret = lexical.$getAdjacentSiblingOrParentSiblingCaret;
exports.$splitNode = lexical.$splitNode;
exports.addClassNamesToElement = lexical.addClassNamesToElement;
exports.isBlockDomNode = lexical.isBlockDomNode;
exports.isHTMLAnchorElement = lexical.isHTMLAnchorElement;
exports.isHTMLElement = lexical.isHTMLElement;
exports.isInlineDomNode = lexical.isInlineDomNode;
exports.mergeRegister = lexical.mergeRegister;
exports.removeClassNamesFromElement = lexical.removeClassNamesFromElement;
exports.$descendantsMatching = $descendantsMatching;
exports.$dfs = $dfs;
exports.$dfsIterator = $dfsIterator;
exports.$filter = $filter;
exports.$firstToLastIterator = $firstToLastIterator;
exports.$getAdjacentCaret = $getAdjacentCaret;
exports.$getDepth = $getDepth;
exports.$getNearestBlockElementAncestorOrThrow = $getNearestBlockElementAncestorOrThrow;
exports.$getNearestNodeOfType = $getNearestNodeOfType;
exports.$getNextRightPreorderNode = $getNextRightPreorderNode;
exports.$getNextSiblingOrParentSibling = $getNextSiblingOrParentSibling;
exports.$handleIndentAndOutdent = $handleIndentAndOutdent;
exports.$insertFirst = $insertFirst;
exports.$insertNodeIntoLeaf = $insertNodeIntoLeaf;
exports.$insertNodeToNearestRoot = $insertNodeToNearestRoot;
exports.$insertNodeToNearestRootAtCaret = $insertNodeToNearestRootAtCaret;
exports.$isEditorIsNestedEditor = $isEditorIsNestedEditor;
exports.$lastToFirstIterator = $lastToFirstIterator;
exports.$restoreEditorState = $restoreEditorState;
exports.$reverseDfs = $reverseDfs;
exports.$reverseDfsIterator = $reverseDfsIterator;
exports.$unwrapAndFilterDescendants = $unwrapAndFilterDescendants;
exports.$unwrapNode = $unwrapNode;
exports.$wrapNodeInElement = $wrapNodeInElement;
exports.CAN_USE_BEFORE_INPUT = CAN_USE_BEFORE_INPUT;
exports.CAN_USE_DOM = CAN_USE_DOM;
exports.IS_ANDROID = IS_ANDROID;
exports.IS_ANDROID_CHROME = IS_ANDROID_CHROME;
exports.IS_APPLE = IS_APPLE;
exports.IS_APPLE_WEBKIT = IS_APPLE_WEBKIT;
exports.IS_CHROME = IS_CHROME;
exports.IS_FIREFOX = IS_FIREFOX;
exports.IS_IOS = IS_IOS;
exports.IS_SAFARI = IS_SAFARI;
exports.calculateZoomLevel = calculateZoomLevel;
exports.isMimeType = isMimeType;
exports.makeStateWrapper = makeStateWrapper;
exports.markSelection = markSelection;
exports.mediaFileReader = mediaFileReader;
exports.objectKlassEquals = objectKlassEquals;
exports.positionNodeOnRange = mlcPositionNodeOnRange;
exports.registerNestedElementResolver = registerNestedElementResolver;
exports.selectionAlwaysOnDisplay = selectionAlwaysOnDisplay;
/**
* 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 { isHTMLElement, mergeRegister, $getSelection, $isRangeSelection, $isElementNode, getDOMTextNode, $getEditorDOMRenderConfig, $getChildCaret, $findMatchingParent, $getChildCaretOrSelf, $getSiblingCaret, $getAdjacentSiblingOrParentSiblingCaret, $caretRangeFromSelection, $getCaretRangeInDirection, $removeTextFromCaretRange, $isTextPointCaret, $splitAtPointCaretNext, $setSelectionFromCaretRange, $getCaretRange, $getPreviousSelection, $caretFromPoint, $getRoot, $createParagraphNode, $getAdjacentChildCaret, $isChildCaret, $normalizeCaret, $getCollapsedCaretRange, $getCaretInDirection, $isSiblingCaret, $rewindSiblingCaret, $cloneWithProperties, $setSelection, makeStepwiseIterator, $getState, $setState } from 'lexical';
export { $findMatchingParent, $getAdjacentSiblingOrParentSiblingCaret, $splitNode, addClassNamesToElement, isBlockDomNode, isHTMLAnchorElement, isHTMLElement, isInlineDomNode, mergeRegister, removeClassNamesFromElement } from 'lexical';
import { createRectsFromDOMRange } from '@lexical/selection';
/**
* 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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
/**
* 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.
*
*/
const CAN_USE_DOM$1 = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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.
*
*/
const documentMode = CAN_USE_DOM$1 && 'documentMode' in document ? document.documentMode : null;
const IS_APPLE$1 = CAN_USE_DOM$1 && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
const IS_FIREFOX$1 = CAN_USE_DOM$1 && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
const CAN_USE_BEFORE_INPUT$1 = CAN_USE_DOM$1 && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
const IS_IOS$1 = CAN_USE_DOM$1 && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const IS_ANDROID$1 = CAN_USE_DOM$1 && /Android/.test(navigator.userAgent);
// Exclude Android — Android WebView's UA contains "Version/X.X ... Safari/537.36"
// which falsely matches the Safari regex, activating wrong composition code paths.
const IS_SAFARI$1 = CAN_USE_DOM$1 && /Version\/[\d.]+.*Safari/.test(navigator.userAgent) && !IS_ANDROID$1;
// Keep these in case we need to use them in the future.
// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
const IS_CHROME$1 = CAN_USE_DOM$1 && /^(?=.*Chrome).*/i.test(navigator.userAgent);
// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;
const IS_ANDROID_CHROME$1 = CAN_USE_DOM$1 && IS_ANDROID$1 && IS_CHROME$1;
const IS_APPLE_WEBKIT$1 = CAN_USE_DOM$1 && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && IS_APPLE$1 && !IS_CHROME$1;
/**
* 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.
*
*/
function px(value) {
return `${value}px`;
}
const mutationObserverConfig = {
attributes: true,
characterData: true,
childList: true,
subtree: true
};
function prependDOMNode(parent, node) {
parent.insertBefore(node, parent.firstChild);
}
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
function mlcPositionNodeOnRange(editor, range, onReposition) {
let rootDOMNode = null;
let parentDOMNode = null;
let observer = null;
let lastNodes = [];
const wrapperNode = document.createElement('div');
wrapperNode.style.position = 'relative';
function position() {
if (!(rootDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null rootDOMNode`);
}
if (!(parentDOMNode !== null)) {
formatDevErrorMessage(`Unexpected null parentDOMNode`);
}
const {
left: parentLeft,
top: parentTop
} = parentDOMNode.getBoundingClientRect();
const rects = createRectsFromDOMRange(editor, range);
if (!wrapperNode.isConnected) {
prependDOMNode(parentDOMNode, wrapperNode);
}
let hasRepositioned = false;
for (let i = 0; i < rects.length; i++) {
const rect = rects[i];
// Try to reuse the previously created Node when possible, no need to
// remove/create on the most common case reposition case
const rectNode = lastNodes[i] || document.createElement('div');
const rectNodeStyle = rectNode.style;
if (rectNodeStyle.position !== 'absolute') {
rectNodeStyle.position = 'absolute';
hasRepositioned = true;
}
const left = px(rect.left - parentLeft);
if (rectNodeStyle.left !== left) {
rectNodeStyle.left = left;
hasRepositioned = true;
}
const top = px(rect.top - parentTop);
if (rectNodeStyle.top !== top) {
rectNode.style.top = top;
hasRepositioned = true;
}
const width = px(rect.width);
if (rectNodeStyle.width !== width) {
rectNode.style.width = width;
hasRepositioned = true;
}
const height = px(rect.height);
if (rectNodeStyle.height !== height) {
rectNode.style.height = height;
hasRepositioned = true;
}
if (rectNode.parentNode !== wrapperNode) {
wrapperNode.append(rectNode);
hasRepositioned = true;
}
lastNodes[i] = rectNode;
}
while (lastNodes.length > rects.length) {
lastNodes.pop();
}
if (hasRepositioned) {
onReposition(lastNodes);
}
}
function stop() {
parentDOMNode = null;
rootDOMNode = null;
if (observer !== null) {
observer.disconnect();
}
observer = null;
wrapperNode.remove();
for (const node of lastNodes) {
node.remove();
}
lastNodes = [];
}
function restart() {
const currentRootDOMNode = editor.getRootElement();
if (currentRootDOMNode === null) {
return stop();
}
const currentParentDOMNode = currentRootDOMNode.parentElement;
if (!isHTMLElement(currentParentDOMNode)) {
return stop();
}
stop();
rootDOMNode = currentRootDOMNode;
parentDOMNode = currentParentDOMNode;
observer = new MutationObserver(mutations => {
const nextRootDOMNode = editor.getRootElement();
const nextParentDOMNode = nextRootDOMNode && nextRootDOMNode.parentElement;
if (nextRootDOMNode !== rootDOMNode || nextParentDOMNode !== parentDOMNode) {
return restart();
}
for (const mutation of mutations) {
if (!wrapperNode.contains(mutation.target)) {
// TODO throttle
return position();
}
}
});
observer.observe(currentParentDOMNode, mutationObserverConfig);
position();
}
const removeRootListener = editor.registerRootListener(restart);
return () => {
removeRootListener();
stop();
};
}
/**
* 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.
*
*/
function $getOrderedSelectionPoints(selection) {
const points = selection.getStartEndPoints();
return selection.isBackward() ? [points[1], points[0]] : points;
}
function $rangeTargetFromPoint(editor, point, node, dom) {
if (point.type === 'text' || !$isElementNode(node)) {
const textDOM = getDOMTextNode(dom) || dom;
return [textDOM, point.offset];
} else {
const slot = $getEditorDOMRenderConfig(editor).$getDOMSlot(node, dom, editor);
return [slot.element, slot.getFirstChildOffset() + point.offset];
}
}
function $rangeFromPoints(editor, start, startNode, startDOM, end, endNode, endDOM) {
const editorDocument = editor._window ? editor._window.document : document;
const range = editorDocument.createRange();
range.setStart(...$rangeTargetFromPoint(editor, start, startNode, startDOM));
range.setEnd(...$rangeTargetFromPoint(editor, end, endNode, endDOM));
return range;
}
function defaultOnReposition(domNodes) {
for (const domNode of domNodes) {
const domNodeStyle = domNode.style;
if (domNodeStyle.background !== 'Highlight') {
domNodeStyle.background = 'Highlight';
}
if (domNodeStyle.color !== 'HighlightText') {
domNodeStyle.color = 'HighlightText';
}
if (domNodeStyle.marginTop !== px(-1.5)) {
domNodeStyle.marginTop = px(-1.5);
}
if (domNodeStyle.paddingTop !== px(4)) {
domNodeStyle.paddingTop = px(4);
}
if (domNodeStyle.paddingBottom !== px(0)) {
domNodeStyle.paddingBottom = px(0);
}
}
}
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
function markSelection(editor, onReposition = defaultOnReposition) {
let previousAnchorNode = null;
let previousAnchorNodeDOM = null;
let previousAnchorOffset = null;
let previousFocusNode = null;
let previousFocusNodeDOM = null;
let previousFocusOffset = null;
let removeRangeListener = () => {};
function compute(editorState) {
editorState.read(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
// TODO
previousAnchorNode = null;
previousAnchorOffset = null;
previousFocusNode = null;
previousFocusOffset = null;
removeRangeListener();
removeRangeListener = () => {};
return;
}
const [start, end] = $getOrderedSelectionPoints(selection);
const currentStartNode = start.getNode();
const currentStartNodeKey = currentStartNode.getKey();
const currentStartOffset = start.offset;
const currentEndNode = end.getNode();
const currentEndNodeKey = currentEndNode.getKey();
const currentEndOffset = end.offset;
const currentStartNodeDOM = editor.getElementByKey(currentStartNodeKey);
const currentEndNodeDOM = editor.getElementByKey(currentEndNodeKey);
const differentStartDOM = previousAnchorNode === null || currentStartNodeDOM !== previousAnchorNodeDOM || currentStartOffset !== previousAnchorOffset || currentStartNodeKey !== previousAnchorNode.getKey();
const differentEndDOM = previousFocusNode === null || currentEndNodeDOM !== previousFocusNodeDOM || currentEndOffset !== previousFocusOffset || currentEndNodeKey !== previousFocusNode.getKey();
if ((differentStartDOM || differentEndDOM) && currentStartNodeDOM !== null && currentEndNodeDOM !== null) {
const range = $rangeFromPoints(editor, start, currentStartNode, currentStartNodeDOM, end, currentEndNode, currentEndNodeDOM);
removeRangeListener();
removeRangeListener = mlcPositionNodeOnRange(editor, range, onReposition);
}
previousAnchorNode = currentStartNode;
previousAnchorNodeDOM = currentStartNodeDOM;
previousAnchorOffset = currentStartOffset;
previousFocusNode = currentEndNode;
previousFocusNodeDOM = currentEndNodeDOM;
previousFocusOffset = currentEndOffset;
});
}
compute(editor.getEditorState());
return mergeRegister(editor.registerUpdateListener(({
editorState
}) => compute(editorState)), () => {
removeRangeListener();
});
}
/**
* 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.
*
*/
function selectionAlwaysOnDisplay(editor, onReposition) {
let removeSelectionMark = null;
const onSelectionChange = () => {
const domSelection = getSelection();
const domAnchorNode = domSelection && domSelection.anchorNode;
const editorRootElement = editor.getRootElement();
const isSelectionInsideEditor = domAnchorNode !== null && editorRootElement !== null && editorRootElement.contains(domAnchorNode);
if (isSelectionInsideEditor) {
if (removeSelectionMark !== null) {
removeSelectionMark();
removeSelectionMark = null;
}
} else {
if (removeSelectionMark === null) {
removeSelectionMark = markSelection(editor, onReposition);
}
}
};
return editor.registerRootListener(rootElement => {
if (rootElement) {
const document = rootElement.ownerDocument;
document.addEventListener('selectionchange', onSelectionChange);
onSelectionChange();
return () => {
if (removeSelectionMark !== null) {
removeSelectionMark();
}
document.removeEventListener('selectionchange', onSelectionChange);
};
}
});
}
// Hotfix to export these with inlined types #5918
const CAN_USE_BEFORE_INPUT = CAN_USE_BEFORE_INPUT$1;
const CAN_USE_DOM = CAN_USE_DOM$1;
const IS_ANDROID = IS_ANDROID$1;
const IS_ANDROID_CHROME = IS_ANDROID_CHROME$1;
const IS_APPLE = IS_APPLE$1;
const IS_APPLE_WEBKIT = IS_APPLE_WEBKIT$1;
const IS_CHROME = IS_CHROME$1;
const IS_FIREFOX = IS_FIREFOX$1;
const IS_IOS = IS_IOS$1;
const IS_SAFARI = IS_SAFARI$1;
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
function isMimeType(file, acceptableMimeTypes) {
for (const acceptableType of acceptableMimeTypes) {
if (file.type.startsWith(acceptableType)) {
return true;
}
}
return false;
}
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', \\{
* src: file.result,
* \\}));
*/
function mediaFileReader(files, acceptableMimeTypes) {
const filesIterator = files[Symbol.iterator]();
return new Promise((resolve, reject) => {
const processed = [];
const handleNextFile = () => {
const {
done,
value: file
} = filesIterator.next();
if (done) {
return resolve(processed);
}
const fileReader = new FileReader();
fileReader.addEventListener('error', reject);
fileReader.addEventListener('load', () => {
const result = fileReader.result;
if (typeof result === 'string') {
processed.push({
file,
result
});
}
handleNextFile();
});
if (isMimeType(file, acceptableMimeTypes)) {
fileReader.readAsDataURL(file);
} else {
handleNextFile();
}
};
handleNextFile();
});
}
/**
* "Depth-First Search" starts at the root/top node of a tree and goes as far as it can down a branch end
* before backtracking and finding a new path. Consider solving a maze by hugging either wall, moving down a
* branch until you hit a dead-end (leaf) and backtracking to find the nearest branching path and repeat.
* It will then return all the nodes found in the search in an array of objects.
* Preorder traversal is used, meaning that nodes are listed in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode. If endNode
* is an ElementNode, it will stop before visiting any of its children.
* @returns An array of objects of all the nodes found by the search, including their depth into the tree.
* \\{depth: number, node: LexicalNode\\} It will always return at least 1 node (the start node).
*/
function $dfs(startNode, endNode) {
return Array.from($dfsIterator(startNode, endNode));
}
/**
* Get the adjacent caret in the same direction
*
* @param caret A caret or null
* @returns `caret.getAdjacentCaret()` or `null`
*/
function $getAdjacentCaret(caret) {
return caret ? caret.getAdjacentCaret() : null;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfs(startNode, endNode) {
return Array.from($reverseDfsIterator(startNode, endNode));
}
/**
* $dfs iterator (left to right). Tree traversal is done on the fly as new values are requested with O(1) memory.
* Preorder traversal is used, meaning that nodes are iterated over in the order of when they are FIRST encountered.
* @param startNode - The node to start the search (inclusive), if omitted, it will start at the root node.
* @param endNode - The node to end the search (inclusive), if omitted, it will find all descendants of the startingNode.
* If endNode is an ElementNode, the iterator will end as soon as it reaches the endNode (no children will be visited).
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $dfsIterator(startNode, endNode) {
return $dfsCaretIterator('next', startNode, endNode);
}
function $getEndCaret(startNode, direction) {
const rval = $getAdjacentSiblingOrParentSiblingCaret($getSiblingCaret(startNode, direction));
return rval && rval[0];
}
function $dfsCaretIterator(direction, startNode, endNode) {
const root = $getRoot();
const start = startNode || root;
const startCaret = $isElementNode(start) ? $getChildCaret(start, direction) : $getSiblingCaret(start, direction);
const startDepth = $getDepth(start);
const endCaret = endNode ? $getAdjacentChildCaret($getChildCaretOrSelf($getSiblingCaret(endNode, direction))) || $getEndCaret(endNode, direction) : $getEndCaret(start, direction);
let depth = startDepth;
return makeStepwiseIterator({
hasNext: state => state !== null,
initial: startCaret,
map: state => ({
depth,
node: state.origin
}),
step: state => {
if (state.isSameNodeCaret(endCaret)) {
return null;
}
if ($isChildCaret(state)) {
depth++;
}
const rval = $getAdjacentSiblingOrParentSiblingCaret(state);
if (!rval || rval[0].isSameNodeCaret(endCaret)) {
return null;
}
depth += rval[1];
return rval[0];
}
});
}
/**
* Returns the Node sibling when this exists, otherwise the closest parent sibling. For example
* R -> P -> T1, T2
* -> P2
* returns T2 for node T1, P2 for node T2, and null for node P2.
* @param node LexicalNode.
* @returns An array (tuple) containing the found Lexical node and the depth difference, or null, if this node doesn't exist.
*/
function $getNextSiblingOrParentSibling(node) {
const rval = $getAdjacentSiblingOrParentSiblingCaret($getSiblingCaret(node, 'next'));
return rval && [rval[0].origin, rval[1]];
}
function $getDepth(node) {
let depth = -1;
for (let innerNode = node; innerNode !== null; innerNode = innerNode.getParent()) {
depth++;
}
return depth;
}
/**
* Performs a right-to-left preorder tree traversal.
* From the starting node it goes to the rightmost child, than backtracks to parent and finds new rightmost path.
* It will return the next node in traversal sequence after the startingNode.
* The traversal is similar to $dfs functions above, but the nodes are visited right-to-left, not left-to-right.
* @param startingNode - The node to start the search.
* @returns The next node in pre-order right to left traversal sequence or `null`, if the node does not exist
*/
function $getNextRightPreorderNode(startingNode) {
const startCaret = $getChildCaretOrSelf($getSiblingCaret(startingNode, 'previous'));
const next = $getAdjacentSiblingOrParentSiblingCaret(startCaret, 'root');
return next && next[0].origin;
}
/**
* $dfs iterator (right to left). Tree traversal is done on the fly as new values are requested with O(1) memory.
* @param startNode - The node to start the search, if omitted, it will start at the root node.
* @param endNode - The node to end the search, if omitted, it will find all descendants of the startingNode.
* @returns An iterator, each yielded value is a DFSNode. It will always return at least 1 node (the start node).
*/
function $reverseDfsIterator(startNode, endNode) {
return $dfsCaretIterator('previous', startNode, endNode);
}
/**
* Takes a node and traverses up its ancestors (toward the root node)
* in order to find a specific type of node.
* @param node - the node to begin searching.
* @param klass - an instance of the type of node to look for.
* @returns the node of type klass that was passed, or null if none exist.
*/
function $getNearestNodeOfType(node, klass) {
let parent = node;
while (parent != null) {
if (parent instanceof klass) {
return parent;
}
parent = parent.getParent();
}
return null;
}
/**
* Returns the element node of the nearest ancestor, otherwise throws an error.
* @param startNode - The starting node of the search
* @returns The ancestor node found
*/
function $getNearestBlockElementAncestorOrThrow(startNode) {
const blockNode = $findMatchingParent(startNode, node => $isElementNode(node) && !node.isInline());
if (!$isElementNode(blockNode)) {
{
formatDevErrorMessage(`Expected node ${startNode.__key} to have closest block element node.`);
}
}
return blockNode;
}
/**
* Attempts to resolve nested element nodes of the same type into a single node of that type.
* It is generally used for marks/commenting
* @param editor - The lexical editor
* @param targetNode - The target for the nested element to be extracted from.
* @param cloneNode - See {@link $createMarkNode}
* @param handleOverlap - Handles any overlap between the node to extract and the targetNode
* @returns The lexical editor
*/
function registerNestedElementResolver(editor, targetNode, cloneNode, handleOverlap) {
const $isTargetNode = node => {
return node instanceof targetNode;
};
const $findMatch = node => {
// First validate we don't have any children that are of the target,
// as we need to handle them first.
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if ($isTargetNode(child)) {
return null;
}
}
let parentNode = node;
let childNode = node;
while (parentNode !== null) {
childNode = parentNode;
parentNode = parentNode.getParent();
if ($isTargetNode(parentNode)) {
return {
child: childNode,
parent: parentNode
};
}
}
return null;
};
const $elementNodeTransform = node => {
const match = $findMatch(node);
if (match !== null) {
const {
child,
parent
} = match;
// Simple path, we can move child out and siblings into a new parent.
if (child.is(node)) {
handleOverlap(parent, node);
const nextSiblings = child.getNextSiblings();
const nextSiblingsLength = nextSiblings.length;
parent.insertAfter(child);
if (nextSiblingsLength !== 0) {
const newParent = cloneNode(parent);
child.insertAfter(newParent);
for (let i = 0; i < nextSiblingsLength; i++) {
newParent.append(nextSiblings[i]);
}
}
if (!parent.canBeEmpty() && parent.getChildrenSize() === 0) {
parent.remove();
}
}
}
};
return editor.registerNodeTransform(targetNode, $elementNodeTransform);
}
/**
* Clones the editor and marks it as dirty to be reconciled. If there was a selection,
* it would be set back to its previous state, or null otherwise.
* @param editor - The lexical editor
* @param editorState - The editor's state
*/
function $restoreEditorState(editor, editorState) {
const FULL_RECONCILE = 2;
const nodeMap = new Map();
const activeEditorState = editor._pendingEditorState;
for (const [key, node] of editorState._nodeMap) {
nodeMap.set(key, $cloneWithProperties(node));
}
if (activeEditorState) {
activeEditorState._nodeMap = nodeMap;
}
editor._dirtyType = FULL_RECONCILE;
const selection = editorState._selection;
$setSelection(selection === null ? null : selection.clone());
}
/**
* If the selected insertion area is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be appended there, otherwise, it will be inserted before the insertion area.
* If there is no selection where the node is to be inserted, it will be appended after any current nodes
* within the tree, as a child of the root node. A paragraph will then be added after the inserted node and selected.
* @param node - The node to be inserted
* @returns The node after its insertion
*/
function $insertNodeToNearestRoot(node) {
const selection = $getSelection() || $getPreviousSelection();
let initialCaret;
if ($isRangeSelection(selection)) {
initialCaret = $caretFromPoint(selection.focus, 'next');
} else {
if (selection != null) {
const nodes = selection.getNodes();
const lastNode = nodes[nodes.length - 1];
if (lastNode) {
initialCaret = $getSiblingCaret(lastNode, 'next');
}
}
initialCaret = initialCaret || $getChildCaret($getRoot(), 'previous').getFlipped().insert($createParagraphNode());
}
const insertCaret = $insertNodeToNearestRootAtCaret(node, initialCaret);
const adjacent = $getAdjacentChildCaret(insertCaret);
const selectionCaret = $isChildCaret(adjacent) ? $normalizeCaret(adjacent) : insertCaret;
$setSelectionFromCaretRange($getCollapsedCaretRange(selectionCaret));
return node.getLatest();
}
/**
* If the insertion caret is the root/shadow root node (see {@link lexical!$isRootOrShadowRoot}),
* the node will be inserted there, otherwise the parent nodes will be split according to the
* given options.
* @param node - The node to be inserted
* @param caret - The location to insert or split from
* @returns The node after its insertion
*/
function $insertNodeToNearestRootAtCaret(node, caret, options) {
let insertCaret = $getCaretInDirection(caret, 'next');
// Normalize boundary cases for TextPointCaret
if ($isTextPointCaret(insertCaret)) {
if (insertCaret.offset === 0) {
insertCaret = $getSiblingCaret(insertCaret.origin, 'previous').getFlipped();
} else if (insertCaret.offset === insertCaret.origin.getTextContentSize()) {
insertCaret = $getSiblingCaret(insertCaret.origin, 'next');
}
}
// Make sure we have a distinct node as the origin
if (insertCaret.origin.is(node)) {
if (!$isSiblingCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret node ${node.getKey()} of type ${node.getType()} can not be inserted into itself`);
}
insertCaret = $rewindSiblingCaret(insertCaret);
}
// Handle split boundary conditions where node is being inserted adjacent to itself
if (node.is(insertCaret.getNodeAtCaret()) || node.is(insertCaret.getFlipped().getNodeAtCaret())) {
node.remove(true);
}
for (let nextCaret = insertCaret; nextCaret; nextCaret = $splitAtPointCaretNext(nextCaret, options)) {
insertCaret = nextCaret;
}
if (!!$isTextPointCaret(insertCaret)) {
formatDevErrorMessage(`$insertNodeToNearestRootAtCaret: An unattached TextNode can not be split`);
}
insertCaret.insert(node.isInline() ? $createParagraphNode().append(node) : node);
return $getCaretInDirection($getSiblingCaret(node.getLatest(), 'next'), caret.direction);
}
/**
* Inserts a node into leaf — the deepest accessible node at the carriage position
* @param node - The node to be inserted
*/
function $insertNodeIntoLeaf(node) {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
if (selection) {
selection.insertNodes([node]);
}
return;
}
const caretRange = $caretRangeFromSelection(selection);
let insertCaret = $getCaretRangeInDirection($removeTextFromCaretRange(caretRange), 'next').anchor;
if ($isTextPointCaret(insertCaret)) {
const nextAnchor = $splitAtPointCaretNext(insertCaret);
if (!nextAnchor) {
return;
}
insertCaret = nextAnchor;
}
const focus = insertCaret.getFlipped();
focus.insert(node);
$setSelectionFromCaretRange($getCaretRange(focus, focus));
}
/**
* Wraps the node into another node created from a createElementNode function, eg. $createParagraphNode
* @param node - Node to be wrapped.
* @param createElementNode - Creates a new lexical element to wrap the to-be-wrapped node and returns it.
* @returns A new lexical element with the previous node appended within (as a child, including its children).
*/
function $wrapNodeInElement(node, createElementNode) {
const elementNode = createElementNode();
node.replace(elementNode);
elementNode.append(node);
return elementNode;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* @param object = The instance of the type
* @param objectClass = The class of the type
* @returns Whether the object is has the same Klass of the objectClass, ignoring the difference across window (e.g. different iframes)
*/
function objectKlassEquals(object, objectClass) {
return object !== null ? Object.getPrototypeOf(object).constructor.name === objectClass.name : false;
}
/**
* @deprecated Use Array filter or flatMap
*
* Filter the nodes
* @param nodes Array of nodes that needs to be filtered
* @param filterFn A filter function that returns node if the current node satisfies the condition otherwise null
* @returns Array of filtered nodes
*/
function $filter(nodes, filterFn) {
const result = [];
for (let i = 0; i < nodes.length; i++) {
const node = filterFn(nodes[i]);
if (node !== null) {
result.push(node);
}
}
return result;
}
/**
* Applies the provided callback to each indentable block element in the Selection
*
* @param indentOrOutdent callback for performing the indent or outdent action
* on a given block element.
* @returns true if at least one block was handled, false otherwise.
*/
function $handleIndentAndOutdent(indentOrOutdent) {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const alreadyHandled = new Set();
const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getKey();
if (alreadyHandled.has(key)) {
continue;
}
const parentBlock = $findMatchingParent(node, parentNode => $isElementNode(parentNode) && !parentNode.isInline());
if (parentBlock === null) {
continue;
}
const parentKey = parentBlock.getKey();
if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
alreadyHandled.add(parentKey);
indentOrOutdent(parentBlock);
}
}
return alreadyHandled.size > 0;
}
/**
* Appends the node before the first child of the parent node
* @param parent A parent node
* @param node Node that needs to be appended
*/
function $insertFirst(parent, node) {
$getChildCaret(parent, 'next').insert(node);
}
let NEEDS_MANUAL_ZOOM = IS_FIREFOX || !CAN_USE_DOM ? false : undefined;
function needsManualZoom() {
if (NEEDS_MANUAL_ZOOM === undefined) {
// If the browser implements standardized CSS zoom, then the client rect
// will be wider after zoom is applied
// https://chromestatus.com/feature/5198254868529152
// https://github.com/facebook/lexical/issues/6863
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.opacity = '0';
div.style.width = '100px';
div.style.left = '-1000px';
document.body.appendChild(div);
const noZoom = div.getBoundingClientRect();
div.style.setProperty('zoom', '2');
NEEDS_MANUAL_ZOOM = div.getBoundingClientRect().width === noZoom.width;
document.body.removeChild(div);
}
return NEEDS_MANUAL_ZOOM;
}
/**
* Calculates the zoom level of an element as a result of using
* css zoom property. For browsers that implement standardized CSS
* zoom (Firefox, Chrome >= 128), this will always return 1.
* @param element
* @param useManualZoom - If true, always use zoom level will be calculated manually, otherwise it will be calculated on as needed basis.
*/
function calculateZoomLevel(element, useManualZoom = false) {
let zoom = 1;
if (needsManualZoom() || useManualZoom) {
while (element) {
zoom *= Number(window.getComputedStyle(element).getPropertyValue('zoom'));
element = element.parentElement;
}
}
return zoom;
}
/**
* Checks if the editor is a nested editor created by LexicalNestedComposer
*/
function $isEditorIsNestedEditor(editor) {
return editor._parentEditor !== null;
}
/**
* A depth first last-to-first traversal of root that stops at each node that matches
* $predicate and ensures that its parent is root. This is typically used to discard
* invalid or unsupported wrapping nodes. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* @param root The root to start the traversal
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns true if this unwrapped or removed any nodes
*/
function $unwrapAndFilterDescendants(root, $predicate) {
return $unwrapAndFilterDescendantsImpl(root, $predicate, null);
}
function $unwrapAndFilterDescendantsImpl(root, $predicate, $onSuccess) {
let didMutate = false;
for (const node of $lastToFirstIterator(root)) {
if ($predicate(node)) {
if ($onSuccess !== null) {
$onSuccess(node);
}
continue;
}
didMutate = true;
if ($isElementNode(node)) {
$unwrapAndFilterDescendantsImpl(node, $predicate, $onSuccess || (child => node.insertAfter(child)));
}
node.remove();
}
return didMutate;
}
/**
* A depth first traversal of the children array that stops at and collects
* each node that `$predicate` matches. This is typically used to discard
* invalid or unsupported wrapping nodes on a children array in the `after`
* of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have
* TableRowNode as children, but an importer might add invalid nodes based on
* caption, tbody, thead, etc. and this will unwrap and discard those.
*
* This function is read-only and performs no mutation operations, which makes
* it suitable for import and export purposes but likely not for any in-place
* mutation. You should use {@link $unwrapAndFilterDescendants} for in-place
* mutations such as node transforms.
*
* @param children The children to traverse
* @param $predicate Should return true for nodes that are permitted to be children of root
* @returns The children or their descendants that match $predicate
*/
function $descendantsMatching(children, $predicate) {
const result = [];
const stack = Array.from(children).reverse();
for (let child = stack.pop(); child !== undefined; child = stack.pop()) {
if ($predicate(child)) {
result.push(child);
} else if ($isElementNode(child)) {
for (const grandchild of $lastToFirstIterator(child)) {
stack.push(grandchild);
}
}
}
return result;
}
/**
* Return an iterator that yields each child of node from first to last, taking
* care to preserve the next sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $firstToLastIterator(node) {
return $childIterator($getChildCaret(node, 'next'));
}
/**
* Return an iterator that yields each child of node from last to first, taking
* care to preserve the previous sibling before yielding the value in case the caller
* removes the yielded node.
*
* @param node The node whose children to iterate
* @returns An iterator of the node's children
*/
function $lastToFirstIterator(node) {
return $childIterator($getChildCaret(node, 'previous'));
}
function $childIterator(startCaret) {
const seen = new Set() ;
return makeStepwiseIterator({
hasNext: $isSiblingCaret,
initial: startCaret.getAdjacentCaret(),
map: caret => {
const origin = caret.origin.getLatest();
if (seen !== null) {
const key = origin.getKey();
if (!!seen.has(key)) {
formatDevErrorMessage(`$childIterator: Cycle detected, node with key ${String(key)} has already been traversed`);
}
seen.add(key);
}
return origin;
},
step: caret => caret.getAdjacentCaret()
});
}
/**
* Replace this node with its children
*
* @param node The ElementNode to unwrap and remove
*/
function $unwrapNode(node) {
$rewindSiblingCaret($getSiblingCaret(node, 'next')).splice(1, node.getChildren());
}
/**
* A wrapper that creates bound functions and methods for the
* StateConfig to save some boilerplate when defining methods
* or exporting only the accessors from your modules rather
* than exposing the StateConfig directly.
*/
/**
* EXPERIMENTAL
*
* A convenience interface for working with {@link $getState} and
* {@link $setState}.
*
* @param stateConfig The stateConfig to wrap with convenience functionality
* @returns a StateWrapper
*/
function makeStateWrapper(stateConfig) {
const $get = node => $getState(node, stateConfig);
const $set = (node, valueOrUpdater) => $setState(node, stateConfig, valueOrUpdater);
return {
$get,
$set,
accessors: [$get, $set],
makeGetterMethod: () => function $getter() {
return $get(this);
},
makeSetterMethod: () => function $setter(valueOrUpdater) {
return $set(this, valueOrUpdater);
},
stateConfig
};
}
export { $descendantsMatching, $dfs, $dfsIterator, $filter, $firstToLastIterator, $getAdjacentCaret, $getDepth, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $getNextRightPreorderNode, $getNextSiblingOrParentSibling, $handleIndentAndOutdent, $insertFirst, $insertNodeIntoLeaf, $insertNodeToNearestRoot, $insertNodeToNearestRootAtCaret, $isEditorIsNestedEditor, $lastToFirstIterator, $restoreEditorState, $reverseDfs, $reverseDfsIterator, $unwrapAndFilterDescendants, $unwrapNode, $wrapNodeInElement, CAN_USE_BEFORE_INPUT, CAN_USE_DOM, IS_ANDROID, IS_ANDROID_CHROME, IS_APPLE, IS_APPLE_WEBKIT, IS_CHROME, IS_FIREFOX, IS_IOS, IS_SAFARI, calculateZoomLevel, isMimeType, makeStateWrapper, markSelection, mediaFileReader, objectKlassEquals, mlcPositionNodeOnRange as positionNodeOnRange, registerNestedElementResolver, selectionAlwaysOnDisplay };
/**
* 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.
*
*/
'use strict'
const LexicalUtils = process.env.NODE_ENV !== 'production' ? require('./LexicalUtils.dev.js') : require('./LexicalUtils.prod.js');
module.exports = LexicalUtils;

Sorry, the diff of this file is not supported yet

/**
* 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 * as modDev from './LexicalUtils.dev.mjs';
import * as modProd from './LexicalUtils.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const $descendantsMatching = mod.$descendantsMatching;
export const $dfs = mod.$dfs;
export const $dfsIterator = mod.$dfsIterator;
export const $filter = mod.$filter;
export const $findMatchingParent = mod.$findMatchingParent;
export const $firstToLastIterator = mod.$firstToLastIterator;
export const $getAdjacentCaret = mod.$getAdjacentCaret;
export const $getAdjacentSiblingOrParentSiblingCaret = mod.$getAdjacentSiblingOrParentSiblingCaret;
export const $getDepth = mod.$getDepth;
export const $getNearestBlockElementAncestorOrThrow = mod.$getNearestBlockElementAncestorOrThrow;
export const $getNearestNodeOfType = mod.$getNearestNodeOfType;
export const $getNextRightPreorderNode = mod.$getNextRightPreorderNode;
export const $getNextSiblingOrParentSibling = mod.$getNextSiblingOrParentSibling;
export const $handleIndentAndOutdent = mod.$handleIndentAndOutdent;
export const $insertFirst = mod.$insertFirst;
export const $insertNodeIntoLeaf = mod.$insertNodeIntoLeaf;
export const $insertNodeToNearestRoot = mod.$insertNodeToNearestRoot;
export const $insertNodeToNearestRootAtCaret = mod.$insertNodeToNearestRootAtCaret;
export const $isEditorIsNestedEditor = mod.$isEditorIsNestedEditor;
export const $lastToFirstIterator = mod.$lastToFirstIterator;
export const $restoreEditorState = mod.$restoreEditorState;
export const $reverseDfs = mod.$reverseDfs;
export const $reverseDfsIterator = mod.$reverseDfsIterator;
export const $splitNode = mod.$splitNode;
export const $unwrapAndFilterDescendants = mod.$unwrapAndFilterDescendants;
export const $unwrapNode = mod.$unwrapNode;
export const $wrapNodeInElement = mod.$wrapNodeInElement;
export const CAN_USE_BEFORE_INPUT = mod.CAN_USE_BEFORE_INPUT;
export const CAN_USE_DOM = mod.CAN_USE_DOM;
export const IS_ANDROID = mod.IS_ANDROID;
export const IS_ANDROID_CHROME = mod.IS_ANDROID_CHROME;
export const IS_APPLE = mod.IS_APPLE;
export const IS_APPLE_WEBKIT = mod.IS_APPLE_WEBKIT;
export const IS_CHROME = mod.IS_CHROME;
export const IS_FIREFOX = mod.IS_FIREFOX;
export const IS_IOS = mod.IS_IOS;
export const IS_SAFARI = mod.IS_SAFARI;
export const addClassNamesToElement = mod.addClassNamesToElement;
export const calculateZoomLevel = mod.calculateZoomLevel;
export const isBlockDomNode = mod.isBlockDomNode;
export const isHTMLAnchorElement = mod.isHTMLAnchorElement;
export const isHTMLElement = mod.isHTMLElement;
export const isInlineDomNode = mod.isInlineDomNode;
export const isMimeType = mod.isMimeType;
export const makeStateWrapper = mod.makeStateWrapper;
export const markSelection = mod.markSelection;
export const mediaFileReader = mod.mediaFileReader;
export const mergeRegister = mod.mergeRegister;
export const objectKlassEquals = mod.objectKlassEquals;
export const positionNodeOnRange = mod.positionNodeOnRange;
export const registerNestedElementResolver = mod.registerNestedElementResolver;
export const removeClassNamesFromElement = mod.removeClassNamesFromElement;
export const selectionAlwaysOnDisplay = mod.selectionAlwaysOnDisplay;
/**
* 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.
*
*/
const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalUtils.dev.mjs') : import('./LexicalUtils.prod.mjs'));
export const $descendantsMatching = mod.$descendantsMatching;
export const $dfs = mod.$dfs;
export const $dfsIterator = mod.$dfsIterator;
export const $filter = mod.$filter;
export const $findMatchingParent = mod.$findMatchingParent;
export const $firstToLastIterator = mod.$firstToLastIterator;
export const $getAdjacentCaret = mod.$getAdjacentCaret;
export const $getAdjacentSiblingOrParentSiblingCaret = mod.$getAdjacentSiblingOrParentSiblingCaret;
export const $getDepth = mod.$getDepth;
export const $getNearestBlockElementAncestorOrThrow = mod.$getNearestBlockElementAncestorOrThrow;
export const $getNearestNodeOfType = mod.$getNearestNodeOfType;
export const $getNextRightPreorderNode = mod.$getNextRightPreorderNode;
export const $getNextSiblingOrParentSibling = mod.$getNextSiblingOrParentSibling;
export const $handleIndentAndOutdent = mod.$handleIndentAndOutdent;
export const $insertFirst = mod.$insertFirst;
export const $insertNodeIntoLeaf = mod.$insertNodeIntoLeaf;
export const $insertNodeToNearestRoot = mod.$insertNodeToNearestRoot;
export const $insertNodeToNearestRootAtCaret = mod.$insertNodeToNearestRootAtCaret;
export const $isEditorIsNestedEditor = mod.$isEditorIsNestedEditor;
export const $lastToFirstIterator = mod.$lastToFirstIterator;
export const $restoreEditorState = mod.$restoreEditorState;
export const $reverseDfs = mod.$reverseDfs;
export const $reverseDfsIterator = mod.$reverseDfsIterator;
export const $splitNode = mod.$splitNode;
export const $unwrapAndFilterDescendants = mod.$unwrapAndFilterDescendants;
export const $unwrapNode = mod.$unwrapNode;
export const $wrapNodeInElement = mod.$wrapNodeInElement;
export const CAN_USE_BEFORE_INPUT = mod.CAN_USE_BEFORE_INPUT;
export const CAN_USE_DOM = mod.CAN_USE_DOM;
export const IS_ANDROID = mod.IS_ANDROID;
export const IS_ANDROID_CHROME = mod.IS_ANDROID_CHROME;
export const IS_APPLE = mod.IS_APPLE;
export const IS_APPLE_WEBKIT = mod.IS_APPLE_WEBKIT;
export const IS_CHROME = mod.IS_CHROME;
export const IS_FIREFOX = mod.IS_FIREFOX;
export const IS_IOS = mod.IS_IOS;
export const IS_SAFARI = mod.IS_SAFARI;
export const addClassNamesToElement = mod.addClassNamesToElement;
export const calculateZoomLevel = mod.calculateZoomLevel;
export const isBlockDomNode = mod.isBlockDomNode;
export const isHTMLAnchorElement = mod.isHTMLAnchorElement;
export const isHTMLElement = mod.isHTMLElement;
export const isInlineDomNode = mod.isInlineDomNode;
export const isMimeType = mod.isMimeType;
export const makeStateWrapper = mod.makeStateWrapper;
export const markSelection = mod.markSelection;
export const mediaFileReader = mod.mediaFileReader;
export const mergeRegister = mod.mergeRegister;
export const objectKlassEquals = mod.objectKlassEquals;
export const positionNodeOnRange = mod.positionNodeOnRange;
export const registerNestedElementResolver = mod.registerNestedElementResolver;
export const removeClassNamesFromElement = mod.removeClassNamesFromElement;
export const selectionAlwaysOnDisplay = mod.selectionAlwaysOnDisplay;
/**
* 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.
*
*/
"use strict";var e=require("lexical"),t=require("@lexical/selection");function n(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const r="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,o=r&&"documentMode"in document?document.documentMode:null,i=r&&/Mac|iPod|iPhone|iPad/.test(navigator.platform),s=r&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent),l=!(!r||!("InputEvent"in window)||o)&&"getTargetRanges"in new window.InputEvent("input"),a=r&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,c=r&&/Android/.test(navigator.userAgent),u=r&&/Version\/[\d.]+.*Safari/.test(navigator.userAgent)&&!c,g=r&&/^(?=.*Chrome).*/i.test(navigator.userAgent),d=r&&c&&g,f=r&&/AppleWebKit\/[\d.]+/.test(navigator.userAgent)&&i&&!g;function p(e){return`${e}px`}const m={attributes:!0,characterData:!0,childList:!0,subtree:!0};function $(r,o,i){let s=null,l=null,a=null,c=[];const u=document.createElement("div");function g(){null===s&&n(182),null===l&&n(183);const{left:e,top:a}=l.getBoundingClientRect(),g=t.createRectsFromDOMRange(r,o);var d,f;u.isConnected||(f=u,(d=l).insertBefore(f,d.firstChild));let m=!1;for(let t=0;t<g.length;t++){const n=g[t],r=c[t]||document.createElement("div"),o=r.style;"absolute"!==o.position&&(o.position="absolute",m=!0);const i=p(n.left-e);o.left!==i&&(o.left=i,m=!0);const s=p(n.top-a);o.top!==s&&(r.style.top=s,m=!0);const l=p(n.width);o.width!==l&&(r.style.width=l,m=!0);const d=p(n.height);o.height!==d&&(r.style.height=d,m=!0),r.parentNode!==u&&(u.append(r),m=!0),c[t]=r}for(;c.length>g.length;)c.pop();m&&i(c)}function d(){l=null,s=null,null!==a&&a.disconnect(),a=null,u.remove();for(const e of c)e.remove();c=[]}u.style.position="relative";const f=r.registerRootListener(function t(){const n=r.getRootElement();if(null===n)return d();const o=n.parentElement;if(!e.isHTMLElement(o))return d();d(),s=n,l=o,a=new MutationObserver(e=>{const n=r.getRootElement(),o=n&&n.parentElement;if(n!==s||o!==l)return t();for(const t of e)if(!u.contains(t.target))return g()}),a.observe(o,m),g()});return()=>{f(),d()}}function h(t,n,r,o){if("text"!==n.type&&e.$isElementNode(r)){const i=e.$getEditorDOMRenderConfig(t).$getDOMSlot(r,o,t);return[i.element,i.getFirstChildOffset()+n.offset]}return[e.getDOMTextNode(o)||o,n.offset]}function C(e){for(const t of e){const e=t.style;"Highlight"!==e.background&&(e.background="Highlight"),"HighlightText"!==e.color&&(e.color="HighlightText"),e.marginTop!==p(-1.5)&&(e.marginTop=p(-1.5)),e.paddingTop!==p(4)&&(e.paddingTop=p(4)),e.paddingBottom!==p(0)&&(e.paddingBottom=p(0))}}function x(t,n=C){let r=null,o=null,i=null,s=null,l=null,a=null,c=()=>{};function u(u){u.read(()=>{const u=e.$getSelection();if(!e.$isRangeSelection(u))return r=null,i=null,s=null,a=null,c(),void(c=()=>{});const[g,d]=function(e){const t=e.getStartEndPoints();return e.isBackward()?[t[1],t[0]]:t}(u),f=g.getNode(),p=f.getKey(),m=g.offset,C=d.getNode(),x=C.getKey(),S=d.offset,E=t.getElementByKey(p),N=t.getElementByKey(x),v=null===r||E!==o||m!==i||p!==r.getKey(),y=null===s||N!==l||S!==a||x!==s.getKey();if((v||y)&&null!==E&&null!==N){const e=function(e,t,n,r,o,i,s){const l=(e._window?e._window.document:document).createRange();return l.setStart(...h(e,t,n,r)),l.setEnd(...h(e,o,i,s)),l}(t,g,f,E,d,C,N);c(),c=$(t,e,n)}r=f,o=E,i=m,s=C,l=N,a=S})}return u(t.getEditorState()),e.mergeRegister(t.registerUpdateListener(({editorState:e})=>u(e)),()=>{c()})}const S=l,E=r,N=c,v=d,y=i,A=f,R=g,b=s,w=a,P=u;function I(e,t){for(const n of t)if(e.type.startsWith(n))return!0;return!1}function T(e,t){return M("next",e,t)}function O(t,n){const r=e.$getAdjacentSiblingOrParentSiblingCaret(e.$getSiblingCaret(t,n));return r&&r[0]}function M(t,n,r){const o=e.$getRoot(),i=n||o,s=e.$isElementNode(i)?e.$getChildCaret(i,t):e.$getSiblingCaret(i,t),l=L(i),a=r?e.$getAdjacentChildCaret(e.$getChildCaretOrSelf(e.$getSiblingCaret(r,t)))||O(r,t):O(i,t);let c=l;return e.makeStepwiseIterator({hasNext:e=>null!==e,initial:s,map:e=>({depth:c,node:e.origin}),step:t=>{if(t.isSameNodeCaret(a))return null;e.$isChildCaret(t)&&c++;const n=e.$getAdjacentSiblingOrParentSiblingCaret(t);return!n||n[0].isSameNodeCaret(a)?null:(c+=n[1],n[0])}})}function L(e){let t=-1;for(let n=e;null!==n;n=n.getParent())t++;return t}function _(e,t){return M("previous",e,t)}function D(t,r,o){let i=e.$getCaretInDirection(r,"next");e.$isTextPointCaret(i)&&(0===i.offset?i=e.$getSiblingCaret(i.origin,"previous").getFlipped():i.offset===i.origin.getTextContentSize()&&(i=e.$getSiblingCaret(i.origin,"next"))),i.origin.is(t)&&(e.$isSiblingCaret(i)||n(342,t.getKey(),t.getType()),i=e.$rewindSiblingCaret(i)),(t.is(i.getNodeAtCaret())||t.is(i.getFlipped().getNodeAtCaret()))&&t.remove(!0);for(let t=i;t;t=e.$splitAtPointCaretNext(t,o))i=t;return e.$isTextPointCaret(i)&&n(283),i.insert(t.isInline()?e.$createParagraphNode().append(t):t),e.$getCaretInDirection(e.$getSiblingCaret(t.getLatest(),"next"),r.direction)}let F=!(b||!E)&&void 0;function B(t,n,r){let o=!1;for(const i of j(t))n(i)?null!==r&&r(i):(o=!0,e.$isElementNode(i)&&B(i,n,r||(e=>i.insertAfter(e))),i.remove());return o}function j(t){return k(e.$getChildCaret(t,"previous"))}function k(t){return e.makeStepwiseIterator({hasNext:e.$isSiblingCaret,initial:t.getAdjacentCaret(),map:e=>e.origin.getLatest(),step:e=>e.getAdjacentCaret()})}exports.$findMatchingParent=e.$findMatchingParent,exports.$getAdjacentSiblingOrParentSiblingCaret=e.$getAdjacentSiblingOrParentSiblingCaret,exports.$splitNode=e.$splitNode,exports.addClassNamesToElement=e.addClassNamesToElement,exports.isBlockDomNode=e.isBlockDomNode,exports.isHTMLAnchorElement=e.isHTMLAnchorElement,exports.isHTMLElement=e.isHTMLElement,exports.isInlineDomNode=e.isInlineDomNode,exports.mergeRegister=e.mergeRegister,exports.removeClassNamesFromElement=e.removeClassNamesFromElement,exports.$descendantsMatching=function(t,n){const r=[],o=Array.from(t).reverse();for(let t=o.pop();void 0!==t;t=o.pop())if(n(t))r.push(t);else if(e.$isElementNode(t))for(const e of j(t))o.push(e);return r},exports.$dfs=function(e,t){return Array.from(T(e,t))},exports.$dfsIterator=T,exports.$filter=function(e,t){const n=[];for(let r=0;r<e.length;r++){const o=t(e[r]);null!==o&&n.push(o)}return n},exports.$firstToLastIterator=function(t){return k(e.$getChildCaret(t,"next"))},exports.$getAdjacentCaret=function(e){return e?e.getAdjacentCaret():null},exports.$getDepth=L,exports.$getNearestBlockElementAncestorOrThrow=function(t){const r=e.$findMatchingParent(t,t=>e.$isElementNode(t)&&!t.isInline());return e.$isElementNode(r)||n(4,t.__key),r},exports.$getNearestNodeOfType=function(e,t){let n=e;for(;null!=n;){if(n instanceof t)return n;n=n.getParent()}return null},exports.$getNextRightPreorderNode=function(t){const n=e.$getChildCaretOrSelf(e.$getSiblingCaret(t,"previous")),r=e.$getAdjacentSiblingOrParentSiblingCaret(n,"root");return r&&r[0].origin},exports.$getNextSiblingOrParentSibling=function(t){const n=e.$getAdjacentSiblingOrParentSiblingCaret(e.$getSiblingCaret(t,"next"));return n&&[n[0].origin,n[1]]},exports.$handleIndentAndOutdent=function(t){const n=e.$getSelection();if(!e.$isRangeSelection(n))return!1;const r=new Set,o=n.getNodes();for(let n=0;n<o.length;n++){const i=o[n],s=i.getKey();if(r.has(s))continue;const l=e.$findMatchingParent(i,t=>e.$isElementNode(t)&&!t.isInline());if(null===l)continue;const a=l.getKey();l.canIndent()&&!r.has(a)&&(r.add(a),t(l))}return r.size>0},exports.$insertFirst=function(t,n){e.$getChildCaret(t,"next").insert(n)},exports.$insertNodeIntoLeaf=function(t){const n=e.$getSelection();if(!e.$isRangeSelection(n))return void(n&&n.insertNodes([t]));const r=e.$caretRangeFromSelection(n);let o=e.$getCaretRangeInDirection(e.$removeTextFromCaretRange(r),"next").anchor;if(e.$isTextPointCaret(o)){const t=e.$splitAtPointCaretNext(o);if(!t)return;o=t}const i=o.getFlipped();i.insert(t),e.$setSelectionFromCaretRange(e.$getCaretRange(i,i))},exports.$insertNodeToNearestRoot=function(t){const n=e.$getSelection()||e.$getPreviousSelection();let r;if(e.$isRangeSelection(n))r=e.$caretFromPoint(n.focus,"next");else{if(null!=n){const t=n.getNodes(),o=t[t.length-1];o&&(r=e.$getSiblingCaret(o,"next"))}r=r||e.$getChildCaret(e.$getRoot(),"previous").getFlipped().insert(e.$createParagraphNode())}const o=D(t,r),i=e.$getAdjacentChildCaret(o),s=e.$isChildCaret(i)?e.$normalizeCaret(i):o;return e.$setSelectionFromCaretRange(e.$getCollapsedCaretRange(s)),t.getLatest()},exports.$insertNodeToNearestRootAtCaret=D,exports.$isEditorIsNestedEditor=function(e){return null!==e._parentEditor},exports.$lastToFirstIterator=j,exports.$restoreEditorState=function(t,n){const r=new Map,o=t._pendingEditorState;for(const[t,o]of n._nodeMap)r.set(t,e.$cloneWithProperties(o));o&&(o._nodeMap=r),t._dirtyType=2;const i=n._selection;e.$setSelection(null===i?null:i.clone())},exports.$reverseDfs=function(e,t){return Array.from(_(e,t))},exports.$reverseDfsIterator=_,exports.$unwrapAndFilterDescendants=function(e,t){return B(e,t,null)},exports.$unwrapNode=function(t){e.$rewindSiblingCaret(e.$getSiblingCaret(t,"next")).splice(1,t.getChildren())},exports.$wrapNodeInElement=function(e,t){const n=t();return e.replace(n),n.append(e),n},exports.CAN_USE_BEFORE_INPUT=S,exports.CAN_USE_DOM=E,exports.IS_ANDROID=N,exports.IS_ANDROID_CHROME=v,exports.IS_APPLE=y,exports.IS_APPLE_WEBKIT=A,exports.IS_CHROME=R,exports.IS_FIREFOX=b,exports.IS_IOS=w,exports.IS_SAFARI=P,exports.calculateZoomLevel=function(e,t=!1){let n=1;if(function(){if(void 0===F){const e=document.createElement("div");e.style.position="absolute",e.style.opacity="0",e.style.width="100px",e.style.left="-1000px",document.body.appendChild(e);const t=e.getBoundingClientRect();e.style.setProperty("zoom","2"),F=e.getBoundingClientRect().width===t.width,document.body.removeChild(e)}return F}()||t)for(;e;)n*=Number(window.getComputedStyle(e).getPropertyValue("zoom")),e=e.parentElement;return n},exports.isMimeType=I,exports.makeStateWrapper=function(t){const n=n=>e.$getState(n,t),r=(n,r)=>e.$setState(n,t,r);return{$get:n,$set:r,accessors:[n,r],makeGetterMethod:()=>function(){return n(this)},makeSetterMethod:()=>function(e){return r(this,e)},stateConfig:t}},exports.markSelection=x,exports.mediaFileReader=function(e,t){const n=e[Symbol.iterator]();return new Promise((e,r)=>{const o=[],i=()=>{const{done:s,value:l}=n.next();if(s)return e(o);const a=new FileReader;a.addEventListener("error",r),a.addEventListener("load",()=>{const e=a.result;"string"==typeof e&&o.push({file:l,result:e}),i()}),I(l,t)?a.readAsDataURL(l):i()};i()})},exports.objectKlassEquals=function(e,t){return null!==e&&Object.getPrototypeOf(e).constructor.name===t.name},exports.positionNodeOnRange=$,exports.registerNestedElementResolver=function(e,t,n,r){const o=e=>e instanceof t;return e.registerNodeTransform(t,e=>{const t=(e=>{const t=e.getChildren();for(let e=0;e<t.length;e++){const n=t[e];if(o(n))return null}let n=e,r=e;for(;null!==n;)if(r=n,n=n.getParent(),o(n))return{child:r,parent:n};return null})(e);if(null!==t){const{child:o,parent:i}=t;if(o.is(e)){r(i,e);const t=o.getNextSiblings(),s=t.length;if(i.insertAfter(o),0!==s){const e=n(i);o.insertAfter(e);for(let n=0;n<s;n++)e.append(t[n])}i.canBeEmpty()||0!==i.getChildrenSize()||i.remove()}}})},exports.selectionAlwaysOnDisplay=function(e,t){let n=null;const r=()=>{const r=getSelection(),o=r&&r.anchorNode,i=e.getRootElement();null!==o&&null!==i&&i.contains(o)?null!==n&&(n(),n=null):null===n&&(n=x(e,t))};return e.registerRootListener(e=>{if(e){const t=e.ownerDocument;return t.addEventListener("selectionchange",r),r(),()=>{null!==n&&n(),t.removeEventListener("selectionchange",r)}}})};
/**
* 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{isHTMLElement as t,mergeRegister as e,$getSelection as n,$isRangeSelection as o,$isElementNode as r,getDOMTextNode as i,$getEditorDOMRenderConfig as l,$getChildCaret as s,$findMatchingParent as u,$getChildCaretOrSelf as c,$getSiblingCaret as a,$getAdjacentSiblingOrParentSiblingCaret as f,$caretRangeFromSelection as d,$getCaretRangeInDirection as g,$removeTextFromCaretRange as p,$isTextPointCaret as m,$splitAtPointCaretNext as h,$setSelectionFromCaretRange as v,$getCaretRange as y,$getPreviousSelection as w,$caretFromPoint as x,$getRoot as E,$createParagraphNode as C,$getAdjacentChildCaret as S,$isChildCaret as A,$normalizeCaret as N,$getCollapsedCaretRange as b,$getCaretInDirection as L,$isSiblingCaret as P,$rewindSiblingCaret as R,$cloneWithProperties as M,$setSelection as T,makeStepwiseIterator as B,$getState as K,$setState as _}from"lexical";export{$findMatchingParent,$getAdjacentSiblingOrParentSiblingCaret,$splitNode,addClassNamesToElement,isBlockDomNode,isHTMLAnchorElement,isHTMLElement,isInlineDomNode,mergeRegister,removeClassNamesFromElement}from"lexical";import{createRectsFromDOMRange as $}from"@lexical/selection";function k(t,...e){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const t of e)o.append("v",t);throw n.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const F="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,I=F&&"documentMode"in document?document.documentMode:null,O=F&&/Mac|iPod|iPhone|iPad/.test(navigator.platform),D=F&&/^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent),H=!(!F||!("InputEvent"in window)||I)&&"getTargetRanges"in new window.InputEvent("input"),j=F&&/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,z=F&&/Android/.test(navigator.userAgent),U=F&&/Version\/[\d.]+.*Safari/.test(navigator.userAgent)&&!z,V=F&&/^(?=.*Chrome).*/i.test(navigator.userAgent),W=F&&z&&V,G=F&&/AppleWebKit\/[\d.]+/.test(navigator.userAgent)&&O&&!V;function q(t){return`${t}px`}const J={attributes:!0,characterData:!0,childList:!0,subtree:!0};function Q(e,n,o){let r=null,i=null,l=null,s=[];const u=document.createElement("div");function c(){null===r&&k(182),null===i&&k(183);const{left:t,top:l}=i.getBoundingClientRect(),c=$(e,n);var a,f;u.isConnected||(f=u,(a=i).insertBefore(f,a.firstChild));let d=!1;for(let e=0;e<c.length;e++){const n=c[e],o=s[e]||document.createElement("div"),r=o.style;"absolute"!==r.position&&(r.position="absolute",d=!0);const i=q(n.left-t);r.left!==i&&(r.left=i,d=!0);const a=q(n.top-l);r.top!==a&&(o.style.top=a,d=!0);const f=q(n.width);r.width!==f&&(o.style.width=f,d=!0);const g=q(n.height);r.height!==g&&(o.style.height=g,d=!0),o.parentNode!==u&&(u.append(o),d=!0),s[e]=o}for(;s.length>c.length;)s.pop();d&&o(s)}function a(){i=null,r=null,null!==l&&l.disconnect(),l=null,u.remove();for(const t of s)t.remove();s=[]}u.style.position="relative";const f=e.registerRootListener(function n(){const o=e.getRootElement();if(null===o)return a();const s=o.parentElement;if(!t(s))return a();a(),r=o,i=s,l=new MutationObserver(t=>{const o=e.getRootElement(),l=o&&o.parentElement;if(o!==r||l!==i)return n();for(const e of t)if(!u.contains(e.target))return c()}),l.observe(s,J),c()});return()=>{f(),a()}}function X(t,e,n,o){if("text"!==e.type&&r(n)){const r=l(t).$getDOMSlot(n,o,t);return[r.element,r.getFirstChildOffset()+e.offset]}return[i(o)||o,e.offset]}function Y(t){for(const e of t){const t=e.style;"Highlight"!==t.background&&(t.background="Highlight"),"HighlightText"!==t.color&&(t.color="HighlightText"),t.marginTop!==q(-1.5)&&(t.marginTop=q(-1.5)),t.paddingTop!==q(4)&&(t.paddingTop=q(4)),t.paddingBottom!==q(0)&&(t.paddingBottom=q(0))}}function Z(t,r=Y){let i=null,l=null,s=null,u=null,c=null,a=null,f=()=>{};function d(e){e.read(()=>{const e=n();if(!o(e))return i=null,s=null,u=null,a=null,f(),void(f=()=>{});const[d,g]=function(t){const e=t.getStartEndPoints();return t.isBackward()?[e[1],e[0]]:e}(e),p=d.getNode(),m=p.getKey(),h=d.offset,v=g.getNode(),y=v.getKey(),w=g.offset,x=t.getElementByKey(m),E=t.getElementByKey(y),C=null===i||x!==l||h!==s||m!==i.getKey(),S=null===u||E!==c||w!==a||y!==u.getKey();if((C||S)&&null!==x&&null!==E){const e=function(t,e,n,o,r,i,l){const s=(t._window?t._window.document:document).createRange();return s.setStart(...X(t,e,n,o)),s.setEnd(...X(t,r,i,l)),s}(t,d,p,x,g,v,E);f(),f=Q(t,e,r)}i=p,l=x,s=h,u=v,c=E,a=w})}return d(t.getEditorState()),e(t.registerUpdateListener(({editorState:t})=>d(t)),()=>{f()})}function tt(t,e){let n=null;const o=()=>{const o=getSelection(),r=o&&o.anchorNode,i=t.getRootElement();null!==r&&null!==i&&i.contains(r)?null!==n&&(n(),n=null):null===n&&(n=Z(t,e))};return t.registerRootListener(t=>{if(t){const e=t.ownerDocument;return e.addEventListener("selectionchange",o),o(),()=>{null!==n&&n(),e.removeEventListener("selectionchange",o)}}})}const et=H,nt=F,ot=z,rt=W,it=O,lt=G,st=V,ut=D,ct=j,at=U;function ft(t,e){for(const n of e)if(t.type.startsWith(n))return!0;return!1}function dt(t,e){const n=t[Symbol.iterator]();return new Promise((t,o)=>{const r=[],i=()=>{const{done:l,value:s}=n.next();if(l)return t(r);const u=new FileReader;u.addEventListener("error",o),u.addEventListener("load",()=>{const t=u.result;"string"==typeof t&&r.push({file:s,result:t}),i()}),ft(s,e)?u.readAsDataURL(s):i()};i()})}function gt(t,e){return Array.from(ht(t,e))}function pt(t){return t?t.getAdjacentCaret():null}function mt(t,e){return Array.from(Ct(t,e))}function ht(t,e){return yt("next",t,e)}function vt(t,e){const n=f(a(t,e));return n&&n[0]}function yt(t,e,n){const o=E(),i=e||o,l=r(i)?s(i,t):a(i,t),u=xt(i),d=n?S(c(a(n,t)))||vt(n,t):vt(i,t);let g=u;return B({hasNext:t=>null!==t,initial:l,map:t=>({depth:g,node:t.origin}),step:t=>{if(t.isSameNodeCaret(d))return null;A(t)&&g++;const e=f(t);return!e||e[0].isSameNodeCaret(d)?null:(g+=e[1],e[0])}})}function wt(t){const e=f(a(t,"next"));return e&&[e[0].origin,e[1]]}function xt(t){let e=-1;for(let n=t;null!==n;n=n.getParent())e++;return e}function Et(t){const e=c(a(t,"previous")),n=f(e,"root");return n&&n[0].origin}function Ct(t,e){return yt("previous",t,e)}function St(t,e){let n=t;for(;null!=n;){if(n instanceof e)return n;n=n.getParent()}return null}function At(t){const e=u(t,t=>r(t)&&!t.isInline());return r(e)||k(4,t.__key),e}function Nt(t,e,n,o){const r=t=>t instanceof e;return t.registerNodeTransform(e,t=>{const e=(t=>{const e=t.getChildren();for(let t=0;t<e.length;t++){const n=e[t];if(r(n))return null}let n=t,o=t;for(;null!==n;)if(o=n,n=n.getParent(),r(n))return{child:o,parent:n};return null})(t);if(null!==e){const{child:r,parent:i}=e;if(r.is(t)){o(i,t);const e=r.getNextSiblings(),l=e.length;if(i.insertAfter(r),0!==l){const t=n(i);r.insertAfter(t);for(let n=0;n<l;n++)t.append(e[n])}i.canBeEmpty()||0!==i.getChildrenSize()||i.remove()}}})}function bt(t,e){const n=new Map,o=t._pendingEditorState;for(const[t,o]of e._nodeMap)n.set(t,M(o));o&&(o._nodeMap=n),t._dirtyType=2;const r=e._selection;T(null===r?null:r.clone())}function Lt(t){const e=n()||w();let r;if(o(e))r=x(e.focus,"next");else{if(null!=e){const t=e.getNodes(),n=t[t.length-1];n&&(r=a(n,"next"))}r=r||s(E(),"previous").getFlipped().insert(C())}const i=Pt(t,r),l=S(i),u=A(l)?N(l):i;return v(b(u)),t.getLatest()}function Pt(t,e,n){let o=L(e,"next");m(o)&&(0===o.offset?o=a(o.origin,"previous").getFlipped():o.offset===o.origin.getTextContentSize()&&(o=a(o.origin,"next"))),o.origin.is(t)&&(P(o)||k(342,t.getKey(),t.getType()),o=R(o)),(t.is(o.getNodeAtCaret())||t.is(o.getFlipped().getNodeAtCaret()))&&t.remove(!0);for(let t=o;t;t=h(t,n))o=t;return m(o)&&k(283),o.insert(t.isInline()?C().append(t):t),L(a(t.getLatest(),"next"),e.direction)}function Rt(t){const e=n();if(!o(e))return void(e&&e.insertNodes([t]));const r=d(e);let i=g(p(r),"next").anchor;if(m(i)){const t=h(i);if(!t)return;i=t}const l=i.getFlipped();l.insert(t),v(y(l,l))}function Mt(t,e){const n=e();return t.replace(n),n.append(t),n}function Tt(t,e){return null!==t&&Object.getPrototypeOf(t).constructor.name===e.name}function Bt(t,e){const n=[];for(let o=0;o<t.length;o++){const r=e(t[o]);null!==r&&n.push(r)}return n}function Kt(t){const e=n();if(!o(e))return!1;const i=new Set,l=e.getNodes();for(let e=0;e<l.length;e++){const n=l[e],o=n.getKey();if(i.has(o))continue;const s=u(n,t=>r(t)&&!t.isInline());if(null===s)continue;const c=s.getKey();s.canIndent()&&!i.has(c)&&(i.add(c),t(s))}return i.size>0}function _t(t,e){s(t,"next").insert(e)}let $t=!(ut||!nt)&&void 0;function kt(t,e=!1){let n=1;if(function(){if(void 0===$t){const t=document.createElement("div");t.style.position="absolute",t.style.opacity="0",t.style.width="100px",t.style.left="-1000px",document.body.appendChild(t);const e=t.getBoundingClientRect();t.style.setProperty("zoom","2"),$t=t.getBoundingClientRect().width===e.width,document.body.removeChild(t)}return $t}()||e)for(;t;)n*=Number(window.getComputedStyle(t).getPropertyValue("zoom")),t=t.parentElement;return n}function Ft(t){return null!==t._parentEditor}function It(t,e){return Ot(t,e,null)}function Ot(t,e,n){let o=!1;for(const i of jt(t))e(i)?null!==n&&n(i):(o=!0,r(i)&&Ot(i,e,n||(t=>i.insertAfter(t))),i.remove());return o}function Dt(t,e){const n=[],o=Array.from(t).reverse();for(let t=o.pop();void 0!==t;t=o.pop())if(e(t))n.push(t);else if(r(t))for(const e of jt(t))o.push(e);return n}function Ht(t){return zt(s(t,"next"))}function jt(t){return zt(s(t,"previous"))}function zt(t){return B({hasNext:P,initial:t.getAdjacentCaret(),map:t=>t.origin.getLatest(),step:t=>t.getAdjacentCaret()})}function Ut(t){R(a(t,"next")).splice(1,t.getChildren())}function Vt(t){const e=e=>K(e,t),n=(e,n)=>_(e,t,n);return{$get:e,$set:n,accessors:[e,n],makeGetterMethod:()=>function(){return e(this)},makeSetterMethod:()=>function(t){return n(this,t)},stateConfig:t}}export{Dt as $descendantsMatching,gt as $dfs,ht as $dfsIterator,Bt as $filter,Ht as $firstToLastIterator,pt as $getAdjacentCaret,xt as $getDepth,At as $getNearestBlockElementAncestorOrThrow,St as $getNearestNodeOfType,Et as $getNextRightPreorderNode,wt as $getNextSiblingOrParentSibling,Kt as $handleIndentAndOutdent,_t as $insertFirst,Rt as $insertNodeIntoLeaf,Lt as $insertNodeToNearestRoot,Pt as $insertNodeToNearestRootAtCaret,Ft as $isEditorIsNestedEditor,jt as $lastToFirstIterator,bt as $restoreEditorState,mt as $reverseDfs,Ct as $reverseDfsIterator,It as $unwrapAndFilterDescendants,Ut as $unwrapNode,Mt as $wrapNodeInElement,et as CAN_USE_BEFORE_INPUT,nt as CAN_USE_DOM,ot as IS_ANDROID,rt as IS_ANDROID_CHROME,it as IS_APPLE,lt as IS_APPLE_WEBKIT,st as IS_CHROME,ut as IS_FIREFOX,ct as IS_IOS,at as IS_SAFARI,kt as calculateZoomLevel,ft as isMimeType,Vt as makeStateWrapper,Z as markSelection,dt as mediaFileReader,Tt as objectKlassEquals,Q as positionNodeOnRange,Nt as registerNestedElementResolver,tt as selectionAlwaysOnDisplay};
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { type LexicalEditor } from 'lexical';
/**
* Place one or multiple newly created Nodes at the current selection. Multiple
* nodes will only be created when the selection spans multiple lines (aka
* client rects).
*
* This function can come useful when you want to show the selection but the
* editor has been focused away.
*/
export default function markSelection(editor: LexicalEditor, onReposition?: (node: readonly HTMLElement[]) => void): () => void;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { type LexicalEditor } from 'lexical';
/**
* Place one or multiple newly created Nodes at the passed Range's position.
* Multiple nodes will only be created when the Range spans multiple lines (aka
* client rects).
*
* This function can come particularly useful to highlight particular parts of
* the text without interfering with the EditorState, that will often replicate
* the state across collab and clipboard.
*
* This function accounts for DOM updates which can modify the passed Range.
* Hence, the function return to remove the listener.
*/
export default function mlcPositionNodeOnRange(editor: LexicalEditor, range: Range, onReposition: (node: Array<HTMLElement>) => void): () => void;
/**
* 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.
*
*/
export default function px(value: number): string;
/**
* 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 { LexicalEditor } from 'lexical';
export default function selectionAlwaysOnDisplay(editor: LexicalEditor, onReposition?: (node: readonly HTMLElement[]) => void): () => void;