lexical
Version:
Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.
1,262 lines (1,196 loc) • 43.4 kB
Flow
/**
* 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.
*
* @flow strict
*/
/**
* LexicalCommands
*/
export type LexicalCommand<P> = $ReadOnly<{type?: string}>;
declare export var SELECTION_CHANGE_COMMAND: LexicalCommand<void>;
declare export var CLICK_COMMAND: LexicalCommand<MouseEvent>;
declare export var DELETE_CHARACTER_COMMAND: LexicalCommand<boolean>;
declare export var INSERT_LINE_BREAK_COMMAND: LexicalCommand<boolean>;
declare export var INSERT_PARAGRAPH_COMMAND: LexicalCommand<void>;
declare export var CONTROLLED_TEXT_INSERTION_COMMAND: LexicalCommand<string>;
declare export var PASTE_COMMAND: LexicalCommand<ClipboardEvent>;
declare export var REMOVE_TEXT_COMMAND: LexicalCommand<InputEvent | null>;
declare export var DELETE_WORD_COMMAND: LexicalCommand<boolean>;
declare export var DELETE_LINE_COMMAND: LexicalCommand<boolean>;
declare export var FORMAT_TEXT_COMMAND: LexicalCommand<TextFormatType>;
declare export var UNDO_COMMAND: LexicalCommand<void>;
declare export var REDO_COMMAND: LexicalCommand<void>;
declare export var KEY_DOWN_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_RIGHT_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_LEFT_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_UP_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ARROW_DOWN_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ENTER_COMMAND: LexicalCommand<KeyboardEvent | null>;
declare export var KEY_SPACE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_BACKSPACE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_ESCAPE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_DELETE_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var KEY_TAB_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var INSERT_TAB_COMMAND: LexicalCommand<void>;
declare export var KEY_MODIFIER_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var INDENT_CONTENT_COMMAND: LexicalCommand<void>;
declare export var OUTDENT_CONTENT_COMMAND: LexicalCommand<void>;
declare export var DROP_COMMAND: LexicalCommand<DragEvent>;
declare export var FORMAT_ELEMENT_COMMAND: LexicalCommand<ElementFormatType>;
declare export var DRAGSTART_COMMAND: LexicalCommand<DragEvent>;
declare export var DRAGOVER_COMMAND: LexicalCommand<DragEvent>;
declare export var DRAGEND_COMMAND: LexicalCommand<DragEvent>;
declare export var COPY_COMMAND: LexicalCommand<
ClipboardEvent | KeyboardEvent | null,
>;
declare export var CUT_COMMAND: LexicalCommand<
ClipboardEvent | KeyboardEvent | null,
>;
declare export var CLEAR_EDITOR_COMMAND: LexicalCommand<void>;
declare export var CLEAR_HISTORY_COMMAND: LexicalCommand<void>;
declare export var CAN_REDO_COMMAND: LexicalCommand<boolean>;
declare export var CAN_UNDO_COMMAND: LexicalCommand<boolean>;
declare export var FOCUS_COMMAND: LexicalCommand<FocusEvent>;
declare export var BLUR_COMMAND: LexicalCommand<FocusEvent>;
declare export var SELECT_ALL_COMMAND: LexicalCommand<KeyboardEvent>;
declare export var MOVE_TO_END: LexicalCommand<KeyboardEvent>;
declare export var MOVE_TO_START: LexicalCommand<KeyboardEvent>;
declare export var SELECTION_INSERT_CLIPBOARD_NODES_COMMAND: LexicalCommand<{
nodes: Array<LexicalNode>;
selection: BaseSelection;
}>;
declare export function createCommand<T>(type?: string): LexicalCommand<T>;
/**
* LexicalConstants
*/
declare export var IS_ALL_FORMATTING: number;
declare export var IS_BOLD: number;
declare export var IS_CODE: number;
declare export var IS_HIGHLIGHT: number;
declare export var IS_ITALIC: number;
declare export var IS_STRIKETHROUGH: number;
declare export var IS_SUBSCRIPT: number;
declare export var IS_SUPERSCRIPT: number;
declare export var IS_UNDERLINE: number;
declare export var IS_UPPERCASE: number;
declare export var IS_LOWERCASE: number;
declare export var IS_CAPITALIZE: number;
declare export var TEXT_TYPE_TO_FORMAT: Record<TextFormatType | string, number>;
/**
* LexicalEditor
*/
type IntentionallyMarkedAsDirtyElement = boolean;
type MutationListeners = Map<MutationListener, Class<LexicalNode>>;
export type NodeMutation = 'created' | 'updated' | 'destroyed';
type UpdateListener = ({
tags: Set<string>,
prevEditorState: EditorState,
editorState: EditorState,
dirtyLeaves: Set<NodeKey>,
dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>,
normalizedNodes: Set<NodeKey>,
}) => void;
type DecoratorListener = (decorator: {
// $FlowFixMe: defined by user
[NodeKey]: any,
}) => void;
type RootListener = (
rootElement: null | HTMLElement,
prevRootElement: null | HTMLElement,
) => void;
type TextContentListener = (text: string) => void;
type ErrorHandler = (error: Error) => void;
export type MutationListener = (
nodes: Map<NodeKey, NodeMutation>,
{
updateTags: Set<string>,
dirtyLeaves: Set<string>,
prevEditorState: EditorState,
},
) => void;
export type MutationListenerOptions = {
skipInitialization?: boolean;
};
export type EditableListener = (editable: boolean) => void;
type Listeners = {
decorator: Set<DecoratorListener>,
mutation: MutationListeners,
textcontent: Set<TextContentListener>,
root: Set<RootListener>,
update: Set<UpdateListener>,
};
export type CommandListener<P> = (payload: P, editor: LexicalEditor) => boolean;
// $FlowFixMe[unclear-type]
type Commands = Map<LexicalCommand<any>, Array<Set<CommandListener<any>>>>;
type RegisteredNodes = Map<string, RegisteredNode>;
type RegisteredNode = {
klass: Class<LexicalNode>,
transforms: Set<Transform<LexicalNode>>,
};
export type Transform<T> = (node: T) => void;
type DOMConversionCache = Map<
string,
Array<(node: Node) => DOMConversion | null>,
>;
export type CreateEditorArgs = {
disableEvents?: boolean;
editorState?: EditorState;
namespace?: string;
nodes?: $ReadOnlyArray<Class<LexicalNode> | LexicalNodeReplacement>;
onError?: ErrorHandler;
parentEditor?: LexicalEditor;
editable?: boolean;
theme?: EditorThemeClasses;
html?: HTMLConfig;
};
declare export class LexicalEditor {
_parentEditor: null | LexicalEditor;
_rootElement: null | HTMLElement;
_editorState: EditorState;
_htmlConversions: DOMConversionCache;
_pendingEditorState: null | EditorState;
_compositionKey: null | NodeKey;
_deferred: Array<() => void>;
_updates: Array<[() => void, void | EditorUpdateOptions]>;
_updating: boolean;
_keyToDOMMap: Map<NodeKey, HTMLElement>;
_listeners: Listeners;
_commands: Commands;
_nodes: RegisteredNodes;
_onError: ErrorHandler;
_decorators: {
[NodeKey]: mixed,
};
_pendingDecorators: null | {
[NodeKey]: mixed,
};
_createEditorArgs?: CreateEditorArgs;
_config: EditorConfig;
_dirtyType: 0 | 1 | 2;
_cloneNotNeeded: Set<NodeKey>;
_dirtyLeaves: Set<NodeKey>;
_dirtyElements: Map<NodeKey, IntentionallyMarkedAsDirtyElement>;
_normalizedNodes: Set<NodeKey>;
_updateTags: Set<string>;
_observer: null | MutationObserver;
_key: string;
_editable: boolean;
_headless: boolean;
isComposing(): boolean;
registerUpdateListener(listener: UpdateListener): () => void;
registerRootListener(listener: RootListener): () => void;
registerDecoratorListener(listener: DecoratorListener): () => void;
registerTextContentListener(listener: TextContentListener): () => void;
registerCommand<P>(
command: LexicalCommand<P>,
listener: CommandListener<P>,
priority: CommandListenerPriority,
): () => void;
registerEditableListener(listener: EditableListener): () => void;
registerMutationListener(
klass: Class<LexicalNode>,
listener: MutationListener,
options?: MutationListenerOptions,
): () => void;
registerNodeTransform<T: LexicalNode>(
klass: Class<T>,
listener: Transform<T>,
): () => void;
dispatchCommand<P>(command: LexicalCommand<P>, payload: P): boolean;
hasNode(node: Class<LexicalNode>): boolean;
hasNodes(nodes: Array<Class<LexicalNode>>): boolean;
getKey(): string;
getDecorators<X>(): {
[NodeKey]: X,
};
getRootElement(): null | HTMLElement;
setRootElement(rootElement: null | HTMLElement): void;
getElementByKey(key: NodeKey): null | HTMLElement;
getEditorState(): EditorState;
setEditorState(editorState: EditorState, options?: EditorSetOptions): void;
parseEditorState(
maybeStringifiedEditorState: string | SerializedEditorState,
updateFn?: () => void,
): EditorState;
read<V>(callbackFn: () => V, options?: EditorReadOptions): V;
update(updateFn: () => void, options?: EditorUpdateOptions): boolean;
focus(callbackFn?: () => void, options?: EditorFocusOptions): void;
blur(): void;
isEditable(): boolean;
setEditable(editable: boolean): void;
toJSON(): SerializedEditor;
}
type EditorReadOptions = {
pending?: boolean,
};
type EditorUpdateOptions = {
onUpdate?: () => void,
tag?: string | Array<string>,
skipTransforms?: true,
discrete?: true,
};
type EditorFocusOptions = {
defaultSelection?: 'rootStart' | 'rootEnd',
};
type EditorSetOptions = {
tag?: string,
};
type EditorThemeClassName = string;
type TextNodeThemeClasses = {
base?: EditorThemeClassName,
bold?: EditorThemeClassName,
underline?: EditorThemeClassName,
strikethrough?: EditorThemeClassName,
underlineStrikethrough?: EditorThemeClassName,
italic?: EditorThemeClassName,
code?: EditorThemeClassName,
subscript?: EditorThemeClassName,
superscript?: EditorThemeClassName,
lowercase?: EditorThemeClassName,
uppercase?: EditorThemeClassName,
capitalize?: EditorThemeClassName,
};
export type EditorThemeClasses = {
characterLimit?: EditorThemeClassName,
ltr?: EditorThemeClassName,
rtl?: EditorThemeClassName,
text?: TextNodeThemeClasses,
paragraph?: EditorThemeClassName,
image?: EditorThemeClassName,
list?: {
ul?: EditorThemeClassName,
ulDepth?: Array<EditorThemeClassName>,
ol?: EditorThemeClassName,
olDepth?: Array<EditorThemeClassName>,
checklist?: EditorThemeClassName,
listitem?: EditorThemeClassName,
listitemChecked?: EditorThemeClassName,
listitemUnchecked?: EditorThemeClassName,
nested?: {
list?: EditorThemeClassName,
listitem?: EditorThemeClassName,
},
},
table?: EditorThemeClassName,
tableRow?: EditorThemeClassName,
tableCell?: EditorThemeClassName,
tableCellHeader?: EditorThemeClassName,
mark?: EditorThemeClassName,
markOverlap?: EditorThemeClassName,
link?: EditorThemeClassName,
quote?: EditorThemeClassName,
code?: EditorThemeClassName,
codeHighlight?: {[string]: EditorThemeClassName},
hashtag?: EditorThemeClassName,
heading?: {
h1?: EditorThemeClassName,
h2?: EditorThemeClassName,
h3?: EditorThemeClassName,
h4?: EditorThemeClassName,
h5?: EditorThemeClassName,
h6?: EditorThemeClassName,
},
embedBlock?: {
base?: EditorThemeClassName,
focus?: EditorThemeClassName,
},
// Handle other generic values
[string]: EditorThemeClassName | {[string]: EditorThemeClassName},
};
export type EditorConfig = {
theme: EditorThemeClasses,
namespace: string,
disableEvents?: boolean,
};
export type CommandListenerPriority = 0 | 1 | 2 | 3 | 4;
export const COMMAND_PRIORITY_EDITOR = 0;
export const COMMAND_PRIORITY_LOW = 1;
export const COMMAND_PRIORITY_NORMAL = 2;
export const COMMAND_PRIORITY_HIGH = 3;
export const COMMAND_PRIORITY_CRITICAL = 4;
export type LexicalNodeReplacement = {
replace: Class<LexicalNode>,
with: (node: LexicalNode) => LexicalNode,
withKlass?: Class<LexicalNode>,
};
export type HTMLConfig = {
export?: Map<
Class<LexicalNode>,
(editor: LexicalEditor, target: LexicalNode) => DOMExportOutput,
>,
import?: DOMConversionMap,
};
declare export function createEditor(editorConfig?: {
editorState?: EditorState,
namespace: string,
theme?: EditorThemeClasses,
parentEditor?: LexicalEditor,
nodes?: $ReadOnlyArray<Class<LexicalNode> | LexicalNodeReplacement>,
onError: (error: Error) => void,
disableEvents?: boolean,
editable?: boolean,
html?: HTMLConfig,
}): LexicalEditor;
/**
* LexicalEditorState
*/
export interface EditorState {
_nodeMap: NodeMap;
_selection: null | BaseSelection;
_flushSync: boolean;
_readOnly: boolean;
constructor(nodeMap: NodeMap, selection?: BaseSelection | null): void;
isEmpty(): boolean;
read<V>(callbackFn: () => V, options?: EditorStateReadOptions): V;
toJSON(): SerializedEditorState;
clone(selection?: BaseSelection | null): EditorState;
}
type EditorStateReadOptions = {
editor?: LexicalEditor | null;
}
/**
* LexicalNode
*/
export type DOMConversion = {
conversion: DOMConversionFn,
priority: 0 | 1 | 2 | 3 | 4,
};
export type DOMConversionFn = (element: Node) => DOMConversionOutput | null;
export type DOMChildConversion = (
lexicalNode: LexicalNode,
parentLexicalNode: ?LexicalNode | null,
) => LexicalNode | null | void;
export type DOMConversionMap = {
[NodeName]: <T: HTMLElement>(node: T) => DOMConversion | null,
};
type NodeName = string;
export type DOMConversionOutput = {
after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>,
forChild?: DOMChildConversion,
node: null | LexicalNode | Array<LexicalNode>,
};
export type DOMExportOutput = {
after?: (generatedElement: ?HTMLElement) => ?HTMLElement,
element?: HTMLElement | null,
};
export type NodeKey = string;
declare export class LexicalNode {
__type: string;
__key: NodeKey;
__parent: null | NodeKey;
__next: null | NodeKey;
__prev: null | NodeKey;
static getType(): string;
static clone(data: $FlowFixMe): LexicalNode;
static importDOM(): DOMConversionMap | null;
constructor(key?: NodeKey): void;
exportDOM(editor: LexicalEditor): DOMExportOutput;
exportJSON(): SerializedLexicalNode;
updateFromJSON(serializedNode: $FlowFixMe): this;
getType(): string;
isAttached(): boolean;
isSelected(): boolean;
getKey(): NodeKey;
getIndexWithinParent(): number;
getParent<T: ElementNode>(): T | null;
getParentOrThrow<T: ElementNode>(): T;
getTopLevelElement(): DecoratorNode<mixed> | ElementNode | null;
getTopLevelElementOrThrow(): DecoratorNode<mixed> | ElementNode;
getParents<T: ElementNode>(): Array<T>;
getParentKeys(): Array<NodeKey>;
getPreviousSibling<T: LexicalNode>(): T | null;
getPreviousSiblings<T: LexicalNode>(): Array<T>;
getNextSibling<T: LexicalNode>(): T | null;
getNextSiblings<T: LexicalNode>(): Array<T>;
getCommonAncestor<T: ElementNode>(node: LexicalNode): T | null;
is(object: ?LexicalNode): boolean;
isBefore(targetNode: LexicalNode): boolean;
isParentOf(targetNode: LexicalNode): boolean;
getNodesBetween(targetNode: LexicalNode): Array<LexicalNode>;
isDirty(): boolean;
// $FlowFixMe[incompatible-type]
getLatest<T: LexicalNode>(this: T): T;
// $FlowFixMe[incompatible-type]
getWritable<T: LexicalNode>(this: T): T;
getTextContent(includeDirectionless?: boolean): string;
getTextContentSize(includeDirectionless?: boolean): number;
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
updateDOM(
// $FlowFixMe[unclear-type]
prevNode: any,
dom: HTMLElement,
config: EditorConfig,
): boolean;
remove(preserveEmptyParent?: boolean): void;
replace<N: LexicalNode>(replaceWith: N): N;
insertAfter(
nodeToInsert: LexicalNode,
restoreSelection?: boolean,
): LexicalNode;
insertBefore(
nodeToInsert: LexicalNode,
restoreSelection?: boolean,
): LexicalNode;
selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection;
selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection;
markDirty(): void;
reconcileObservedMutation(dom: HTMLElement, editor: LexicalEditor): void;
}
export type NodeMap = Map<NodeKey, LexicalNode>;
/**
* LexicalSelection
*/
declare export function $isBlockElementNode(
node: ?LexicalNode,
): node is ElementNode;
export interface BaseSelection {
dirty: boolean;
clone(): BaseSelection;
extract(): Array<LexicalNode>;
getNodes(): Array<LexicalNode>;
getStartEndPoints(): null | [PointType, PointType];
getTextContent(): string;
insertRawText(text: string): void;
is(selection: null | BaseSelection): boolean;
isBackward(): boolean;
isCollapsed(): boolean;
insertText(text: string): void;
insertNodes(nodes: Array<LexicalNode>): void;
getCachedNodes(): null | Array<LexicalNode>;
setCachedNodes(nodes: null | Array<LexicalNode>): void;
}
declare export class NodeSelection implements BaseSelection {
_nodes: Set<NodeKey>;
dirty: boolean;
constructor(objects: Set<NodeKey>): void;
is(selection: null | BaseSelection): boolean;
isBackward(): boolean;
isCollapsed(): boolean;
add(key: NodeKey): void;
delete(key: NodeKey): void;
clear(): void;
has(key: NodeKey): boolean;
clone(): NodeSelection;
extract(): Array<LexicalNode>;
insertRawText(): void;
insertText(): void;
getNodes(): Array<LexicalNode>;
getStartEndPoints(): null;
getTextContent(): string;
insertNodes(nodes: Array<LexicalNode>): void;
getCachedNodes(): null | Array<LexicalNode>;
setCachedNodes(nodes: null | Array<LexicalNode>): void;
}
declare export function $isNodeSelection(
x: ?mixed,
): x is NodeSelection;
declare export class RangeSelection implements BaseSelection {
anchor: PointType;
focus: PointType;
dirty: boolean;
format: number;
style: string;
constructor(anchor: PointType, focus: PointType, format: number): void;
is(selection: null | BaseSelection): boolean;
isBackward(): boolean;
isCollapsed(): boolean;
getNodes(): Array<LexicalNode>;
setTextNodeRange(
anchorNode: TextNode,
anchorOffset: number,
focusNode: TextNode,
focusOffset: number,
): void;
getTextContent(): string;
// $FlowFixMe[cannot-resolve-name] DOM API
applyDOMRange(range: StaticRange): void;
clone(): RangeSelection;
toggleFormat(format: TextFormatType): void;
setStyle(style: string): void;
hasFormat(type: TextFormatType): boolean;
insertText(text: string): void;
insertRawText(text: string): void;
removeText(): void;
formatText(formatType: TextFormatType): void;
insertNodes(nodes: Array<LexicalNode>): void;
insertParagraph(): void;
insertLineBreak(selectStart?: boolean): void;
extract(): Array<LexicalNode>;
modify(
alter: 'move' | 'extend',
isBackward: boolean,
granularity: 'character' | 'word' | 'lineboundary',
): void;
deleteCharacter(isBackward: boolean): void;
deleteLine(isBackward: boolean): void;
deleteWord(isBackward: boolean): void;
insertNodes(nodes: Array<LexicalNode>): void;
getCachedNodes(): null | Array<LexicalNode>;
setCachedNodes(nodes: null | Array<LexicalNode>): void;
forwardDeletion(anchor: PointType, anchorNode: ElementNode | TextNode, isBackward: boolean): boolean;
getStartEndPoints(): null | [PointType, PointType];
}
export type TextPoint = TextPointType;
type TextPointType = {
key: NodeKey,
offset: number,
type: 'text',
is: (PointType) => boolean,
isBefore: (PointType) => boolean,
getNode: () => TextNode,
set: (key: NodeKey, offset: number, type: 'text' | 'element') => void,
getCharacterOffset: () => number,
};
export type ElementPoint = ElementPointType;
type ElementPointType = {
key: NodeKey,
offset: number,
type: 'element',
is: (PointType) => boolean,
isBefore: (PointType) => boolean,
getNode: () => ElementNode,
set: (key: NodeKey, offset: number, type: 'text' | 'element') => void,
};
export type Point = PointType;
export type PointType = TextPointType | ElementPointType;
declare class _Point {
key: NodeKey;
offset: number;
type: 'text' | 'element';
constructor(key: NodeKey, offset: number, type: 'text' | 'element'): void;
is(point: PointType): boolean;
isBefore(b: PointType): boolean;
getNode(): LexicalNode;
set(key: NodeKey, offset: number, type: 'text' | 'element', onlyIfChanged?: boolean): void;
}
declare export function $createRangeSelection(): RangeSelection;
declare export function $createNodeSelection(): NodeSelection;
declare export function $isRangeSelection(
x: ?mixed,
): x is RangeSelection;
declare export function $getSelection(): null | BaseSelection;
declare export function $getPreviousSelection(): null | BaseSelection;
declare export function $insertNodes(nodes: Array<LexicalNode>): void;
declare export function $getCharacterOffsets(
selection: BaseSelection,
): [number, number];
/**
* LexicalTextNode
*/
export type TextFormatType =
| 'bold'
| 'underline'
| 'strikethrough'
| 'italic'
| 'highlight'
| 'code'
| 'subscript'
| 'superscript'
| 'lowercase'
| 'uppercase'
| 'capitalize';
type TextModeType = 'normal' | 'token' | 'segmented';
declare export class TextNode extends LexicalNode {
__text: string;
__format: number;
__style: string;
__mode: 0 | 1 | 2 | 3;
__detail: number;
static getType(): string;
static clone(node: $FlowFixMe): TextNode;
constructor(text: string, key?: NodeKey): void;
getTopLevelElement(): ElementNode | null;
getTopLevelElementOrThrow(): ElementNode;
getFormat(): number;
getStyle(): string;
isComposing(): boolean;
isInline(): true;
isToken(): boolean;
isSegmented(): boolean;
isDirectionless(): boolean;
isUnmergeable(): boolean;
hasFormat(type: TextFormatType): boolean;
isSimpleText(): boolean;
getTextContent(): string;
getFormatFlags(type: TextFormatType, alignWithFormat: null | number): number;
createDOM(config: EditorConfig): HTMLElement;
selectionTransform(
prevSelection: null | BaseSelection,
nextSelection: RangeSelection,
): void;
setFormat(format: number): this;
setStyle(style: string): this;
toggleFormat(type: TextFormatType): TextNode;
toggleDirectionless(): this;
toggleUnmergeable(): this;
setMode(type: TextModeType): this;
setDetail(detail: number): this;
getDetail(): number;
getMode(): TextModeType;
setTextContent(text: string): TextNode;
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
spliceText(
offset: number,
delCount: number,
newText: string,
moveSelection?: boolean,
): TextNode;
canInsertTextBefore(): boolean;
canInsertTextAfter(): boolean;
splitText(...splitOffsets: Array<number>): Array<TextNode>;
mergeWithSibling(target: TextNode): TextNode;
isTextEntity(): boolean;
static importJSON(serializedTextNode: SerializedTextNode): TextNode;
exportJSON(): SerializedTextNode;
}
declare export function $createTextNode(text?: string): TextNode;
declare export function $isTextNode(
node: ?LexicalNode,
): node is TextNode;
/**
* LexicalTabNode
*/
export type SerializedTabNode = SerializedTextNode;
declare export function $createTabNode(): TabNode;
declare export function $isTabNode(
node: LexicalNode | null | void,
): node is TabNode;
declare export class TabNode extends TextNode {
static getType(): string;
static clone(node: TabNode): TabNode;
constructor(key?: NodeKey): void;
static importDOM(): DOMConversionMap | null;
static importJSON(serializedTabNode: SerializedTabNode): TabNode;
exportJSON(): SerializedTabNode;
}
/**
* LexicalLineBreakNode
*/
declare export class LineBreakNode extends LexicalNode {
static getType(): string;
static clone(node: LineBreakNode): LineBreakNode;
constructor(key?: NodeKey): void;
getTextContent(): '\n';
createDOM(): HTMLElement;
updateDOM(): false;
isInline(): true;
static importJSON(
serializedLineBreakNode: SerializedLineBreakNode,
): LineBreakNode;
exportJSON(): SerializedLexicalNode;
}
declare export function $createLineBreakNode(): LineBreakNode;
declare export function $isLineBreakNode(
node: ?LexicalNode,
): node is LineBreakNode;
/**
* LexicalRootNode
*/
declare export class RootNode extends ElementNode {
__cachedText: null | string;
static getType(): string;
static clone(): RootNode;
constructor(): void;
getTextContent(): string;
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
remove(): void;
replace<N: LexicalNode>(node: N): N;
insertBefore<T: LexicalNode>(nodeToInsert: T): T;
insertAfter<T: LexicalNode>(nodeToInsert: T): T;
append(...nodesToAppend: Array<LexicalNode>): this;
canBeEmpty(): false;
}
declare export function $isRootNode(
node: ?LexicalNode,
): node is RootNode;
/**
* LexicalElementNode
*/
export type ElementFormatType =
| 'left'
| 'start'
| 'center'
| 'right'
| 'end'
| 'justify'
| '';
declare export class ElementNode extends LexicalNode {
__first: null | NodeKey;
__last: null | NodeKey;
__size: number;
__format: number;
__indent: number;
__dir: 'ltr' | 'rtl' | null;
constructor(key?: NodeKey): void;
getTopLevelElement(): ElementNode | null;
getTopLevelElementOrThrow(): ElementNode;
getFormat(): number;
getFormatType(): ElementFormatType;
getIndent(): number;
getChildren<T: LexicalNode>(): Array<T>;
getChildren<T: Array<LexicalNode>>(): T;
getChildrenKeys(): Array<NodeKey>;
getChildrenSize(): number;
isEmpty(): boolean;
isDirty(): boolean;
getAllTextNodes(): Array<TextNode>;
getFirstDescendant<T: LexicalNode>(): null | T;
getLastDescendant<T: LexicalNode>(): null | T;
getDescendantByIndex<T: LexicalNode>(index: number): null | T;
getFirstChild<T: LexicalNode>(): null | T;
getFirstChildOrThrow<T: LexicalNode>(): T;
getLastChild<T: LexicalNode>(): null | T;
getLastChildOrThrow<T: LexicalNode>(): T;
getChildAtIndex<T: LexicalNode>(index: number): null | T;
getTextContent(): string;
getDirection(): 'ltr' | 'rtl' | null;
hasFormat(type: ElementFormatType): boolean;
select(_anchorOffset?: number, _focusOffset?: number): RangeSelection;
selectStart(): RangeSelection;
selectEnd(): RangeSelection;
clear(): this;
append(...nodesToAppend: Array<LexicalNode>): this;
setDirection(direction: 'ltr' | 'rtl' | null): this;
setFormat(type: ElementFormatType): this;
setIndent(indentLevel: number): this;
insertNewAfter(
selection: RangeSelection,
restoreSelection?: boolean,
): null | LexicalNode;
canIndent(): boolean;
collapseAtStart(selection: RangeSelection): boolean;
excludeFromCopy(destination: 'clone' | 'html'): boolean;
canReplaceWith(replacement: LexicalNode): boolean;
canInsertAfter(node: LexicalNode): boolean;
extractWithChild(
child: LexicalNode,
selection: BaseSelection,
destination: 'clone' | 'html',
): boolean;
canBeEmpty(): boolean;
canInsertTextBefore(): boolean;
canInsertTextAfter(): boolean;
isInline(): boolean;
isShadowRoot(): boolean;
canSelectionRemove(): boolean;
splice(
start: number,
deleteCount: number,
nodesToInsert: Array<LexicalNode>,
): this;
exportJSON(): SerializedElementNode;
getDOMSlot(dom: HTMLElement): ElementDOMSlot<HTMLElement>;
}
declare export function $isElementNode(
node: ?LexicalNode,
): node is ElementNode;
/**
* ElementDOMSlot
*/
declare export class ElementDOMSlot<+T: HTMLElement> {
+element: HTMLElement;
+before: Node | null;
+after: Node | null;
constructor(element: HTMLElement, before?: Node | null | void, after?: Node | null | void): void;
withBefore(before: Node | null | void): ElementDOMSlot<T>;
withAfter(after: Node | null | void): ElementDOMSlot<T>;
withElement<ElementType: HTMLElement>(element: ElementType): ElementDOMSlot<ElementType>;
insertChild(dom: Node): this;
removeChild(dom: Node): this;
replaceChild(dom: Node, prevDom: Node): this;
getFirstChild(): Node | null;
//
getManagedLineBreak(): HTMLElement | null;
removeManagedLineBreak(): void;
insertManagedLineBreak(webkitHack: boolean): void;
getFirstChildOffset(): number;
resolveChildIndex(element: ElementNode, elementDOM: HTMLElement, initialDOM: Node, initialOffset: number): [node: ElementNode, idx: number];
}
declare export function setDOMUnmanaged(elementDOM: HTMLElement): void;
declare export function isDOMUnmanaged(elementDOM: HTMLElement): boolean;
/**
* LexicalDecoratorNode
*/
declare export class DecoratorNode<X> extends LexicalNode {
constructor(key?: NodeKey): void;
// Not sure how to get flow to agree that the DecoratorNode<mixed> is compatible with this,
// so we have a less precise type than in TS
// getTopLevelElement(): this | ElementNode | null;
// getTopLevelElementOrThrow(): this | ElementNode;
decorate(editor: LexicalEditor, config: EditorConfig): X;
isIsolated(): boolean;
isInline(): boolean;
isKeyboardSelectable(): boolean;
}
declare export function $isDecoratorNode<T = mixed>(
node: ?LexicalNode,
): node is DecoratorNode<T>;
/**
* LexicalParagraphNode
*/
declare export class ParagraphNode extends ElementNode {
static getType(): string;
static clone(node: ParagraphNode): ParagraphNode;
constructor(key?: NodeKey): void;
createDOM(config: EditorConfig): HTMLElement;
insertNewAfter(
selection: RangeSelection,
restoreSelection?: boolean,
): ParagraphNode;
collapseAtStart(): boolean;
static importJSON(
serializedParagraphNode: SerializedParagraphNode,
): ParagraphNode;
exportJSON(): SerializedElementNode;
}
declare export function $createParagraphNode(): ParagraphNode;
declare export function $isParagraphNode(
node: ?LexicalNode,
): node is ParagraphNode;
/**
* LexicalUtils
*/
export type EventHandler = (event: Event, editor: LexicalEditor) => void;
declare export function $hasUpdateTag(tag: string): boolean;
declare export function $addUpdateTag(tag: string): void;
declare export function $onUpdate(updateFn: () => void): void;
declare export function getNearestEditorFromDOMNode(
node: Node | null,
): LexicalEditor | null;
declare export function $getNearestNodeFromDOMNode(
startingDOM: Node,
): LexicalNode | null;
declare export function $getNodeByKey<N: LexicalNode>(key: NodeKey): N | null;
declare export function $getNodeByKeyOrThrow<N: LexicalNode>(key: NodeKey): N;
declare export function $getRoot(): RootNode;
declare export function $isLeafNode<T = mixed>(
node: ?LexicalNode,
): node is TextNode | LineBreakNode | DecoratorNode<T>;
declare export function $setCompositionKey(
compositionKey: null | NodeKey,
): void;
declare export function $setSelection(selection: null | BaseSelection): void;
declare export function $nodesOfType<T: LexicalNode>(klass: Class<T>): Array<T>;
declare export function $getAdjacentNode(
focus: Point,
isBackward: boolean,
): null | LexicalNode;
declare export function resetRandomKey(): void;
declare export function generateRandomKey(): string;
declare export function $isInlineElementOrDecoratorNode<T = mixed>(
node: LexicalNode,
): node is ElementNode| DecoratorNode<T>;
declare export function $getNearestRootOrShadowRoot(
node: LexicalNode,
): RootNode | ElementNode;
declare export function $isRootOrShadowRoot(
node: ?LexicalNode,
): node is RootNode | ElementNode;
declare export function $hasAncestor(
child: LexicalNode,
targetNode: LexicalNode,
): boolean;
declare export function $cloneWithProperties<T: LexicalNode>(node: T): T;
declare export function $copyNode(
node: ElementNode,
offset: number,
): [ElementNode, ElementNode];
declare export function $getEditor(): LexicalEditor;
/**
* LexicalNormalization
*/
declare export function $normalizeSelection__EXPERIMENTAL(
selection: RangeSelection,
): RangeSelection;
/**
* Serialization/Deserialization
* */
type InternalSerializedNode = {
children?: Array<InternalSerializedNode>,
type: string,
version: number,
};
declare export function $parseSerializedNode(
serializedNode: InternalSerializedNode,
): LexicalNode;
declare export function $applyNodeReplacement<N: LexicalNode>(
node: LexicalNode,
): N;
export type SerializedLexicalNode = {
type: string,
version: number,
...
};
export type SerializedTextNode = {
...SerializedLexicalNode,
detail: number,
format: number,
mode: TextModeType,
style: string,
text: string,
...
};
export type SerializedElementNode = {
...SerializedLexicalNode,
children: Array<SerializedLexicalNode>,
direction: 'ltr' | 'rtl' | null,
format: ElementFormatType,
indent: number,
...
};
export type SerializedParagraphNode = {
...SerializedElementNode,
...
};
export type SerializedLineBreakNode = {
...SerializedLexicalNode,
...
};
export type SerializedDecoratorNode = {
...SerializedLexicalNode,
...
};
export type SerializedRootNode = {
...SerializedElementNode,
...
};
export type SerializedGridCellNode = {
...SerializedElementNode,
colSpan: number,
...
};
export interface SerializedEditorState {
root: SerializedRootNode;
}
export type SerializedEditor = {
editorState: SerializedEditorState,
};
/**
* LexicalCaret
*/
export interface BaseCaret<T: LexicalNode, D: CaretDirection, Type> extends Iterable<SiblingCaret<LexicalNode, D>> {
+origin: T;
+type: Type;
+direction: D;
getParentAtCaret(): null | ElementNode;
getNodeAtCaret(): null | LexicalNode;
getAdjacentCaret(): null | SiblingCaret<LexicalNode, D>;
getSiblingCaret(): SiblingCaret<T, D>;
remove(): BaseCaret<T, D, Type>; // this
insert(node: LexicalNode): BaseCaret<T, D, Type>; // this
replaceOrInsert(node: LexicalNode, includeChildren?: boolean): BaseCaret<T, D, Type>; // this
splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): BaseCaret<T, D, Type>; // this
}
export type CaretDirection = 'next' | 'previous';
type FLIP_DIRECTION = {'next' : 'previous', 'previous': 'next'};
export interface CaretRange<D: CaretDirection = CaretDirection> extends Iterable<NodeCaret<D>> {
+type: 'node-caret-range';
+direction: D;
anchor: PointCaret<D>;
focus: PointCaret<D>;
isCollapsed(): boolean;
iterNodeCarets(rootMode?: RootMode): Iterable<NodeCaret<D>>;
getTextSlices(): TextPointCaretSliceTuple<D>;
}
export type CaretType = 'sibling' | 'child';
export interface ChildCaret<T: ElementNode = ElementNode, D: CaretDirection = CaretDirection> extends BaseCaret<T, D, 'child'> {
getLatest(): ChildCaret<T, D>;
getParentCaret(mode?: RootMode): null | SiblingCaret<T, D>;
getParentAtCaret(): T;
getChildCaret(): ChildCaret<T, D>;
isSameNodeCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is ChildCaret<T, D>;
isSamePointCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is ChildCaret<T, D>;
getFlipped(): NodeCaret<FlipDirection<D>>;
// Refine chained types
remove(): ChildCaret<T, D>;
insert(node: LexicalNode): ChildCaret<T, D>;
replaceOrInsert(node: LexicalNode, includeChildren?: boolean): ChildCaret<T, D>;
splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): ChildCaret<T, D>;
}
export type FlipDirection<D: CaretDirection> = FLIP_DIRECTION[D];
export type NodeCaret<D: CaretDirection = CaretDirection> = ChildCaret<ElementNode, D> | SiblingCaret<LexicalNode, D>;
export type PointCaret<D: CaretDirection = CaretDirection> = ChildCaret<ElementNode, D> | SiblingCaret<LexicalNode, D> | TextPointCaret<TextNode, D>;
export type RootMode = 'root' | 'shadowRoot';
export interface SiblingCaret<T: LexicalNode = LexicalNode, D: CaretDirection = CaretDirection> extends BaseCaret<T, D, 'sibling'> {
getLatest(): SiblingCaret<T, D>;
getChildCaret(): null | ChildCaret<T & ElementNode, D>;
getParentCaret(mode?: RootMode): null | SiblingCaret<ElementNode, D>;
isSameNodeCaret(other: null | void | PointCaret<CaretDirection>): boolean; // </CaretDirection>other is SiblingCaret<T, D> | T extends TextNode ? TextPointCaret<T & TextNode, D> : empty;
isSamePointCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is SiblingCaret<T, D>;
getFlipped(): NodeCaret<FlipDirection<D>>;
// Refine chained types
remove(): SiblingCaret<T, D>;
insert(node: LexicalNode): SiblingCaret<T, D>;
replaceOrInsert(node: LexicalNode, includeChildren?: boolean): SiblingCaret<T, D>;
splice(deleteCount: number, nodes: Iterable<LexicalNode>, nodesDirection?: CaretDirection): SiblingCaret<T, D>;
}
export interface StepwiseIteratorConfig<State, Stop, Value> {
+initial: State | Stop;
+hasNext: (value: State | Stop) => implies value is State;
+step: (value: State) => State | Stop;
+map: (value: State) => Value;
}
export interface TextPointCaret<T: TextNode = TextNode, D: CaretDirection = CaretDirection> extends BaseCaret<T, D, 'text'> {
+offset: number;
getLatest(): TextPointCaret<T, D>;
getChildCaret(): null;
getParentCaret(): null | SiblingCaret<ElementNode, D>;
isSameNodeCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is TextPointCaret<T, D> | SiblingCaret<T, D>;
isSamePointCaret(other: null | void | PointCaret<CaretDirection>): boolean; // other is TextPointCaret<T, D>;
getFlipped(): TextPointCaret<T, FlipDirection<D>>;
}
export interface TextPointCaretSlice<T: TextNode = TextNode, D: CaretDirection = CaretDirection> {
+type: 'slice';
+caret: TextPointCaret<T, D>;
+distance: number;
getSliceIndices(): [startIndex: number, endIndex: number];
getTextContent(): string;
getTextContentSize(): number;
removeTextSlice(): TextPointCaret<T, D>;
}
export type TextPointCaretSliceTuple<D: CaretDirection> = [
+anchorSlice: null | TextPointCaretSlice<TextNode, D>,
+focusSlice: null | TextPointCaretSlice<TextNode, D>,
];
declare export function $getAdjacentChildCaret<D: CaretDirection>(caret: null | NodeCaret<D>): null | NodeCaret<D>;
declare export function $getCaretRange<D: CaretDirection>(anchor: PointCaret<D>, focus: PointCaret<D>): CaretRange<D>;
declare export function $getChildCaret<T: null | ElementNode, D: CaretDirection>(origin: T, direction: D): ChildCaret<Exclude<null, T>, D> | Extract<null, T>;
declare export function $getChildCaretOrSelf<Caret: null | PointCaret<CaretDirection>>(caret: Caret): Caret | ChildCaret<ElementNode, Exclude<null, Caret>['direction']>;
declare export function $getSiblingCaret<T: null | LexicalNode, D: CaretDirection>(origin: T, direction: D): SiblingCaret<Exclude<null, T>, D> | Extract<null, T>;
declare export function $getTextNodeOffset(origin: TextNode, offset: number | CaretDirection): number;
declare export function $getTextPointCaret<T: null | TextNode, D: CaretDirection>(origin: T, direction: D, offset: number | CaretDirection): TextPointCaret<Exclude<null, T>, D> | Extract<null, T>;
declare export function $getTextPointCaretSlice<T: TextNode, D: CaretDirection>(caret: TextPointCaret<T, D>, distance: number): TextPointCaretSlice<T, D>;
declare export function $isChildCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is ChildCaret<ElementNode, D>;
declare export function $isNodeCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is NodeCaret<D>;
declare export function $isSiblingCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is SiblingCaret<LexicalNode, D>;
declare export function $isTextPointCaret<D: CaretDirection>(caret: null | void | PointCaret<D>): caret is TextPointCaret<TextNode, D>;
declare export function $isTextPointCaretSlice<D: CaretDirection>(caret: null | void | PointCaret<D> | TextPointCaretSlice<TextNode, D>): caret is TextPointCaretSlice<TextNode, D>;
declare export function flipDirection<D: CaretDirection>(direction: D): FlipDirection<D>;
declare export function makeStepwiseIterator<State, Stop, Value>(
config: StepwiseIteratorConfig<State, Stop, Value>,
): Iterator<Value>;
/**
* LexicalCaretUtils
*/
declare export function $caretFromPoint<D: CaretDirection>(
point: PointType,
direction: D,
): PointCaret<D>;
declare export function $caretRangeFromSelection(
selection: RangeSelection,
): CaretRange<CaretDirection>;
declare export function $getAdjacentSiblingOrParentSiblingCaret<
D: CaretDirection,
>(
startCaret: NodeCaret<D>,
rootMode?: RootMode
): null | [NodeCaret<D>, number]
declare export function $getCaretInDirection<
Caret: PointCaret<CaretDirection>,
D: CaretDirection,
>(
caret: Caret,
direction: D,
):
| NodeCaret<D>
| (Caret extends TextPointCaret<TextNode, CaretDirection>
? TextPointCaret<TextNode, D>
: empty);
declare export function $getCaretRangeInDirection<D: CaretDirection>(
range: CaretRange<CaretDirection>,
direction: D,
): CaretRange<D>;
declare export function $getChildCaretAtIndex<D: CaretDirection>(
parent: ElementNode,
index: number,
direction: D,
): NodeCaret<D>;
declare export function $normalizeCaret<D: CaretDirection>(
initialCaret: PointCaret<D>,
): PointCaret<D>;
declare export function $removeTextFromCaretRange<D: CaretDirection>(
initialRange: CaretRange<D>,
sliceMode?:
| 'removeEmptySlices'
| 'preserveEmptyTextSliceCaret'
): CaretRange<D>;
declare export function $rewindSiblingCaret<
T: LexicalNode,
D: CaretDirection,
>(caret: SiblingCaret<T, D>): NodeCaret<D>;
declare export function $setPointFromCaret<D: CaretDirection>(
point: PointType,
caret: PointCaret<D>,
): void;
declare export function $setSelectionFromCaretRange(
caretRange: CaretRange<CaretDirection>,
): RangeSelection;
declare export function $updateRangeSelectionFromCaretRange(
selection: RangeSelection,
caretRange: CaretRange<CaretDirection>,
): void;
export type CommonAncestorResult<
A: LexicalNode,
B: LexicalNode,
> =
| CommonAncestorResultSame<A>
| CommonAncestorResultAncestor<A & ElementNode>
| CommonAncestorResultDescendant<B & ElementNode>
| CommonAncestorResultBranch<A, B>;
export interface CommonAncestorResultBranch<
A: LexicalNode,
B: LexicalNode,
> {
+type: 'branch';
+commonAncestor: ElementNode;
+a: A | ElementNode;
+b: B | ElementNode;
}
export interface CommonAncestorResultAncestor<A: ElementNode> {
+type: 'ancestor';
+commonAncestor: A;
}
export interface CommonAncestorResultDescendant<B: ElementNode> {
+type: 'descendant';
+commonAncestor: B;
}
export interface CommonAncestorResultSame<A: LexicalNode> {
+type: 'same';
+commonAncestor: A;
}
declare export function $comparePointCaretNext(
a: PointCaret<'next'>,
b: PointCaret<'next'>,
): -1 | 0 | 1;
declare export function $getCommonAncestorResultBranchOrder<
A: LexicalNode,
B: LexicalNode,
>(compare: CommonAncestorResultBranch<A, B>): -1 | 1 ;
declare export function $getCommonAncestor<
A: LexicalNode,
B: LexicalNode,
>(a: A, b: B): null | CommonAncestorResult<A, B>;
declare export function $extendCaretToRange<D: CaretDirection>(
anchor: PointCaret<D>,
): CaretRange<D>;
declare export function $getCollapsedCaretRange<D: CaretDirection>(
anchor: PointCaret<D>,
): CaretRange<D>;
declare export function $isExtendableTextPointCaret<D: CaretDirection>(
caret: PointCaret<D>
): implies caret is TextPointCaret<TextNode, D>;
export interface SplitAtPointCaretNextOptions {
$copyElementNode?: (node: ElementNode) => ElementNode;
$splitTextPointCaretNext?: (
caret: TextPointCaret<TextNode, 'next'>,
) => NodeCaret<'next'>;
rootMode?: RootMode;
$shouldSplit?: (node: ElementNode, edge: 'first' | 'last') => boolean;
}
declare export function $splitAtPointCaretNext(
pointCaret: PointCaret<'next'>,
options?: SplitAtPointCaretNextOptions,
): null | NodeCaret<'next'>;