@lexical/extension 0.35.1-nightly.20250925.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,1445 @@
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
+
9
+ 'use strict';
10
+
11
+ var lexical = require('lexical');
12
+ var utils = require('@lexical/utils');
13
+
14
+ const i=Symbol.for("preact-signals");function t(){if(r>1){r--;return}let i,t=false;while(void 0!==s){let o=s;s=void 0;f++;while(void 0!==o){const n=o.o;o.o=void 0;o.f&=-3;if(!(8&o.f)&&v(o))try{o.c();}catch(o){if(!t){i=o;t=true;}}o=n;}}f=0;r--;if(t)throw i}function o(i){if(r>0)return i();r++;try{return i()}finally{t();}}let n,s;function h(i){const t=n;n=void 0;try{return i()}finally{n=t;}}let r=0,f=0,e=0;function u(i){if(void 0===n)return;let t=i.n;if(void 0===t||t.t!==n){t={i:0,S:i,p:n.s,n:void 0,t:n,e:void 0,x:void 0,r:t};if(void 0!==n.s)n.s.n=t;n.s=t;i.n=t;if(32&n.f)i.S(t);return t}else if(-1===t.i){t.i=0;if(void 0!==t.n){t.n.p=t.p;if(void 0!==t.p)t.p.n=t.n;t.p=n.s;t.n=void 0;n.s.n=t;n.s=t;}return t}}function c(i,t){this.v=i;this.i=0;this.n=void 0;this.t=void 0;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;this.name=null==t?void 0:t.name;}c.prototype.brand=i;c.prototype.h=function(){return true};c.prototype.S=function(i){const t=this.t;if(t!==i&&void 0===i.e){i.x=t;this.t=i;if(void 0!==t)t.e=i;else h(()=>{var i;null==(i=this.W)||i.call(this);});}};c.prototype.U=function(i){if(void 0!==this.t){const t=i.e,o=i.x;if(void 0!==t){t.x=o;i.e=void 0;}if(void 0!==o){o.e=t;i.x=void 0;}if(i===this.t){this.t=o;if(void 0===o)h(()=>{var i;null==(i=this.Z)||i.call(this);});}}};c.prototype.subscribe=function(i){return E(()=>{const t=this.value,o=n;n=void 0;try{i(t);}finally{n=o;}},{name:"sub"})};c.prototype.valueOf=function(){return this.value};c.prototype.toString=function(){return this.value+""};c.prototype.toJSON=function(){return this.value};c.prototype.peek=function(){const i=n;n=void 0;try{return this.value}finally{n=i;}};Object.defineProperty(c.prototype,"value",{get(){const i=u(this);if(void 0!==i)i.i=this.i;return this.v},set(i){if(i!==this.v){if(f>100)throw new Error("Cycle detected");this.v=i;this.i++;e++;r++;try{for(let i=this.t;void 0!==i;i=i.x)i.t.N();}finally{t();}}}});function d(i,t){return new c(i,t)}function v(i){for(let t=i.s;void 0!==t;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return true;return false}function l(i){for(let t=i.s;void 0!==t;t=t.n){const o=t.S.n;if(void 0!==o)t.r=o;t.S.n=t;t.i=-1;if(void 0===t.n){i.s=t;break}}}function y(i){let t,o=i.s;while(void 0!==o){const i=o.p;if(-1===o.i){o.S.U(o);if(void 0!==i)i.n=o.n;if(void 0!==o.n)o.n.p=i;}else t=o;o.S.n=o.r;if(void 0!==o.r)o.r=void 0;o=i;}i.s=t;}function a(i,t){c.call(this,void 0);this.x=i;this.s=void 0;this.g=e-1;this.f=4;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;this.name=null==t?void 0:t.name;}a.prototype=new c;a.prototype.h=function(){this.f&=-3;if(1&this.f)return false;if(32==(36&this.f))return true;this.f&=-5;if(this.g===e)return true;this.g=e;this.f|=1;if(this.i>0&&!v(this)){this.f&=-2;return true}const i=n;try{l(this);n=this;const i=this.x();if(16&this.f||this.v!==i||0===this.i){this.v=i;this.f&=-17;this.i++;}}catch(i){this.v=i;this.f|=16;this.i++;}n=i;y(this);this.f&=-2;return true};a.prototype.S=function(i){if(void 0===this.t){this.f|=36;for(let i=this.s;void 0!==i;i=i.n)i.S.S(i);}c.prototype.S.call(this,i);};a.prototype.U=function(i){if(void 0!==this.t){c.prototype.U.call(this,i);if(void 0===this.t){this.f&=-33;for(let i=this.s;void 0!==i;i=i.n)i.S.U(i);}}};a.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let i=this.t;void 0!==i;i=i.x)i.t.N();}};Object.defineProperty(a.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const i=u(this);this.h();if(void 0!==i)i.i=this.i;if(16&this.f)throw this.v;return this.v}});function w(i,t){return new a(i,t)}function _(i){const o=i.u;i.u=void 0;if("function"==typeof o){r++;const s=n;n=void 0;try{o();}catch(t){i.f&=-2;i.f|=8;b(i);throw t}finally{n=s;t();}}}function b(i){for(let t=i.s;void 0!==t;t=t.n)t.S.U(t);i.x=void 0;i.s=void 0;_(i);}function g(i){if(n!==this)throw new Error("Out-of-order effect");y(this);n=i;this.f&=-2;if(8&this.f)b(this);t();}function p(i,t){this.x=i;this.u=void 0;this.s=void 0;this.o=void 0;this.f=32;this.name=null==t?void 0:t.name;}p.prototype.c=function(){const i=this.S();try{if(8&this.f)return;if(void 0===this.x)return;const t=this.x();if("function"==typeof t)this.u=t;}finally{i();}};p.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1;this.f&=-9;_(this);l(this);r++;const i=n;n=this;return g.bind(this,i)};p.prototype.N=function(){if(!(2&this.f)){this.f|=2;this.o=s;s=this;}};p.prototype.d=function(){this.f|=8;if(!(1&this.f))b(this);};p.prototype.dispose=function(){this.d();};function E(i,t){const o=new p(i,t);try{o.c();}catch(i){o.d();throw i}const n=o.d.bind(o);n[Symbol.dispose]=n;return n}
15
+
16
+ /**
17
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
18
+ *
19
+ * This source code is licensed under the MIT license found in the
20
+ * LICENSE file in the root directory of this source tree.
21
+ *
22
+ */
23
+ /**
24
+ * @experimental
25
+ * Return an object with the same shape as `defaults` with a {@link Signal}
26
+ * for each value. If specified, the second `opts` argument is a partial
27
+ * of overrides to the defaults and will be used as the initial value.
28
+ *
29
+ * Typically used to make a reactive version of some subset of the
30
+ * configuration of an extension, so it can be reconfigured at runtime.
31
+ *
32
+ * @param defaults The object with default values
33
+ * @param opts Overrides to those default values
34
+ * @returns An object with signals initialized with the default values
35
+ */
36
+ function namedSignals(defaults, opts = {}) {
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ const initial = {};
39
+ for (const k in defaults) {
40
+ const v = opts[k];
41
+ const store = d(v === undefined ? defaults[k] : v);
42
+ initial[k] = store;
43
+ }
44
+ return initial;
45
+ }
46
+
47
+ /**
48
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
49
+ *
50
+ * This source code is licensed under the MIT license found in the
51
+ * LICENSE file in the root directory of this source tree.
52
+ *
53
+ */
54
+
55
+ /**
56
+ * An Extension to focus the LexicalEditor when the root element is set
57
+ * (typically only when the editor is first created).
58
+ */
59
+ const AutoFocusExtension = lexical.defineExtension({
60
+ build: (editor, config, state) => {
61
+ return namedSignals(config);
62
+ },
63
+ config: lexical.safeCast({
64
+ defaultSelection: 'rootEnd',
65
+ disabled: false
66
+ }),
67
+ name: '@lexical/extension/AutoFocus',
68
+ register(editor, config, state) {
69
+ const stores = state.getOutput();
70
+ return E(() => stores.disabled.value ? undefined : editor.registerRootListener(rootElement => {
71
+ editor.focus(() => {
72
+ // If we try and move selection to the same point with setBaseAndExtent, it won't
73
+ // trigger a re-focus on the element. So in the case this occurs, we'll need to correct it.
74
+ // Normally this is fine, Selection API !== Focus API, but fore the intents of the naming
75
+ // of this plugin, which should preserve focus too.
76
+ const activeElement = document.activeElement;
77
+ if (rootElement !== null && (activeElement === null || !rootElement.contains(activeElement))) {
78
+ // Note: preventScroll won't work in Webkit.
79
+ rootElement.focus({
80
+ preventScroll: true
81
+ });
82
+ }
83
+ }, {
84
+ defaultSelection: stores.defaultSelection.peek()
85
+ });
86
+ }));
87
+ }
88
+ });
89
+
90
+ /**
91
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
92
+ *
93
+ * This source code is licensed under the MIT license found in the
94
+ * LICENSE file in the root directory of this source tree.
95
+ *
96
+ */
97
+
98
+ function $defaultOnClear() {
99
+ const root = lexical.$getRoot();
100
+ const selection = lexical.$getSelection();
101
+ const paragraph = lexical.$createParagraphNode();
102
+ root.clear();
103
+ root.append(paragraph);
104
+ if (selection !== null) {
105
+ paragraph.select();
106
+ }
107
+ if (lexical.$isRangeSelection(selection)) {
108
+ selection.format = 0;
109
+ }
110
+ }
111
+ function registerClearEditor(editor, $onClear = $defaultOnClear) {
112
+ return editor.registerCommand(lexical.CLEAR_EDITOR_COMMAND, payload => {
113
+ editor.update($onClear);
114
+ return true;
115
+ }, lexical.COMMAND_PRIORITY_EDITOR);
116
+ }
117
+
118
+ /**
119
+ * An extension to provide an implementation of {@link CLEAR_EDITOR_COMMAND}
120
+ */
121
+ const ClearEditorExtension = lexical.defineExtension({
122
+ build(editor, config, state) {
123
+ return namedSignals(config);
124
+ },
125
+ config: lexical.safeCast({
126
+ $onClear: $defaultOnClear
127
+ }),
128
+ name: '@lexical/extension/ClearEditor',
129
+ register(editor, config, state) {
130
+ const {
131
+ $onClear
132
+ } = state.getOutput();
133
+ return E(() => registerClearEditor(editor, $onClear.value));
134
+ }
135
+ });
136
+
137
+ /**
138
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
139
+ *
140
+ * This source code is licensed under the MIT license found in the
141
+ * LICENSE file in the root directory of this source tree.
142
+ *
143
+ */
144
+
145
+ /**
146
+ * @experimental
147
+ * Get the sets of nodes and types registered in the
148
+ * {@link InitialEditorConfig}. This is to be used when an extension
149
+ * needs to register optional behavior if some node or type is present.
150
+ *
151
+ * @param config The InitialEditorConfig (accessible from an extension's init)
152
+ * @returns The known types and nodes as Sets
153
+ */
154
+ function getKnownTypesAndNodes(config) {
155
+ const types = new Set();
156
+ const nodes = new Set();
157
+ for (const klassOrReplacement of config.nodes ?? []) {
158
+ const klass = typeof klassOrReplacement === 'function' ? klassOrReplacement : klassOrReplacement.replace;
159
+ types.add(klass.getType());
160
+ nodes.add(klass);
161
+ }
162
+ return {
163
+ nodes,
164
+ types
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
170
+ *
171
+ * This source code is licensed under the MIT license found in the
172
+ * LICENSE file in the root directory of this source tree.
173
+ *
174
+ */
175
+
176
+ /**
177
+ * @experimental
178
+ * Create a Signal that will subscribe to a value from an external store when watched, similar to
179
+ * React's [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore).
180
+ *
181
+ * @param getSnapshot Used to get the initial value of the signal when created and when first watched.
182
+ * @param register A callback that will subscribe to some external store and update the signal, must return a dispose function.
183
+ * @returns The signal
184
+ */
185
+ function watchedSignal(getSnapshot, register) {
186
+ let dispose;
187
+ return d(getSnapshot(), {
188
+ unwatched() {
189
+ if (dispose) {
190
+ dispose();
191
+ dispose = undefined;
192
+ }
193
+ },
194
+ watched() {
195
+ this.value = getSnapshot();
196
+ dispose = register(this);
197
+ }
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
203
+ *
204
+ * This source code is licensed under the MIT license found in the
205
+ * LICENSE file in the root directory of this source tree.
206
+ *
207
+ */
208
+
209
+ /**
210
+ * An extension to provide the current EditorState as a signal
211
+ */
212
+ const EditorStateExtension = lexical.defineExtension({
213
+ build(editor) {
214
+ return watchedSignal(() => editor.getEditorState(), editorStateSignal => editor.registerUpdateListener(payload => {
215
+ editorStateSignal.value = payload.editorState;
216
+ }));
217
+ },
218
+ name: '@lexical/extension/EditorState'
219
+ });
220
+
221
+ /**
222
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
223
+ *
224
+ * This source code is licensed under the MIT license found in the
225
+ * LICENSE file in the root directory of this source tree.
226
+ *
227
+ */
228
+
229
+ // Do not require this module directly! Use normal `invariant` calls.
230
+
231
+ function formatDevErrorMessage(message) {
232
+ throw new Error(message);
233
+ }
234
+
235
+ /**
236
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
237
+ *
238
+ * This source code is licensed under the MIT license found in the
239
+ * LICENSE file in the root directory of this source tree.
240
+ *
241
+ */
242
+
243
+ /**
244
+ * Recursively merge the given theme configuration in-place.
245
+ *
246
+ * @returns If `a` and `b` are both objects (and `b` is not an Array) then
247
+ * all keys in `b` are merged into `a` then `a` is returned.
248
+ * Otherwise `b` is returned.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const a = { a: "a", nested: { a: 1 } };
253
+ * const b = { b: "b", nested: { b: 2 } };
254
+ * const rval = deepThemeMergeInPlace(a, b);
255
+ * expect(a).toBe(rval);
256
+ * expect(a).toEqual({ a: "a", b: "b", nested: { a: 1, b: 2 } });
257
+ * ```
258
+ */
259
+ function deepThemeMergeInPlace(a, b) {
260
+ if (a && b && !Array.isArray(b) && typeof a === 'object' && typeof b === 'object') {
261
+ const aObj = a;
262
+ const bObj = b;
263
+ for (const k in bObj) {
264
+ aObj[k] = deepThemeMergeInPlace(aObj[k], bObj[k]);
265
+ }
266
+ return a;
267
+ }
268
+ return b;
269
+ }
270
+
271
+ const ExtensionRepStateIds = {
272
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
273
+ unmarked: 0,
274
+ temporary: 1,
275
+ permanent: 2,
276
+ configured: 3,
277
+ initialized: 4,
278
+ built: 5,
279
+ registered: 6,
280
+ afterRegistration: 7
281
+ /* eslint-enable sort-keys-fix/sort-keys-fix */
282
+ };
283
+ function isExactlyUnmarkedExtensionRepState(state) {
284
+ return state.id === ExtensionRepStateIds.unmarked;
285
+ }
286
+ function isExactlyTemporaryExtensionRepState(state) {
287
+ return state.id === ExtensionRepStateIds.temporary;
288
+ }
289
+ function isExactlyPermanentExtensionRepState(state) {
290
+ return state.id === ExtensionRepStateIds.permanent;
291
+ }
292
+ function isConfiguredExtensionRepState(state) {
293
+ return state.id >= ExtensionRepStateIds.configured;
294
+ }
295
+ function isInitializedExtensionRepState(state) {
296
+ return state.id >= ExtensionRepStateIds.initialized;
297
+ }
298
+ function isBuiltExtensionRepState(state) {
299
+ return state.id >= ExtensionRepStateIds.built;
300
+ }
301
+ function isAfterRegistrationState(state) {
302
+ return state.id >= ExtensionRepStateIds.afterRegistration;
303
+ }
304
+ function applyTemporaryMark(state) {
305
+ if (!isExactlyUnmarkedExtensionRepState(state)) {
306
+ formatDevErrorMessage(`LexicalBuilder: Can not apply a temporary mark from state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.unmarked)} unmarked)`);
307
+ }
308
+ return Object.assign(state, {
309
+ id: ExtensionRepStateIds.temporary
310
+ });
311
+ }
312
+ function applyPermanentMark(state) {
313
+ if (!isExactlyTemporaryExtensionRepState(state)) {
314
+ formatDevErrorMessage(`LexicalBuilder: Can not apply a permanent mark from state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.temporary)} temporary)`);
315
+ }
316
+ return Object.assign(state, {
317
+ id: ExtensionRepStateIds.permanent
318
+ });
319
+ }
320
+ function applyConfiguredState(state, config, registerState) {
321
+ return Object.assign(state, {
322
+ config,
323
+ id: ExtensionRepStateIds.configured,
324
+ registerState
325
+ });
326
+ }
327
+ function applyInitializedState(state, initResult, registerState) {
328
+ return Object.assign(state, {
329
+ id: ExtensionRepStateIds.initialized,
330
+ initResult,
331
+ registerState
332
+ });
333
+ }
334
+ function applyBuiltState(state, output, registerState) {
335
+ return Object.assign(state, {
336
+ id: ExtensionRepStateIds.built,
337
+ output,
338
+ registerState
339
+ });
340
+ }
341
+ function applyRegisteredState(state) {
342
+ return Object.assign(state, {
343
+ id: ExtensionRepStateIds.registered
344
+ });
345
+ }
346
+ function applyAfterRegistrationState(state) {
347
+ return Object.assign(state, {
348
+ id: ExtensionRepStateIds.afterRegistration
349
+ });
350
+ }
351
+ function rollbackToBuiltState(state) {
352
+ return Object.assign(state, {
353
+ id: ExtensionRepStateIds.built
354
+ });
355
+ }
356
+ const emptySet = new Set();
357
+
358
+ /**
359
+ * @internal
360
+ */
361
+ class ExtensionRep {
362
+ builder;
363
+ configs;
364
+ _dependency;
365
+ _peerNameSet;
366
+ extension;
367
+ state;
368
+ _signal;
369
+ constructor(builder, extension) {
370
+ this.builder = builder;
371
+ this.extension = extension;
372
+ this.configs = new Set();
373
+ this.state = {
374
+ id: ExtensionRepStateIds.unmarked
375
+ };
376
+ }
377
+ mergeConfigs() {
378
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- LexicalExtensionConfig<Extension> is any
379
+ let config = this.extension.config || {};
380
+ const mergeConfig = this.extension.mergeConfig ? this.extension.mergeConfig.bind(this.extension) : lexical.shallowMergeConfig;
381
+ for (const cfg of this.configs) {
382
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- LexicalExtensionConfig<Extension> is any
383
+ config = mergeConfig(config, cfg);
384
+ }
385
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- any
386
+ return config;
387
+ }
388
+ init(editorConfig) {
389
+ const initialState = this.state;
390
+ if (!isExactlyPermanentExtensionRepState(initialState)) {
391
+ formatDevErrorMessage(`ExtensionRep: Can not configure from state id ${String(initialState.id)}`);
392
+ }
393
+ const initState = {
394
+ getDependency: this.getInitDependency.bind(this),
395
+ getDirectDependentNames: this.getDirectDependentNames.bind(this),
396
+ getPeer: this.getInitPeer.bind(this),
397
+ getPeerNameSet: this.getPeerNameSet.bind(this)
398
+ };
399
+ const buildState = {
400
+ ...initState,
401
+ getDependency: this.getDependency.bind(this),
402
+ getInitResult: this.getInitResult.bind(this),
403
+ getPeer: this.getPeer.bind(this)
404
+ };
405
+ const state = applyConfiguredState(initialState, this.mergeConfigs(), initState);
406
+ this.state = state;
407
+ let initResult;
408
+ if (this.extension.init) {
409
+ initResult = this.extension.init(editorConfig, state.config, initState);
410
+ }
411
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- false positive
412
+ this.state = applyInitializedState(state, initResult, buildState);
413
+ }
414
+ build(editor) {
415
+ const state = this.state;
416
+ if (!(state.id === ExtensionRepStateIds.initialized)) {
417
+ formatDevErrorMessage(`ExtensionRep: register called in state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.built)} initialized)`);
418
+ }
419
+ let output;
420
+ if (this.extension.build) {
421
+ output = this.extension.build(editor, state.config, state.registerState);
422
+ }
423
+ const registerState = {
424
+ ...state.registerState,
425
+ getOutput: () => output,
426
+ getSignal: this.getSignal.bind(this)
427
+ };
428
+ this.state = applyBuiltState(state, output, registerState);
429
+ }
430
+ register(editor, signal) {
431
+ this._signal = signal;
432
+ const state = this.state;
433
+ if (!(state.id === ExtensionRepStateIds.built)) {
434
+ formatDevErrorMessage(`ExtensionRep: register called in state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.built)} built)`);
435
+ }
436
+ const cleanup = this.extension.register && this.extension.register(editor, state.config, state.registerState);
437
+ this.state = applyRegisteredState(state);
438
+ return () => {
439
+ const afterRegistrationState = this.state;
440
+ if (!(afterRegistrationState.id === ExtensionRepStateIds.afterRegistration)) {
441
+ formatDevErrorMessage(`ExtensionRep: rollbackToBuiltState called in state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.afterRegistration)} afterRegistration)`);
442
+ }
443
+ this.state = rollbackToBuiltState(afterRegistrationState);
444
+ if (cleanup) {
445
+ cleanup();
446
+ }
447
+ };
448
+ }
449
+ afterRegistration(editor) {
450
+ const state = this.state;
451
+ if (!(state.id === ExtensionRepStateIds.registered)) {
452
+ formatDevErrorMessage(`ExtensionRep: afterRegistration called in state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.registered)} registered)`);
453
+ }
454
+ let rval;
455
+ if (this.extension.afterRegistration) {
456
+ rval = this.extension.afterRegistration(editor, state.config, state.registerState);
457
+ }
458
+ this.state = applyAfterRegistrationState(state);
459
+ return rval;
460
+ }
461
+ getSignal() {
462
+ if (!(this._signal !== undefined)) {
463
+ formatDevErrorMessage(`ExtensionRep.getSignal() called before register`);
464
+ }
465
+ return this._signal;
466
+ }
467
+ getInitResult() {
468
+ if (!(this.extension.init !== undefined)) {
469
+ formatDevErrorMessage(`ExtensionRep: getInitResult() called for Extension ${this.extension.name} that does not define init`);
470
+ }
471
+ const state = this.state;
472
+ if (!isInitializedExtensionRepState(state)) {
473
+ formatDevErrorMessage(`ExtensionRep: getInitResult() called for ExtensionRep in state id ${String(state.id)} < ${String(ExtensionRepStateIds.initialized)} (initialized)`);
474
+ } // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- any
475
+ return state.initResult;
476
+ }
477
+ getInitPeer(name) {
478
+ const rep = this.builder.extensionNameMap.get(name);
479
+ return rep ? rep.getExtensionInitDependency() : undefined;
480
+ }
481
+ getExtensionInitDependency() {
482
+ const state = this.state;
483
+ if (!isConfiguredExtensionRepState(state)) {
484
+ formatDevErrorMessage(`ExtensionRep: getExtensionInitDependency called in state id ${String(state.id)} (expected >= ${String(ExtensionRepStateIds.configured)} configured)`);
485
+ }
486
+ return {
487
+ config: state.config
488
+ };
489
+ }
490
+ getPeer(name) {
491
+ const rep = this.builder.extensionNameMap.get(name);
492
+ return rep ? rep.getExtensionDependency() : undefined;
493
+ }
494
+ getInitDependency(dep) {
495
+ const rep = this.builder.getExtensionRep(dep);
496
+ if (!(rep !== undefined)) {
497
+ formatDevErrorMessage(`LexicalExtensionBuilder: Extension ${this.extension.name} missing dependency extension ${dep.name} to be in registry`);
498
+ }
499
+ return rep.getExtensionInitDependency();
500
+ }
501
+ getDependency(dep) {
502
+ const rep = this.builder.getExtensionRep(dep);
503
+ if (!(rep !== undefined)) {
504
+ formatDevErrorMessage(`LexicalExtensionBuilder: Extension ${this.extension.name} missing dependency extension ${dep.name} to be in registry`);
505
+ }
506
+ return rep.getExtensionDependency();
507
+ }
508
+ getState() {
509
+ const state = this.state;
510
+ if (!isAfterRegistrationState(state)) {
511
+ formatDevErrorMessage(`ExtensionRep getState called in state id ${String(state.id)} (expected ${String(ExtensionRepStateIds.afterRegistration)} afterRegistration)`);
512
+ }
513
+ return state;
514
+ }
515
+ getDirectDependentNames() {
516
+ return this.builder.incomingEdges.get(this.extension.name) || emptySet;
517
+ }
518
+ getPeerNameSet() {
519
+ let s = this._peerNameSet;
520
+ if (!s) {
521
+ s = new Set((this.extension.peerDependencies || []).map(([name]) => name));
522
+ this._peerNameSet = s;
523
+ }
524
+ return s;
525
+ }
526
+ getExtensionDependency() {
527
+ if (!this._dependency) {
528
+ const state = this.state;
529
+ if (!isBuiltExtensionRepState(state)) {
530
+ formatDevErrorMessage(`Extension ${this.extension.name} used as a dependency before build`);
531
+ }
532
+ this._dependency = {
533
+ config: state.config,
534
+ init: state.initResult,
535
+ output: state.output
536
+ };
537
+ }
538
+ return this._dependency;
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
544
+ *
545
+ * This source code is licensed under the MIT license found in the
546
+ * LICENSE file in the root directory of this source tree.
547
+ *
548
+ */
549
+
550
+ const HISTORY_MERGE_OPTIONS = {
551
+ tag: lexical.HISTORY_MERGE_TAG
552
+ };
553
+ function $defaultInitializer() {
554
+ const root = lexical.$getRoot();
555
+ if (root.isEmpty()) {
556
+ root.append(lexical.$createParagraphNode());
557
+ }
558
+ }
559
+ /**
560
+ * An extension to set the initial state of the editor from
561
+ * a function or serialized JSON EditorState. This is
562
+ * implicitly included with all editors built with
563
+ * Lexical Extension. This happens in the `afterRegistration`
564
+ * phase so your initial state may depend on registered commands,
565
+ * but you should not call `editor.setRootElement` earlier than
566
+ * this phase to avoid rendering an empty editor first.
567
+ */
568
+ const InitialStateExtension = lexical.defineExtension({
569
+ config: lexical.safeCast({
570
+ setOptions: HISTORY_MERGE_OPTIONS,
571
+ updateOptions: HISTORY_MERGE_OPTIONS
572
+ }),
573
+ init({
574
+ $initialEditorState = $defaultInitializer
575
+ }) {
576
+ return {
577
+ $initialEditorState,
578
+ initialized: false
579
+ };
580
+ },
581
+ // eslint-disable-next-line sort-keys-fix/sort-keys-fix -- typescript inference is order dependent here for some reason
582
+ afterRegistration(editor, {
583
+ updateOptions,
584
+ setOptions
585
+ }, state) {
586
+ const initResult = state.getInitResult();
587
+ if (!initResult.initialized) {
588
+ initResult.initialized = true;
589
+ const {
590
+ $initialEditorState
591
+ } = initResult;
592
+ if (lexical.$isEditorState($initialEditorState)) {
593
+ editor.setEditorState($initialEditorState, setOptions);
594
+ } else if (typeof $initialEditorState === 'function') {
595
+ editor.update(() => {
596
+ $initialEditorState(editor);
597
+ }, updateOptions);
598
+ } else if ($initialEditorState && (typeof $initialEditorState === 'string' || typeof $initialEditorState === 'object')) {
599
+ const parsedEditorState = editor.parseEditorState($initialEditorState);
600
+ editor.setEditorState(parsedEditorState, setOptions);
601
+ }
602
+ }
603
+ return () => {};
604
+ },
605
+ name: '@lexical/extension/InitialState',
606
+ // These are automatically added by createEditor, we add them here so they are
607
+ // visible during extensionRep.init so extensions can see all known types before the
608
+ // editor is created.
609
+ // (excluding ArtificialNode__DO_NOT_USE because it isn't really public API
610
+ // and shouldn't change anything)
611
+ nodes: [lexical.RootNode, lexical.TextNode, lexical.LineBreakNode, lexical.TabNode, lexical.ParagraphNode]
612
+ });
613
+
614
+ /** @internal Use a well-known symbol for dev tools purposes */
615
+ const builderSymbol = Symbol.for('@lexical/extension/LexicalBuilder');
616
+
617
+ /**
618
+ * Build a LexicalEditor by combining together one or more extensions, optionally
619
+ * overriding some of their configuration.
620
+ *
621
+ * @param extensions - Extension arguments (extensions or extensions with config overrides)
622
+ * @returns An editor handle
623
+ *
624
+ * @example
625
+ * A single root extension with multiple dependencies
626
+ *
627
+ * ```ts
628
+ * const editor = buildEditorFromExtensions(
629
+ * defineExtension({
630
+ * name: "[root]",
631
+ * dependencies: [
632
+ * RichTextExtension,
633
+ * configExtension(EmojiExtension, { emojiBaseUrl: "/assets/emoji" }),
634
+ * ],
635
+ * register: (editor: LexicalEditor) => {
636
+ * console.log("Editor Created");
637
+ * return () => console.log("Editor Disposed");
638
+ * },
639
+ * }),
640
+ * );
641
+ * ```
642
+ *
643
+ * @example
644
+ * A very similar minimal configuration without the register hook
645
+ *
646
+ * ```ts
647
+ * const editor = buildEditorFromExtensions(
648
+ * RichTextExtension,
649
+ * configExtension(EmojiExtension, { emojiBaseUrl: "/assets/emoji" }),
650
+ * );
651
+ * ```
652
+ */
653
+ function buildEditorFromExtensions(...extensions) {
654
+ return LexicalBuilder.fromExtensions(extensions).buildEditor();
655
+ }
656
+
657
+ /** @internal */
658
+ function noop() {
659
+ /*empty*/
660
+ }
661
+
662
+ /** Throw the given Error */
663
+ function defaultOnError(err) {
664
+ throw err;
665
+ }
666
+ /** @internal */
667
+ function maybeWithBuilder(editor) {
668
+ return editor;
669
+ }
670
+ function normalizeExtensionArgument(arg) {
671
+ return Array.isArray(arg) ? arg : [arg];
672
+ }
673
+ const PACKAGE_VERSION = "0.35.1-nightly.20250925.0+dev.cjs";
674
+
675
+ /** @internal */
676
+ class LexicalBuilder {
677
+ roots;
678
+ extensionNameMap;
679
+ outgoingConfigEdges;
680
+ incomingEdges;
681
+ conflicts;
682
+ _sortedExtensionReps;
683
+ PACKAGE_VERSION;
684
+ constructor(roots) {
685
+ this.outgoingConfigEdges = new Map();
686
+ this.incomingEdges = new Map();
687
+ this.extensionNameMap = new Map();
688
+ this.conflicts = new Map();
689
+ this.PACKAGE_VERSION = PACKAGE_VERSION;
690
+ this.roots = roots;
691
+ for (const extension of roots) {
692
+ this.addExtension(extension);
693
+ }
694
+ }
695
+ static fromExtensions(extensions) {
696
+ const roots = [normalizeExtensionArgument(InitialStateExtension)];
697
+ for (const extension of extensions) {
698
+ roots.push(normalizeExtensionArgument(extension));
699
+ }
700
+ return new LexicalBuilder(roots);
701
+ }
702
+ static maybeFromEditor(editor) {
703
+ const builder = maybeWithBuilder(editor)[builderSymbol];
704
+ if (builder) {
705
+ // The dev tools variant of this will relax some of these invariants
706
+ if (!(builder.PACKAGE_VERSION === PACKAGE_VERSION)) {
707
+ formatDevErrorMessage(`LexicalBuilder.fromEditor: The given editor was created with LexicalBuilder ${builder.PACKAGE_VERSION} but this version is ${PACKAGE_VERSION}. A project should have exactly one copy of LexicalBuilder`);
708
+ }
709
+ if (!(builder instanceof LexicalBuilder)) {
710
+ formatDevErrorMessage(`LexicalBuilder.fromEditor: There are multiple copies of the same version of LexicalBuilder in your project, and this editor was created with another one. Your project, or one of its dependencies, has its package.json and/or bundler configured incorrectly.`);
711
+ }
712
+ }
713
+ return builder;
714
+ }
715
+
716
+ /** Look up the editor that was created by this LexicalBuilder or throw */
717
+ static fromEditor(editor) {
718
+ const builder = LexicalBuilder.maybeFromEditor(editor);
719
+ if (!(builder !== undefined)) {
720
+ formatDevErrorMessage(`LexicalBuilder.fromEditor: The given editor was not created with LexicalBuilder`);
721
+ }
722
+ return builder;
723
+ }
724
+ constructEditor() {
725
+ const {
726
+ $initialEditorState: _$initialEditorState,
727
+ onError,
728
+ ...editorConfig
729
+ } = this.buildCreateEditorArgs();
730
+ const editor = Object.assign(lexical.createEditor({
731
+ ...editorConfig,
732
+ ...(onError ? {
733
+ onError: err => {
734
+ onError(err, editor);
735
+ }
736
+ } : {})
737
+ }), {
738
+ [builderSymbol]: this
739
+ });
740
+ for (const extensionRep of this.sortedExtensionReps()) {
741
+ extensionRep.build(editor);
742
+ }
743
+ return editor;
744
+ }
745
+ buildEditor() {
746
+ let disposeOnce = noop;
747
+ function dispose() {
748
+ try {
749
+ disposeOnce();
750
+ } finally {
751
+ disposeOnce = noop;
752
+ }
753
+ }
754
+ const editor = Object.assign(this.constructEditor(), {
755
+ dispose,
756
+ [Symbol.dispose]: dispose
757
+ });
758
+ disposeOnce = utils.mergeRegister(this.registerEditor(editor), () => editor.setRootElement(null));
759
+ return editor;
760
+ }
761
+ hasExtensionByName(name) {
762
+ return this.extensionNameMap.has(name);
763
+ }
764
+ getExtensionRep(extension) {
765
+ const rep = this.extensionNameMap.get(extension.name);
766
+ if (rep) {
767
+ if (!(rep.extension === extension)) {
768
+ formatDevErrorMessage(`LexicalBuilder: A registered extension with name ${extension.name} exists but does not match the given extension`);
769
+ }
770
+ return rep;
771
+ }
772
+ }
773
+ addEdge(fromExtensionName, toExtensionName, configs) {
774
+ const outgoing = this.outgoingConfigEdges.get(fromExtensionName);
775
+ if (outgoing) {
776
+ outgoing.set(toExtensionName, configs);
777
+ } else {
778
+ this.outgoingConfigEdges.set(fromExtensionName, new Map([[toExtensionName, configs]]));
779
+ }
780
+ const incoming = this.incomingEdges.get(toExtensionName);
781
+ if (incoming) {
782
+ incoming.add(fromExtensionName);
783
+ } else {
784
+ this.incomingEdges.set(toExtensionName, new Set([fromExtensionName]));
785
+ }
786
+ }
787
+ addExtension(arg) {
788
+ if (!(this._sortedExtensionReps === undefined)) {
789
+ formatDevErrorMessage(`LexicalBuilder: addExtension called after finalization`);
790
+ }
791
+ const normalized = normalizeExtensionArgument(arg);
792
+ const [extension] = normalized;
793
+ if (!(typeof extension.name === 'string')) {
794
+ formatDevErrorMessage(`LexicalBuilder: extension name must be string, not ${typeof extension.name}`);
795
+ }
796
+ let extensionRep = this.extensionNameMap.get(extension.name);
797
+ if (!(extensionRep === undefined || extensionRep.extension === extension)) {
798
+ formatDevErrorMessage(`LexicalBuilder: Multiple extensions registered with name ${extension.name}, names must be unique`);
799
+ }
800
+ if (!extensionRep) {
801
+ extensionRep = new ExtensionRep(this, extension);
802
+ this.extensionNameMap.set(extension.name, extensionRep);
803
+ const hasConflict = this.conflicts.get(extension.name);
804
+ if (typeof hasConflict === 'string') {
805
+ {
806
+ formatDevErrorMessage(`LexicalBuilder: extension ${extension.name} conflicts with ${hasConflict}`);
807
+ }
808
+ }
809
+ for (const name of extension.conflictsWith || []) {
810
+ if (!!this.extensionNameMap.has(name)) {
811
+ formatDevErrorMessage(`LexicalBuilder: extension ${extension.name} conflicts with ${name}`);
812
+ }
813
+ this.conflicts.set(name, extension.name);
814
+ }
815
+ for (const dep of extension.dependencies || []) {
816
+ const normDep = normalizeExtensionArgument(dep);
817
+ this.addEdge(extension.name, normDep[0].name, normDep.slice(1));
818
+ this.addExtension(normDep);
819
+ }
820
+ for (const [depName, config] of extension.peerDependencies || []) {
821
+ this.addEdge(extension.name, depName, config ? [config] : []);
822
+ }
823
+ }
824
+ }
825
+ sortedExtensionReps() {
826
+ if (this._sortedExtensionReps) {
827
+ return this._sortedExtensionReps;
828
+ }
829
+ // depth-first search based topological DAG sort
830
+ // https://en.wikipedia.org/wiki/Topological_sorting
831
+ const sortedExtensionReps = [];
832
+ const visit = (rep, fromExtensionName) => {
833
+ let mark = rep.state;
834
+ if (isExactlyPermanentExtensionRepState(mark)) {
835
+ return;
836
+ }
837
+ const extensionName = rep.extension.name;
838
+ if (!isExactlyUnmarkedExtensionRepState(mark)) {
839
+ formatDevErrorMessage(`LexicalBuilder: Circular dependency detected for Extension ${extensionName} from ${fromExtensionName || '[unknown]'}`);
840
+ }
841
+ mark = applyTemporaryMark(mark);
842
+ rep.state = mark;
843
+ const outgoingConfigEdges = this.outgoingConfigEdges.get(extensionName);
844
+ if (outgoingConfigEdges) {
845
+ for (const toExtensionName of outgoingConfigEdges.keys()) {
846
+ const toRep = this.extensionNameMap.get(toExtensionName);
847
+ // may be undefined for an optional peer dependency
848
+ if (toRep) {
849
+ visit(toRep, extensionName);
850
+ }
851
+ }
852
+ }
853
+ mark = applyPermanentMark(mark);
854
+ rep.state = mark;
855
+ sortedExtensionReps.push(rep);
856
+ };
857
+ for (const rep of this.extensionNameMap.values()) {
858
+ if (isExactlyUnmarkedExtensionRepState(rep.state)) {
859
+ visit(rep);
860
+ }
861
+ }
862
+ for (const rep of sortedExtensionReps) {
863
+ for (const [toExtensionName, configs] of this.outgoingConfigEdges.get(rep.extension.name) || []) {
864
+ if (configs.length > 0) {
865
+ const toRep = this.extensionNameMap.get(toExtensionName);
866
+ if (toRep) {
867
+ for (const config of configs) {
868
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- any
869
+ toRep.configs.add(config);
870
+ }
871
+ }
872
+ }
873
+ }
874
+ }
875
+ for (const [extension, ...configs] of this.roots) {
876
+ if (configs.length > 0) {
877
+ const toRep = this.extensionNameMap.get(extension.name);
878
+ if (!(toRep !== undefined)) {
879
+ formatDevErrorMessage(`LexicalBuilder: Expecting existing ExtensionRep for ${extension.name}`);
880
+ }
881
+ for (const config of configs) {
882
+ toRep.configs.add(config);
883
+ }
884
+ }
885
+ }
886
+ this._sortedExtensionReps = sortedExtensionReps;
887
+ return this._sortedExtensionReps;
888
+ }
889
+ registerEditor(editor) {
890
+ const extensionReps = this.sortedExtensionReps();
891
+ const controller = new AbortController();
892
+ const cleanups = [() => controller.abort()];
893
+ const signal = controller.signal;
894
+ for (const extensionRep of extensionReps) {
895
+ const cleanup = extensionRep.register(editor, signal);
896
+ if (cleanup) {
897
+ cleanups.push(cleanup);
898
+ }
899
+ }
900
+ for (const extensionRep of extensionReps) {
901
+ const cleanup = extensionRep.afterRegistration(editor);
902
+ if (cleanup) {
903
+ cleanups.push(cleanup);
904
+ }
905
+ }
906
+ return utils.mergeRegister(...cleanups);
907
+ }
908
+ buildCreateEditorArgs() {
909
+ const config = {};
910
+ const nodes = new Set();
911
+ const replacedNodes = new Map();
912
+ const htmlExport = new Map();
913
+ const htmlImport = {};
914
+ const theme = {};
915
+ const extensionReps = this.sortedExtensionReps();
916
+ for (const extensionRep of extensionReps) {
917
+ const {
918
+ extension
919
+ } = extensionRep;
920
+ if (extension.onError !== undefined) {
921
+ config.onError = extension.onError;
922
+ }
923
+ if (extension.disableEvents !== undefined) {
924
+ config.disableEvents = extension.disableEvents;
925
+ }
926
+ if (extension.parentEditor !== undefined) {
927
+ config.parentEditor = extension.parentEditor;
928
+ }
929
+ if (extension.editable !== undefined) {
930
+ config.editable = extension.editable;
931
+ }
932
+ if (extension.namespace !== undefined) {
933
+ config.namespace = extension.namespace;
934
+ }
935
+ if (extension.$initialEditorState !== undefined) {
936
+ config.$initialEditorState = extension.$initialEditorState;
937
+ }
938
+ if (extension.nodes) {
939
+ for (const node of extension.nodes) {
940
+ if (typeof node !== 'function') {
941
+ const conflictExtension = replacedNodes.get(node.replace);
942
+ if (conflictExtension) {
943
+ {
944
+ formatDevErrorMessage(`LexicalBuilder: Extension ${extension.name} can not register replacement for node ${node.replace.name} because ${conflictExtension.extension.name} already did`);
945
+ }
946
+ }
947
+ replacedNodes.set(node.replace, extensionRep);
948
+ }
949
+ nodes.add(node);
950
+ }
951
+ }
952
+ if (extension.html) {
953
+ if (extension.html.export) {
954
+ for (const [k, v] of extension.html.export.entries()) {
955
+ htmlExport.set(k, v);
956
+ }
957
+ }
958
+ if (extension.html.import) {
959
+ Object.assign(htmlImport, extension.html.import);
960
+ }
961
+ }
962
+ if (extension.theme) {
963
+ deepThemeMergeInPlace(theme, extension.theme);
964
+ }
965
+ }
966
+ if (Object.keys(theme).length > 0) {
967
+ config.theme = theme;
968
+ }
969
+ if (nodes.size) {
970
+ config.nodes = [...nodes];
971
+ }
972
+ const hasImport = Object.keys(htmlImport).length > 0;
973
+ const hasExport = htmlExport.size > 0;
974
+ if (hasImport || hasExport) {
975
+ config.html = {};
976
+ if (hasImport) {
977
+ config.html.import = htmlImport;
978
+ }
979
+ if (hasExport) {
980
+ config.html.export = htmlExport;
981
+ }
982
+ }
983
+ for (const extensionRep of extensionReps) {
984
+ extensionRep.init(config);
985
+ }
986
+ if (!config.onError) {
987
+ config.onError = defaultOnError;
988
+ }
989
+ return config;
990
+ }
991
+ }
992
+
993
+ /**
994
+ * @experimental
995
+ * Get the finalized config and output of an Extension that was used to build the editor.
996
+ *
997
+ * This is useful in the implementation of a LexicalNode or in other
998
+ * situations where you have an editor reference but it's not easy to
999
+ * pass the config or {@link ExtensionRegisterState} around.
1000
+ *
1001
+ * It will throw if the Editor was not built using this Extension.
1002
+ *
1003
+ * @param editor - The editor that was built using extension
1004
+ * @param extension - The concrete reference to an Extension used to build this editor
1005
+ * @returns The config and output for that Extension
1006
+ */
1007
+ function getExtensionDependencyFromEditor(editor, extension) {
1008
+ const builder = LexicalBuilder.fromEditor(editor);
1009
+ const rep = builder.getExtensionRep(extension);
1010
+ if (!(rep !== undefined)) {
1011
+ formatDevErrorMessage(`getExtensionDependencyFromEditor: Extension ${extension.name} was not built when creating this editor`);
1012
+ }
1013
+ return rep.getExtensionDependency();
1014
+ }
1015
+
1016
+ /**
1017
+ * @experimental
1018
+ * Get the finalized config and output of an Extension that was used to build the
1019
+ * editor by name.
1020
+ *
1021
+ * This can be used from the implementation of a LexicalNode or in other
1022
+ * situation where you have an editor reference but it's not easy to pass the
1023
+ * config around. Use this version if you do not have a concrete reference to
1024
+ * the Extension for some reason (e.g. it is an optional peer dependency, or you
1025
+ * are avoiding a circular import).
1026
+ *
1027
+ * Both the explicit Extension type and the name are required.
1028
+ *
1029
+ * @example
1030
+ * ```tsx
1031
+ * import type { HistoryExtension } from "@lexical/history";
1032
+ * getPeerDependencyFromEditor<typeof HistoryExtension>(editor, "@lexical/history/History");
1033
+ * ```
1034
+
1035
+ * @param editor - The editor that may have been built using extension
1036
+ * @param extensionName - The name of the Extension
1037
+ * @returns The config and output of the Extension or undefined
1038
+ */
1039
+ function getPeerDependencyFromEditor(editor, extensionName) {
1040
+ const builder = LexicalBuilder.fromEditor(editor);
1041
+ const peer = builder.extensionNameMap.get(extensionName);
1042
+ return peer ? peer.getExtensionDependency() : undefined;
1043
+ }
1044
+
1045
+ /**
1046
+ * Get the finalized config and output of an Extension that was used to build the
1047
+ * editor by name.
1048
+ *
1049
+ * This can be used from the implementation of a LexicalNode or in other
1050
+ * situation where you have an editor reference but it's not easy to pass the
1051
+ * config around. Use this version if you do not have a concrete reference to
1052
+ * the Extension for some reason (e.g. it is an optional peer dependency, or you
1053
+ * are avoiding a circular import).
1054
+ *
1055
+ * Both the explicit Extension type and the name are required.
1056
+ *
1057
+ * @example
1058
+ * ```tsx
1059
+ * import type { EmojiExtension } from "./EmojiExtension";
1060
+ * export class EmojiNode extends TextNode {
1061
+ * // other implementation details not included
1062
+ * createDOM(
1063
+ * config: EditorConfig,
1064
+ * editor?: LexicalEditor | undefined
1065
+ * ): HTMLElement {
1066
+ * const dom = super.createDOM(config, editor);
1067
+ * addClassNamesToElement(
1068
+ * dom,
1069
+ * getPeerDependencyFromEditorOrThrow<typeof EmojiExtension>(
1070
+ * editor || $getEditor(),
1071
+ * "@lexical/playground/emoji",
1072
+ * ).config.emojiClass,
1073
+ * );
1074
+ * return dom;
1075
+ * }
1076
+ * }
1077
+ * ```
1078
+
1079
+ * @param editor - The editor that may have been built using extension
1080
+ * @param extensionName - The name of the Extension
1081
+ * @returns The config and output of the Extension
1082
+ */
1083
+ function getPeerDependencyFromEditorOrThrow(editor, extensionName) {
1084
+ const dep = getPeerDependencyFromEditor(editor, extensionName);
1085
+ if (!(dep !== undefined)) {
1086
+ formatDevErrorMessage(`getPeerDependencyFromEditorOrThrow: Editor was not built with Extension ${extensionName}`);
1087
+ }
1088
+ return dep;
1089
+ }
1090
+
1091
+ /**
1092
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1093
+ *
1094
+ * This source code is licensed under the MIT license found in the
1095
+ * LICENSE file in the root directory of this source tree.
1096
+ *
1097
+ */
1098
+
1099
+ const EMPTY_SET = new Set();
1100
+
1101
+ /**
1102
+ * An extension that provides a `watchNodeKey` output that
1103
+ * returns a signal for the selection state of a node.
1104
+ *
1105
+ * Typically used for tracking whether a DecoratorNode is
1106
+ * currently selected or not. A framework independent
1107
+ * alternative to {@link useLexicalNodeSelection}.
1108
+ */
1109
+ const NodeSelectionExtension = lexical.defineExtension({
1110
+ build(editor, config, state) {
1111
+ const editorStateStore = state.getDependency(EditorStateExtension).output;
1112
+ const watchedNodeStore = d({
1113
+ watchedNodeKeys: new Map()
1114
+ });
1115
+ const selectedNodeKeys = watchedSignal(() => undefined, () => E(() => {
1116
+ const prevSelectedNodeKeys = selectedNodeKeys.peek();
1117
+ const {
1118
+ watchedNodeKeys
1119
+ } = watchedNodeStore.value;
1120
+ let nextSelectedNodeKeys;
1121
+ let didChange = false;
1122
+ editorStateStore.value.read(() => {
1123
+ const selection = lexical.$getSelection();
1124
+ if (selection) {
1125
+ for (const [key, listeners] of watchedNodeKeys.entries()) {
1126
+ if (listeners.size === 0) {
1127
+ // We intentionally mutate this without firing a signal, to
1128
+ // avoid re-triggering this effect. There are no subscribers
1129
+ // so nothing can observe whether key was in the set or not
1130
+ watchedNodeKeys.delete(key);
1131
+ continue;
1132
+ }
1133
+ const node = lexical.$getNodeByKey(key);
1134
+ const isSelected = node && node.isSelected() || false;
1135
+ didChange = didChange || isSelected !== (prevSelectedNodeKeys ? prevSelectedNodeKeys.has(key) : false);
1136
+ if (isSelected) {
1137
+ nextSelectedNodeKeys = nextSelectedNodeKeys || new Set();
1138
+ nextSelectedNodeKeys.add(key);
1139
+ }
1140
+ }
1141
+ }
1142
+ });
1143
+ if (!(!didChange && nextSelectedNodeKeys && prevSelectedNodeKeys && nextSelectedNodeKeys.size === prevSelectedNodeKeys.size)) {
1144
+ selectedNodeKeys.value = nextSelectedNodeKeys;
1145
+ }
1146
+ }));
1147
+ function watchNodeKey(key) {
1148
+ const watcher = w(() => (selectedNodeKeys.value || EMPTY_SET).has(key));
1149
+ const {
1150
+ watchedNodeKeys
1151
+ } = watchedNodeStore.peek();
1152
+ let listeners = watchedNodeKeys.get(key);
1153
+ const hadListener = listeners !== undefined;
1154
+ listeners = listeners || new Set();
1155
+ listeners.add(watcher);
1156
+ if (!hadListener) {
1157
+ watchedNodeKeys.set(key, listeners);
1158
+ watchedNodeStore.value = {
1159
+ watchedNodeKeys
1160
+ };
1161
+ }
1162
+ return watcher;
1163
+ }
1164
+ return {
1165
+ watchNodeKey
1166
+ };
1167
+ },
1168
+ dependencies: [EditorStateExtension],
1169
+ name: '@lexical/extension/NodeSelection'
1170
+ });
1171
+
1172
+ /**
1173
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1174
+ *
1175
+ * This source code is licensed under the MIT license found in the
1176
+ * LICENSE file in the root directory of this source tree.
1177
+ *
1178
+ */
1179
+
1180
+ const INSERT_HORIZONTAL_RULE_COMMAND = lexical.createCommand('INSERT_HORIZONTAL_RULE_COMMAND');
1181
+ class HorizontalRuleNode extends lexical.DecoratorNode {
1182
+ static getType() {
1183
+ return 'horizontalrule';
1184
+ }
1185
+ static clone(node) {
1186
+ return new HorizontalRuleNode(node.__key);
1187
+ }
1188
+ static importJSON(serializedNode) {
1189
+ return $createHorizontalRuleNode().updateFromJSON(serializedNode);
1190
+ }
1191
+ static importDOM() {
1192
+ return {
1193
+ hr: () => ({
1194
+ conversion: $convertHorizontalRuleElement,
1195
+ priority: 0
1196
+ })
1197
+ };
1198
+ }
1199
+ exportDOM() {
1200
+ return {
1201
+ element: document.createElement('hr')
1202
+ };
1203
+ }
1204
+ createDOM(config) {
1205
+ const element = document.createElement('hr');
1206
+ utils.addClassNamesToElement(element, config.theme.hr);
1207
+ return element;
1208
+ }
1209
+ getTextContent() {
1210
+ return '\n';
1211
+ }
1212
+ isInline() {
1213
+ return false;
1214
+ }
1215
+ updateDOM() {
1216
+ return false;
1217
+ }
1218
+ }
1219
+ function $convertHorizontalRuleElement() {
1220
+ return {
1221
+ node: $createHorizontalRuleNode()
1222
+ };
1223
+ }
1224
+ function $createHorizontalRuleNode() {
1225
+ return lexical.$create(HorizontalRuleNode);
1226
+ }
1227
+ function $isHorizontalRuleNode(node) {
1228
+ return node instanceof HorizontalRuleNode;
1229
+ }
1230
+ function $toggleNodeSelection(node, shiftKey = false) {
1231
+ const selection = lexical.$getSelection();
1232
+ const wasSelected = node.isSelected();
1233
+ const key = node.getKey();
1234
+ let nodeSelection;
1235
+ if (shiftKey && lexical.$isNodeSelection(selection)) {
1236
+ nodeSelection = selection;
1237
+ } else {
1238
+ nodeSelection = lexical.$createNodeSelection();
1239
+ lexical.$setSelection(nodeSelection);
1240
+ }
1241
+ if (wasSelected) {
1242
+ nodeSelection.delete(key);
1243
+ } else {
1244
+ nodeSelection.add(key);
1245
+ }
1246
+ }
1247
+
1248
+ /**
1249
+ * An extension for HorizontalRuleNode that provides an implementation that
1250
+ * works without any React dependency.
1251
+ */
1252
+ const HorizontalRuleExtension = lexical.defineExtension({
1253
+ dependencies: [EditorStateExtension, NodeSelectionExtension],
1254
+ name: '@lexical/extension/HorizontalRule',
1255
+ nodes: [HorizontalRuleNode],
1256
+ register(editor, config, state) {
1257
+ const {
1258
+ watchNodeKey
1259
+ } = state.getDependency(NodeSelectionExtension).output;
1260
+ const nodeSelectionStore = d({
1261
+ nodeSelections: new Map()
1262
+ });
1263
+ const isSelectedClassName = editor._config.theme.hrSelected ?? 'selected';
1264
+ return utils.mergeRegister(editor.registerCommand(lexical.CLICK_COMMAND, event => {
1265
+ if (lexical.isDOMNode(event.target)) {
1266
+ const node = lexical.$getNodeFromDOMNode(event.target);
1267
+ if ($isHorizontalRuleNode(node)) {
1268
+ $toggleNodeSelection(node, event.shiftKey);
1269
+ return true;
1270
+ }
1271
+ }
1272
+ return false;
1273
+ }, lexical.COMMAND_PRIORITY_LOW), editor.registerMutationListener(HorizontalRuleNode, (nodes, payload) => {
1274
+ o(() => {
1275
+ let didChange = false;
1276
+ const {
1277
+ nodeSelections
1278
+ } = nodeSelectionStore.peek();
1279
+ for (const [k, v] of nodes.entries()) {
1280
+ if (v === 'destroyed') {
1281
+ nodeSelections.delete(k);
1282
+ didChange = true;
1283
+ } else {
1284
+ const prev = nodeSelections.get(k);
1285
+ const dom = editor.getElementByKey(k);
1286
+ if (prev) {
1287
+ prev.domNode.value = dom;
1288
+ } else {
1289
+ didChange = true;
1290
+ nodeSelections.set(k, {
1291
+ domNode: d(dom),
1292
+ selectedSignal: watchNodeKey(k)
1293
+ });
1294
+ }
1295
+ }
1296
+ }
1297
+ if (didChange) {
1298
+ nodeSelectionStore.value = {
1299
+ nodeSelections
1300
+ };
1301
+ }
1302
+ });
1303
+ }), E(() => {
1304
+ const effects = [];
1305
+ for (const {
1306
+ domNode,
1307
+ selectedSignal
1308
+ } of nodeSelectionStore.value.nodeSelections.values()) {
1309
+ effects.push(E(() => {
1310
+ const dom = domNode.value;
1311
+ if (dom) {
1312
+ const isSelected = selectedSignal.value;
1313
+ if (isSelected) {
1314
+ utils.addClassNamesToElement(dom, isSelectedClassName);
1315
+ } else {
1316
+ utils.removeClassNamesFromElement(dom, isSelectedClassName);
1317
+ }
1318
+ }
1319
+ }));
1320
+ }
1321
+ return utils.mergeRegister(...effects);
1322
+ }));
1323
+ }
1324
+ });
1325
+
1326
+ /**
1327
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1328
+ *
1329
+ * This source code is licensed under the MIT license found in the
1330
+ * LICENSE file in the root directory of this source tree.
1331
+ *
1332
+ */
1333
+
1334
+ function $indentOverTab(selection) {
1335
+ // const handled = new Set();
1336
+ const nodes = selection.getNodes();
1337
+ const canIndentBlockNodes = utils.$filter(nodes, node => {
1338
+ if (lexical.$isBlockElementNode(node) && node.canIndent()) {
1339
+ return node;
1340
+ }
1341
+ return null;
1342
+ });
1343
+ // 1. If selection spans across canIndent block nodes: indent
1344
+ if (canIndentBlockNodes.length > 0) {
1345
+ return true;
1346
+ }
1347
+ // 2. If first (anchor/focus) is at block start: indent
1348
+ const anchor = selection.anchor;
1349
+ const focus = selection.focus;
1350
+ const first = focus.isBefore(anchor) ? focus : anchor;
1351
+ const firstNode = first.getNode();
1352
+ const firstBlock = utils.$getNearestBlockElementAncestorOrThrow(firstNode);
1353
+ if (firstBlock.canIndent()) {
1354
+ const firstBlockKey = firstBlock.getKey();
1355
+ let selectionAtStart = lexical.$createRangeSelection();
1356
+ selectionAtStart.anchor.set(firstBlockKey, 0, 'element');
1357
+ selectionAtStart.focus.set(firstBlockKey, 0, 'element');
1358
+ selectionAtStart = lexical.$normalizeSelection__EXPERIMENTAL(selectionAtStart);
1359
+ if (selectionAtStart.anchor.is(first)) {
1360
+ return true;
1361
+ }
1362
+ }
1363
+ // 3. Else: tab
1364
+ return false;
1365
+ }
1366
+ function registerTabIndentation(editor, maxIndent) {
1367
+ return utils.mergeRegister(editor.registerCommand(lexical.KEY_TAB_COMMAND, event => {
1368
+ const selection = lexical.$getSelection();
1369
+ if (!lexical.$isRangeSelection(selection)) {
1370
+ return false;
1371
+ }
1372
+ event.preventDefault();
1373
+ const command = $indentOverTab(selection) ? event.shiftKey ? lexical.OUTDENT_CONTENT_COMMAND : lexical.INDENT_CONTENT_COMMAND : lexical.INSERT_TAB_COMMAND;
1374
+ return editor.dispatchCommand(command, undefined);
1375
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.INDENT_CONTENT_COMMAND, () => {
1376
+ const currentMaxIndent = typeof maxIndent === 'number' ? maxIndent : maxIndent ? maxIndent.peek() : null;
1377
+ if (currentMaxIndent == null) {
1378
+ return false;
1379
+ }
1380
+ const selection = lexical.$getSelection();
1381
+ if (!lexical.$isRangeSelection(selection)) {
1382
+ return false;
1383
+ }
1384
+ const indents = selection.getNodes().map(node => utils.$getNearestBlockElementAncestorOrThrow(node).getIndent());
1385
+ return Math.max(...indents) + 1 >= currentMaxIndent;
1386
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1387
+ }
1388
+ /**
1389
+ * This extension adds the ability to indent content using the tab key. Generally, we don't
1390
+ * recommend using this plugin as it could negatively affect accessibility for keyboard
1391
+ * users, causing focus to become trapped within the editor.
1392
+ */
1393
+ const TabIndentationExtension = lexical.defineExtension({
1394
+ build(editor, config, state) {
1395
+ return namedSignals(config);
1396
+ },
1397
+ config: lexical.safeCast({
1398
+ disabled: false,
1399
+ maxIndent: null
1400
+ }),
1401
+ name: '@lexical/extension/TabIndentation',
1402
+ register(editor, config, state) {
1403
+ const {
1404
+ disabled,
1405
+ maxIndent
1406
+ } = state.getOutput();
1407
+ return E(() => {
1408
+ if (!disabled.value) {
1409
+ return registerTabIndentation(editor, maxIndent);
1410
+ }
1411
+ });
1412
+ }
1413
+ });
1414
+
1415
+ exports.configExtension = lexical.configExtension;
1416
+ exports.declarePeerDependency = lexical.declarePeerDependency;
1417
+ exports.defineExtension = lexical.defineExtension;
1418
+ exports.safeCast = lexical.safeCast;
1419
+ exports.shallowMergeConfig = lexical.shallowMergeConfig;
1420
+ exports.$createHorizontalRuleNode = $createHorizontalRuleNode;
1421
+ exports.$isHorizontalRuleNode = $isHorizontalRuleNode;
1422
+ exports.AutoFocusExtension = AutoFocusExtension;
1423
+ exports.ClearEditorExtension = ClearEditorExtension;
1424
+ exports.EditorStateExtension = EditorStateExtension;
1425
+ exports.HorizontalRuleExtension = HorizontalRuleExtension;
1426
+ exports.HorizontalRuleNode = HorizontalRuleNode;
1427
+ exports.INSERT_HORIZONTAL_RULE_COMMAND = INSERT_HORIZONTAL_RULE_COMMAND;
1428
+ exports.InitialStateExtension = InitialStateExtension;
1429
+ exports.LexicalBuilder = LexicalBuilder;
1430
+ exports.NodeSelectionExtension = NodeSelectionExtension;
1431
+ exports.TabIndentationExtension = TabIndentationExtension;
1432
+ exports.batch = o;
1433
+ exports.buildEditorFromExtensions = buildEditorFromExtensions;
1434
+ exports.computed = w;
1435
+ exports.effect = E;
1436
+ exports.getExtensionDependencyFromEditor = getExtensionDependencyFromEditor;
1437
+ exports.getKnownTypesAndNodes = getKnownTypesAndNodes;
1438
+ exports.getPeerDependencyFromEditor = getPeerDependencyFromEditor;
1439
+ exports.getPeerDependencyFromEditorOrThrow = getPeerDependencyFromEditorOrThrow;
1440
+ exports.namedSignals = namedSignals;
1441
+ exports.registerClearEditor = registerClearEditor;
1442
+ exports.registerTabIndentation = registerTabIndentation;
1443
+ exports.signal = d;
1444
+ exports.untracked = h;
1445
+ exports.watchedSignal = watchedSignal;