diff --git a/packages/lexical-conformance/src/ops.ts b/packages/lexical-conformance/src/ops.ts index 170c8eea967..69687e8c451 100644 --- a/packages/lexical-conformance/src/ops.ts +++ b/packages/lexical-conformance/src/ops.ts @@ -13,9 +13,21 @@ import { registerCodeIndentation, } from '@lexical/code-core'; import { + batch, + buildEditorFromExtensions, + ClearEditorExtension, + computed, + DecoratorTextExtension, DecoratorTextNode, + EditorStateExtension, + effect, + getExtensionDependencyFromEditor, + getKnownTypesAndNodes, + getPeerDependencyFromEditor, HorizontalRuleNode, namedSignals, + type NamedSignalsOutput, + untracked, } from '@lexical/extension'; import {HashtagNode, registerLexicalHashtag} from '@lexical/hashtag'; import {createEmptyHistoryState, registerHistory} from '@lexical/history'; @@ -65,6 +77,7 @@ import { $createNodeSelection, $createParagraphNode, $createRangeSelection, + $createTextNode, $getCaretRange, $getChildCaret, $getNodeByKey, @@ -87,6 +100,8 @@ import { $setSelectionFromCaretRange, $setState, type AnyLexicalCommand, + type AnyLexicalExtension, + type AnyLexicalExtensionArgument, type AnyStateConfig, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, @@ -94,10 +109,12 @@ import { CLEAR_EDITOR_COMMAND, CLEAR_HISTORY_COMMAND, COMMAND_PRIORITY_EDITOR, + configExtension, CONTROLLED_TEXT_INSERTION_COMMAND, createCommand, createEditor, createState, + declarePeerDependency, DELETE_CHARACTER_COMMAND, DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, @@ -110,6 +127,8 @@ import { INSERT_TAB_COMMAND, type LexicalCommand, type LexicalEditor, + type LexicalEditorWithDispose, + type LexicalExtension, type LexicalNode, type NodeCaret, OUTDENT_CONTENT_COMMAND, @@ -120,8 +139,10 @@ import { resetRandomKey, RootNode, SELECT_ALL_COMMAND, + type SerializedEditorState, type SerializedLexicalNode, SET_TEXT_FORMAT_COMMAND, + shallowMergeConfig, TextNode, UNDO_COMMAND, } from 'lexical'; @@ -241,8 +262,127 @@ export type HandlerSpec = { export type Step = ( | {update: Action[]; tag?: string[]} | {dispatch: string; payload?: Json} + /** `editor.dispose()` of an editor built from `extensions`. */ + | {dispose: true} + /** + * In one `batch`, `output[signal].value = value` for each + * `[extension, signal, value]`, where `output` is + * `getExtensionDependencyFromEditor(editor, extension).output` of an + * extension of `extensionDefs` with `signals` (its `namedSignals`). + */ + | {setSignals: [string, string, Json][]} + /** + * Logs `['dependency', key, config]`: + * `getExtensionDependencyFromEditor(editor, extension).config` of an + * extension of `extensionDefs`. + */ + | {logDependency: string} + /** + * Logs `['peer', name, config]`: the config of + * `getPeerDependencyFromEditor(editor, name)`, or `null` without one. + */ + | {logPeer: string} ) & {now?: number}; +/** A config of an extension of `extensionDefs`, or overrides of one. */ +export type ExtensionConfig = {[key: string]: Json}; + +/** + * An extension, by its key in `extensionDefs` or its name in + * REAL_EXTENSIONS, alone or with config overrides + * (`configExtension(extension, ...overrides)`). + */ +export type ExtensionArg = string | [string, ...ExtensionConfig[]]; + +/** The phases an extension of `extensionDefs` defines. */ +export type ExtensionPhase = + | 'init' + | 'build' + | 'register' + | 'afterRegistration'; + +/** + * A test extension, made by `testExtension` from JSON so that every runner + * can make the same one. Each phase it defines (all four by default) logs + * its name and merged config: `['init', name, config]`, + * `['build', name, config]`, `['register', name, config, text]` and + * `['afterRegistration', name, config, text]`, where `text` is the root + * text of `editor.getEditorState()`. `init` returns `{init: name}`. The + * clean-ups that `register` and `afterRegistration` return log + * `['unregister', name]` and `['afterUnregister', name]`. Unless it has + * `signals`, `register` also handles `EXTENSION_COMMAND` (a command the + * script makes), logging `['handled', name, payload]` and returning false. + */ +export type ExtensionSpec = { + name: string; + /** The default config; without it, the builder's default is `{}`. */ + config?: ExtensionConfig; + dependencies?: ExtensionArg[]; + /** `declarePeerDependency(name, config?)` each. */ + peerDependencies?: [string, ExtensionConfig?][]; + conflictsWith?: string[]; + /** Node types, of `@lexical/extension`'s decorators (NODES). */ + nodes?: string[]; + /** + * `$initialEditorState`: `null`, an editor state serialized with + * `JSON.stringify` (`json`), or a function that appends a paragraph + * with `text` to the root. + */ + initialEditorState?: null | {json: SerializedEditorState} | {text: string}; + /** + * `mergeConfig` that merges with `shallowMergeConfig`, then concatenates + * every array of the overrides to the config's array of the same key. + */ + mergeConfig?: 'concat'; + phases?: ExtensionPhase[]; + /** + * `init` logs `['initPeer', name, peer, config]` for `state.getPeer(peer)` + * of each, `null` when it is absent. + */ + initPeers?: string[]; + /** `build` logs `['buildPeer', name, peer, config]`, as `initPeers`. */ + buildPeers?: string[]; + /** + * `init` logs `['initDependency', name, key, config]` and `build` + * `['buildDependency', name, key, config]` for + * `state.getDependency(extension)` of each key of `extensionDefs`. + */ + lookups?: string[]; + /** `register` logs `['initResult', name, state.getInitResult()]`. */ + initResult?: boolean; + /** + * `init` logs `['knownTypes', name, types]` of + * `getKnownTypesAndNodes(editorConfig)`, `['dependents', name, names]` of + * `state.getDirectDependentNames()` and `['peerNames', name, names]` of + * `state.getPeerNameSet()`. + */ + inspect?: boolean; + /** `register` logs `['aborted', name]` when `state.getSignal()` aborts. */ + abort?: boolean; + /** + * `register` registers an update listener that logs + * `['update', name, tags]` (sorted). + */ + listen?: boolean; + /** + * `build` returns `namedSignals(config)`, of a config with a boolean + * `disabled` and a string `label`; `register` runs two effects. One logs + * `['summary', name, summary]`, where `summary` is a `computed` of + * `'off'` when disabled and `on: