From 33ecc911bda013bccb1ddfa44c8da944e07e5679 Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Sat, 15 Feb 2025 20:41:02 -0800 Subject: [PATCH 1/4] $config/config RFC --- packages/lexical-html/src/index.ts | 3 +- .../lexical-list/src/LexicalListItemNode.ts | 36 +- packages/lexical-list/src/LexicalListNode.ts | 23 +- packages/lexical-overflow/src/index.ts | 37 +- .../lexical-playground/src/nodes/PollNode.tsx | 57 +- .../CollapsiblePlugin/CollapsibleTitleNode.ts | 41 +- .../src/LexicalNestedComposer.tsx | 9 +- packages/lexical/src/LexicalConstants.ts | 1 + packages/lexical/src/LexicalEditor.ts | 82 ++- packages/lexical/src/LexicalNode.ts | 187 ++++- packages/lexical/src/LexicalNodeState.ts | 678 ++++++++++++------ packages/lexical/src/LexicalNormalization.ts | 4 +- packages/lexical/src/LexicalUpdates.ts | 3 + packages/lexical/src/LexicalUtils.ts | 155 +++- .../src/__tests__/unit/LexicalEditor.test.tsx | 10 +- .../src/__tests__/unit/LexicalNode.test.ts | 102 +++ .../__tests__/unit/LexicalNodeState.test.ts | 100 ++- .../src/__tests__/unit/LexicalUtils.test.ts | 10 + .../lexical/src/caret/LexicalCaretUtils.ts | 7 +- packages/lexical/src/index.ts | 12 + 20 files changed, 1174 insertions(+), 383 deletions(-) diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index 1832ca67d40..1b99876f763 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -29,6 +29,7 @@ import { $isTextNode, ArtificialNode__DO_NOT_USE, ElementNode, + getRegisteredNode, isDocumentFragment, isInlineDomNode, } from 'lexical'; @@ -110,7 +111,7 @@ function $appendNodesToHTML( target = clone; } const children = $isElementNode(target) ? target.getChildren() : []; - const registeredNode = editor._nodes.get(target.getType()); + const registeredNode = getRegisteredNode(editor, target.getType()); let exportOutput; // Use HTMLConfig overrides, if available. diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index e0849259634..aedd03cfc12 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -21,6 +21,7 @@ import type { RangeSelection, SerializedElementNode, Spread, + StaticNodeConfigRecord, } from 'lexical'; import { @@ -58,6 +59,26 @@ export class ListItemNode extends ElementNode { /** @internal */ __checked?: boolean; + /** @internal */ + $config(): StaticNodeConfigRecord< + 'listitem', + {$transform: (node: ListItemNode) => void} + > { + return this.config('listitem', { + $transform: (node: ListItemNode): void => { + if (node.__checked == null) { + return; + } + const parent = node.getParent(); + if ($isListNode(parent)) { + if (parent.getListType() !== 'check' && node.getChecked() != null) { + node.setChecked(undefined); + } + } + }, + }); + } + static getType(): string { return 'listitem'; } @@ -110,21 +131,6 @@ export class ListItemNode extends ElementNode { return false; } - static transform(): (node: LexicalNode) => void { - return (node: LexicalNode) => { - invariant($isListItemNode(node), 'node is not a ListItemNode'); - if (node.__checked == null) { - return; - } - const parent = node.getParent(); - if ($isListNode(parent)) { - if (parent.getListType() !== 'check' && node.getChecked() != null) { - node.setChecked(undefined); - } - } - }; - } - static importDOM(): DOMConversionMap | null { return { li: () => ({ diff --git a/packages/lexical-list/src/LexicalListNode.ts b/packages/lexical-list/src/LexicalListNode.ts index 6fdab61c6b1..f27ac3071bf 100644 --- a/packages/lexical-list/src/LexicalListNode.ts +++ b/packages/lexical-list/src/LexicalListNode.ts @@ -27,8 +27,8 @@ import { NodeKey, SerializedElementNode, Spread, + StaticNodeConfigRecord, } from 'lexical'; -import invariant from 'shared/invariant'; import normalizeClassNames from 'shared/normalizeClassNames'; import {$createListItemNode, $isListItemNode, ListItemNode} from '.'; @@ -60,6 +60,19 @@ export class ListNode extends ElementNode { /** @internal */ __listType: ListType; + /** @internal */ + $config(): StaticNodeConfigRecord< + 'list', + {$transform: (node: ListNode) => void} + > { + return this.config('list', { + $transform: (node: ListNode): void => { + mergeNextSiblingListIfSameType(node); + updateChildrenListItemValue(node); + }, + }); + } + static getType(): string { return 'list'; } @@ -129,14 +142,6 @@ export class ListNode extends ElementNode { return false; } - static transform(): (node: LexicalNode) => void { - return (node: LexicalNode) => { - invariant($isListNode(node), 'node is not a ListNode'); - mergeNextSiblingListIfSameType(node); - updateChildrenListItemValue(node); - }; - } - static importDOM(): DOMConversionMap | null { return { ol: () => ({ diff --git a/packages/lexical-overflow/src/index.ts b/packages/lexical-overflow/src/index.ts index c6284e3fd95..54e2dcfb6c3 100644 --- a/packages/lexical-overflow/src/index.ts +++ b/packages/lexical-overflow/src/index.ts @@ -11,29 +11,27 @@ import type { LexicalNode, RangeSelection, SerializedElementNode, + StaticNodeConfigRecord, } from 'lexical'; import {$applyNodeReplacement, ElementNode} from 'lexical'; -import invariant from 'shared/invariant'; export type SerializedOverflowNode = SerializedElementNode; /** @noInheritDoc */ export class OverflowNode extends ElementNode { - static getType(): string { - return 'overflow'; - } - - static clone(node: OverflowNode): OverflowNode { - return new OverflowNode(node.__key); - } - - static importJSON(serializedNode: SerializedOverflowNode): OverflowNode { - return $createOverflowNode().updateFromJSON(serializedNode); - } - - static importDOM(): null { - return null; + /** @internal */ + $config(): StaticNodeConfigRecord< + 'overflow', + {$transform: (node: OverflowNode) => void} + > { + return this.config('overflow', { + $transform(node: OverflowNode) { + if (node.isEmpty()) { + node.remove(); + } + }, + }); } createDOM(config: EditorConfig): HTMLElement { @@ -60,15 +58,6 @@ export class OverflowNode extends ElementNode { excludeFromCopy(): boolean { return true; } - - static transform(): (node: LexicalNode) => void { - return (node: LexicalNode) => { - invariant($isOverflowNode(node), 'node is not a OverflowNode'); - if (node.isEmpty()) { - node.remove(); - } - }; - } } export function $createOverflowNode(): OverflowNode { diff --git a/packages/lexical-playground/src/nodes/PollNode.tsx b/packages/lexical-playground/src/nodes/PollNode.tsx index 3e066b4f5f8..9d7e6e28459 100644 --- a/packages/lexical-playground/src/nodes/PollNode.tsx +++ b/packages/lexical-playground/src/nodes/PollNode.tsx @@ -8,8 +8,9 @@ import type {JSX} from 'react'; -import {makeStateWrapper} from '@lexical/utils'; import { + $getState, + $setState, createState, DecoratorNode, DOMConversionMap, @@ -18,7 +19,9 @@ import { LexicalNode, SerializedLexicalNode, Spread, + StateConfigValue, } from 'lexical'; +import {StateValueOrUpdater} from 'packages/lexical/src/LexicalNodeState'; import * as React from 'react'; export type Options = ReadonlyArray