@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.
- package/dist/HMRExtension.d.ts +170 -0
- package/dist/LexicalExtension.dev.js +967 -57
- package/dist/LexicalExtension.dev.mjs +968 -59
- package/dist/LexicalExtension.js.flow +16 -0
- package/dist/LexicalExtension.mjs +1 -0
- package/dist/LexicalExtension.node.mjs +1 -0
- package/dist/LexicalExtension.prod.js +1 -1
- package/dist/LexicalExtension.prod.mjs +1 -1
- package/dist/editorStateFamily.d.ts +132 -0
- package/dist/index.d.ts +1 -0
- package/package.json +4 -4
- package/src/HMRExtension.ts +710 -0
- package/src/editorStateFamily.ts +510 -0
- package/src/index.ts +6 -0
|
@@ -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 {};
|