@lexical/extension 0.49.1-nightly.20260901.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import { type EditorState, type LexicalEditor, type LexicalExtension } from 'lexical';
9
+ import { type ReadonlySignal, type Signal } from './signals';
10
+ /**
11
+ * Minimal interface for bundler HMR contexts. Satisfied by Vite's
12
+ * `ViteHotContext` and similar bundler HMR contexts. Only the `data`
13
+ * property is read and written; other HMR lifecycle methods are not required.
14
+ *
15
+ * Webpack and Parcel expose `module.hot` instead of `import.meta.hot`. Their
16
+ * `module.hot.data` is populated by dispose handlers and is not directly
17
+ * mutable, so `module.hot` cannot be passed here — a custom adapter using
18
+ * `module.hot.addDisposeHandler` is required for those bundlers.
19
+ */
20
+ export interface HotContext {
21
+ readonly data: Record<string, unknown>;
22
+ }
23
+ /** The state {@link HMRExtension} creates before the editor exists. */
24
+ interface HMRInit {
25
+ restoreCount: Signal<number>;
26
+ }
27
+ /** The output of {@link HMRExtension}. */
28
+ export interface HMROutput {
29
+ /**
30
+ * Increments every time this editor's state has been restored from the
31
+ * module instance that was replaced.
32
+ *
33
+ * Anything that derives editor state from somewhere else has to run again
34
+ * afterwards, and can depend on this signal to be told when: a nested editor
35
+ * wired up by `SharedHistoryExtension` re-points its `HistoryState` at its
36
+ * parent's, which a restore would otherwise have replaced with one of its
37
+ * own.
38
+ */
39
+ restoreCount: ReadonlySignal<number>;
40
+ }
41
+ /** Configuration for {@link HMRExtension}. */
42
+ export interface HMRConfig {
43
+ /**
44
+ * The bundler's HMR context, typically `import.meta.hot`. Pass `null`
45
+ * in production or when HMR is not available.
46
+ */
47
+ hot: HotContext | null;
48
+ /**
49
+ * Stable identifier for this editor instance. Must be stable across HMR
50
+ * reloads — do not use `useId()`, `Math.random()`, or any per-mount
51
+ * identifier (these generate a new value on every mount and will fail to
52
+ * match the key from the previous HMR cycle, preventing state restoration).
53
+ * Only needed when multiple editors share both the same `import.meta.hot`
54
+ * context and the same `namespace` (set via
55
+ * `defineExtension({ namespace: '...' })` or
56
+ * `createEditor({ namespace: '...' })`); editors with distinct namespaces
57
+ * are isolated automatically, editors with no configured namespace all share
58
+ * one key, and a nested editor shares its parent's namespace and so needs an
59
+ * `id` of its own. Must be a non-empty string when provided;
60
+ * passing `''` triggers a dev warning and is treated as no `id`. Both are
61
+ * escaped into the key, so either may contain any character.
62
+ */
63
+ id?: string;
64
+ }
65
+ /**
66
+ * The parts of `@lexical/history` this extension touches, declared here rather
67
+ * than imported from it.
68
+ *
69
+ * `@lexical/history` depends on `@lexical/extension`, not the other way
70
+ * around, and importing even a type across that boundary leaves
71
+ * `@lexical/extension` with an undeclared dependency: anyone type-checking it
72
+ * through its `source` export condition without `@lexical/history` installed
73
+ * gets an unresolved import. History is an optional peer here — an editor
74
+ * without it is restored just the same — so the shapes are described locally.
75
+ * The unit tests assert that they still match the real ones.
76
+ *
77
+ * @internal
78
+ */
79
+ export interface HMRHistoryStateEntry {
80
+ editor: LexicalEditor;
81
+ editorState: EditorState;
82
+ }
83
+ /** @internal See {@link HMRHistoryStateEntry}. */
84
+ export interface HMRHistoryState {
85
+ current: null | HMRHistoryStateEntry;
86
+ redoStack: HMRHistoryStateEntry[];
87
+ undoStack: HMRHistoryStateEntry[];
88
+ }
89
+ /**
90
+ * Preserves editor state, the selection, editability, and undo/redo history
91
+ * across Hot Module Replacement (HMR) cycles. When `HistoryExtension` is
92
+ * present as a peer, undo/redo stacks are preserved as well.
93
+ *
94
+ * Passing `hot: null` is a safe no-op, so `import.meta.hot ?? null` works
95
+ * correctly in both development and production without a build-time
96
+ * conditional. If a saved state cannot be parsed, the extension warns in dev
97
+ * and falls back to `$initialEditorState` rather than throwing.
98
+ *
99
+ * Editor updates only stash a reference to the current `EditorState` (and to
100
+ * the `HistoryState`, which `@lexical/history` mutates in place), so the
101
+ * per-update cost does not grow with the size of the document or of the undo
102
+ * stack. Everything is serialized once, by the module instance that replaces
103
+ * this one, when it restores the saved state.
104
+ *
105
+ * The editor state and the history entries are serialized as one family rather
106
+ * than one at a time, so that the nodes they shared come back shared and every
107
+ * version of a node keeps answering to one key — see `editorStateFamily`. That
108
+ * is what makes an undo after a reload a diff of what changed rather than a
109
+ * rebuild of the document, and it is why the selection can be carried by key.
110
+ *
111
+ * @example
112
+ * Basic usage
113
+ * ```ts
114
+ * import {buildEditorFromExtensions, configExtension, defineExtension, HMRExtension} from '@lexical/extension';
115
+ * import {RichTextExtension} from '@lexical/rich-text';
116
+ * import {HistoryExtension} from '@lexical/history';
117
+ *
118
+ * const editor = buildEditorFromExtensions(
119
+ * defineExtension({
120
+ * name: '[root]',
121
+ * namespace: 'my-editor',
122
+ * dependencies: [
123
+ * RichTextExtension,
124
+ * HistoryExtension,
125
+ * configExtension(HMRExtension, {hot: import.meta.hot ?? null}),
126
+ * ],
127
+ * }),
128
+ * );
129
+ * ```
130
+ *
131
+ * @example
132
+ * Multiple editors sharing an HMR context
133
+ * Editors with distinct `namespace` values are isolated automatically. Only
134
+ * add `id` when two editors share both the same `import.meta.hot` context
135
+ * and the same `namespace`.
136
+ * ```ts
137
+ * // Different namespaces — automatic isolation, no `id` needed
138
+ * defineExtension({ name: '[main]', namespace: 'main', dependencies: [configExtension(HMRExtension, {hot: import.meta.hot ?? null})] })
139
+ * defineExtension({ name: '[sidebar]', namespace: 'sidebar', dependencies: [configExtension(HMRExtension, {hot: import.meta.hot ?? null})] })
140
+ *
141
+ * // Same namespace — use `id` to distinguish
142
+ * defineExtension({ name: '[first]', namespace: 'shared', dependencies: [configExtension(HMRExtension, {hot: import.meta.hot ?? null, id: 'first'})] })
143
+ * defineExtension({ name: '[second]', namespace: 'shared', dependencies: [configExtension(HMRExtension, {hot: import.meta.hot ?? null, id: 'second'})] })
144
+ * ```
145
+ *
146
+ * Only the undo/redo entries this editor recorded are preserved. With
147
+ * `SharedHistoryExtension` a nested editor pushes onto its parent's stacks, and
148
+ * an entry can only be applied to the editor that recorded it — editors the
149
+ * reload replaced, so those entries are left behind rather than re-pointed at
150
+ * whichever editor happens to restore the history.
151
+ *
152
+ * A nested editor inherits its parent's namespace, so namespaces do not
153
+ * isolate it from its parent: give it a distinct `id` (or its own
154
+ * `namespace`), which is warned about in dev when both use HMRExtension.
155
+ *
156
+ * An editor that was given no `namespace` at all is keyed without one, because
157
+ * the namespace `createEditor` generates for it is a fresh random string on
158
+ * every reload — a key built from that would never match what the previous
159
+ * instance saved. Such editors all share one key, so give each editor a
160
+ * `namespace` (or an `id`) as soon as a page has more than one.
161
+ *
162
+ * Saved state belongs to the key for as long as the page lives, not to the
163
+ * reload that produced it. An editor that is unmounted and later remounted
164
+ * under the same key during development restores what the previous one had
165
+ * rather than its own `$initialEditorState`, so two editors showing different
166
+ * documents need distinct namespaces (or ids) even when they are never on
167
+ * screen at the same time.
168
+ */
169
+ export declare const HMRExtension: LexicalExtension<HMRConfig, "@lexical/extension/HMR", HMROutput, HMRInit>;
170
+ export {};