@lexical/html 0.44.1-nightly.20260518.0 → 0.45.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.
Files changed (66) hide show
  1. package/{DOMRenderExtension.d.ts → dist/DOMRenderExtension.d.ts} +12 -1
  2. package/dist/DOMRenderRuntime.d.ts +51 -0
  3. package/dist/LexicalHtml.dev.js +3192 -0
  4. package/dist/LexicalHtml.dev.mjs +3146 -0
  5. package/{LexicalHtml.js.flow → dist/LexicalHtml.js.flow} +16 -16
  6. package/dist/LexicalHtml.mjs +56 -0
  7. package/dist/LexicalHtml.node.mjs +54 -0
  8. package/dist/LexicalHtml.prod.js +9 -0
  9. package/dist/LexicalHtml.prod.mjs +9 -0
  10. package/dist/RenderContext.d.ts +68 -0
  11. package/{compileDOMRenderConfigOverrides.d.ts → dist/compileDOMRenderConfigOverrides.d.ts} +1 -1
  12. package/{constants.d.ts → dist/constants.d.ts} +2 -0
  13. package/dist/domOverride.d.ts +23 -0
  14. package/dist/import/CoreImportExtension.d.ts +11 -0
  15. package/dist/import/DOMImportExtension.d.ts +82 -0
  16. package/dist/import/HorizontalRuleImportExtension.d.ts +27 -0
  17. package/dist/import/ImportContext.d.ts +208 -0
  18. package/dist/import/compileImportRules.d.ts +50 -0
  19. package/dist/import/coreImportRules.d.ts +25 -0
  20. package/dist/import/defineImportRule.d.ts +32 -0
  21. package/dist/import/defineOverlayRules.d.ts +66 -0
  22. package/dist/import/index.d.ts +38 -0
  23. package/dist/import/inlineStylesFromStyleSheets.d.ts +28 -0
  24. package/dist/import/parseCss.d.ts +18 -0
  25. package/dist/import/runImport.d.ts +19 -0
  26. package/dist/import/schemas.d.ts +91 -0
  27. package/dist/import/sel.d.ts +74 -0
  28. package/dist/import/types.d.ts +394 -0
  29. package/dist/index.d.ts +44 -0
  30. package/{types.d.ts → dist/types.d.ts} +96 -8
  31. package/package.json +33 -18
  32. package/src/ContextRecord.ts +243 -0
  33. package/src/DOMRenderExtension.ts +96 -0
  34. package/src/DOMRenderRuntime.ts +265 -0
  35. package/src/RenderContext.ts +168 -0
  36. package/src/compileDOMRenderConfigOverrides.ts +416 -0
  37. package/src/constants.ts +18 -0
  38. package/src/domOverride.ts +46 -0
  39. package/src/import/CoreImportExtension.ts +26 -0
  40. package/src/import/DOMImportExtension.ts +221 -0
  41. package/src/import/HorizontalRuleImportExtension.ts +53 -0
  42. package/src/import/ImportContext.ts +339 -0
  43. package/src/import/compileImportRules.ts +178 -0
  44. package/src/import/coreImportRules.ts +485 -0
  45. package/src/import/defineImportRule.ts +40 -0
  46. package/src/import/defineOverlayRules.ts +105 -0
  47. package/src/import/index.ts +96 -0
  48. package/src/import/inlineStylesFromStyleSheets.ts +104 -0
  49. package/src/import/parseCss.ts +219 -0
  50. package/src/import/runImport.ts +245 -0
  51. package/src/import/schemas.ts +236 -0
  52. package/src/import/sel.ts +314 -0
  53. package/src/import/types.ts +471 -0
  54. package/src/index.ts +555 -0
  55. package/src/types.ts +470 -0
  56. package/LexicalHtml.dev.js +0 -914
  57. package/LexicalHtml.dev.mjs +0 -900
  58. package/LexicalHtml.mjs +0 -24
  59. package/LexicalHtml.node.mjs +0 -22
  60. package/LexicalHtml.prod.js +0 -9
  61. package/LexicalHtml.prod.mjs +0 -9
  62. package/RenderContext.d.ts +0 -32
  63. package/domOverride.d.ts +0 -18
  64. package/index.d.ts +0 -32
  65. /package/{ContextRecord.d.ts → dist/ContextRecord.d.ts} +0 -0
  66. /package/{LexicalHtml.js → dist/LexicalHtml.js} +0 -0
@@ -0,0 +1,3146 @@
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
+ import { $sliceSelectedTextNodeContent } from '@lexical/selection';
10
+ import { $getEditor, createState, isDOMDocumentNode, isHTMLElement, $getEditorDOMRenderConfig, DEFAULT_EDITOR_DOM_CONFIG, $isLexicalNode, getStaticNodeConfig, $fullReconcile, defineExtension, shallowMergeConfig, RootNode, isDOMTextNode, isBlockDomNode, isInlineDomNode, IS_HIGHLIGHT, IS_CODE, $generateNodesFromRawText, $createTextNode, $createLineBreakNode, $createParagraphNode, $setFormatFromDOM, setNodeIndentFromDOM, $setDirectionFromDOM, $isTextNode, IS_BOLD, IS_ITALIC, IS_UNDERLINE, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, $isElementNode, $isBlockElementNode, $isDecoratorNode, configExtension, $getRoot, isDocumentFragment, $isRootOrShadowRoot, ArtificialNode__DO_NOT_USE } from 'lexical';
11
+ import { objectKlassEquals } from '@lexical/utils';
12
+ import { getPeerDependencyFromEditor, getKnownTypesAndNodes, $getExtensionOutput, HorizontalRuleExtension, $createHorizontalRuleNode } from '@lexical/extension';
13
+
14
+ /**
15
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
16
+ *
17
+ * This source code is licensed under the MIT license found in the
18
+ * LICENSE file in the root directory of this source tree.
19
+ *
20
+ */
21
+
22
+ // Do not require this module directly! Use normal `invariant` calls.
23
+
24
+ function formatDevErrorMessage(message) {
25
+ throw new Error(message);
26
+ }
27
+
28
+ /**
29
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
30
+ *
31
+ * This source code is licensed under the MIT license found in the
32
+ * LICENSE file in the root directory of this source tree.
33
+ *
34
+ */
35
+
36
+ let activeContext;
37
+
38
+ /**
39
+ * @experimental
40
+ *
41
+ * The LexicalEditor with context
42
+ */
43
+
44
+ /**
45
+ * @experimental
46
+ *
47
+ * @param contextRecord The ContextRecord
48
+ * @param cfg The configuration
49
+ * @returns The value or defaultValue of cfg
50
+ */
51
+ function getContextValue(contextRecord, cfg) {
52
+ const {
53
+ key
54
+ } = cfg;
55
+ return contextRecord && key in contextRecord ? contextRecord[key] : cfg.defaultValue;
56
+ }
57
+ function getEditorContext(editor) {
58
+ return activeContext && activeContext.editor === editor ? activeContext : undefined;
59
+ }
60
+
61
+ /**
62
+ * @experimental
63
+ *
64
+ * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol)
65
+ * @param editor The editor
66
+ * @returns The current context or undefined
67
+ */
68
+ function getContextRecord(sym, editor) {
69
+ const editorContext = getEditorContext(editor);
70
+ return editorContext && editorContext[sym];
71
+ }
72
+ function toPair(contextRecord, pairOrUpdater) {
73
+ if ('cfg' in pairOrUpdater) {
74
+ const {
75
+ cfg,
76
+ updater
77
+ } = pairOrUpdater;
78
+ return [cfg, updater(getContextValue(contextRecord, cfg))];
79
+ }
80
+ return pairOrUpdater;
81
+ }
82
+
83
+ /**
84
+ * Construct a new context from a parent context and pairs
85
+ *
86
+ * @param pairs The pairs and updaters to build the context from
87
+ * @param parent The parent context
88
+ * @returns The new context
89
+ */
90
+ function contextFromPairs(pairs, parent) {
91
+ let rval = parent;
92
+ for (const pairOrUpdater of pairs) {
93
+ const [k, v] = toPair(rval, pairOrUpdater);
94
+ const key = k.key;
95
+ if (rval === parent && getContextValue(rval, k) === v) {
96
+ continue;
97
+ }
98
+ // If we haven't branched away from `parent` yet, create a fresh child
99
+ // context so we never mutate the caller's parent record. Subsequent
100
+ // pairs in this loop accumulate into the same child. Inside the loop
101
+ // `rval` is non-null after the first iteration, since createChildContext
102
+ // never returns null/undefined.
103
+ const ctx = rval === parent || rval === undefined ? createChildContext(parent) : rval;
104
+ ctx[key] = v;
105
+ rval = ctx;
106
+ }
107
+ return rval;
108
+ }
109
+ function createChildContext(parent) {
110
+ return Object.create(parent || null);
111
+ }
112
+
113
+ /**
114
+ * Create a context config pair that sets a value in the render context.
115
+ * @experimental
116
+ */
117
+ function contextValue(cfg, value) {
118
+ return [cfg, value];
119
+ }
120
+
121
+ /**
122
+ * Create a context config updater that transforms a value in the render context.
123
+ * @experimental
124
+ */
125
+ function contextUpdater(cfg, updater) {
126
+ return {
127
+ cfg,
128
+ updater
129
+ };
130
+ }
131
+
132
+ /**
133
+ * @internal
134
+ * @experimental
135
+ * @__NO_SIDE_EFFECTS__
136
+ */
137
+ function $withFullContext(sym, contextRecord, f, editor = $getEditor()) {
138
+ const prevDOMContext = activeContext;
139
+ const parentEditorContext = getEditorContext(editor);
140
+ try {
141
+ activeContext = {
142
+ ...parentEditorContext,
143
+ editor,
144
+ [sym]: contextRecord
145
+ };
146
+ return f();
147
+ } finally {
148
+ activeContext = prevDOMContext;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * @internal
154
+ * @experimental
155
+ * @__NO_SIDE_EFFECTS__
156
+ */
157
+ function $withContext(sym, $defaults = () => undefined) {
158
+ return (cfg, editor = $getEditor()) => {
159
+ return f => {
160
+ const parentEditorContext = getEditorContext(editor);
161
+ const parentContextRecord = parentEditorContext && parentEditorContext[sym];
162
+ const contextRecord = contextFromPairs(cfg, parentContextRecord || $defaults(editor));
163
+ if (!contextRecord || contextRecord === parentContextRecord) {
164
+ return f();
165
+ }
166
+ return $withFullContext(sym, contextRecord, f, editor);
167
+ };
168
+ };
169
+ }
170
+
171
+ /**
172
+ * @experimental
173
+ * @internal
174
+ * @__NO_SIDE_EFFECTS__
175
+ */
176
+ function createContextState(tag, name, getDefaultValue, isEqual) {
177
+ return Object.assign(createState(Symbol(name), {
178
+ isEqual,
179
+ parse: getDefaultValue
180
+ }), {
181
+ [tag]: true
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
187
+ *
188
+ * This source code is licensed under the MIT license found in the
189
+ * LICENSE file in the root directory of this source tree.
190
+ *
191
+ */
192
+
193
+
194
+ /**
195
+ * Inlines CSS rules from `<style>` tags onto matching elements as inline
196
+ * styles.
197
+ *
198
+ * Used by apps like Excel that generate HTML where styles live in
199
+ * class-based `<style>` rules (e.g. `.xl65 { background: #FFFF00; color:
200
+ * blue; }`) rather than inline styles. Since Lexical's import converters
201
+ * read inline styles, we resolve stylesheet rules into inline styles
202
+ * before conversion.
203
+ *
204
+ * Mutates the DOM in-place. Original inline styles always take
205
+ * precedence over stylesheet rules (matching CSS specificity behavior).
206
+ *
207
+ * No-op for {@link ParentNode}s that are not {@link Document}s — only a
208
+ * full document carries `styleSheets` we can iterate.
209
+ *
210
+ * @experimental
211
+ */
212
+ const $inlineStylesFromStyleSheets = (dom, _ctx, $next) => {
213
+ $inlineStylesFromStyleSheetsDOM(dom);
214
+ $next();
215
+ };
216
+ function $inlineStylesFromStyleSheetsDOM(dom) {
217
+ if (!isDOMDocumentNode(dom)) {
218
+ return;
219
+ }
220
+ const doc = dom;
221
+ if (doc.querySelector('style') === null) {
222
+ return;
223
+ }
224
+ const originalInlineStyles = new Map();
225
+ function getOriginalInlineProps(el) {
226
+ let props = originalInlineStyles.get(el);
227
+ if (props === undefined) {
228
+ props = new Set();
229
+ for (let i = 0; i < el.style.length; i++) {
230
+ props.add(el.style[i]);
231
+ }
232
+ originalInlineStyles.set(el, props);
233
+ }
234
+ return props;
235
+ }
236
+ try {
237
+ for (const sheet of Array.from(doc.styleSheets)) {
238
+ let rules;
239
+ try {
240
+ rules = sheet.cssRules;
241
+ } catch (_unused) {
242
+ continue;
243
+ }
244
+ for (const rule of Array.from(rules)) {
245
+ if (!objectKlassEquals(rule, CSSStyleRule)) {
246
+ continue;
247
+ }
248
+ let elements;
249
+ try {
250
+ elements = doc.querySelectorAll(rule.selectorText);
251
+ } catch (_unused2) {
252
+ continue;
253
+ }
254
+ for (const el of Array.from(elements)) {
255
+ if (!isHTMLElement(el)) {
256
+ continue;
257
+ }
258
+ const originalProps = getOriginalInlineProps(el);
259
+ for (let i = 0; i < rule.style.length; i++) {
260
+ const prop = rule.style[i];
261
+ if (!originalProps.has(prop)) {
262
+ el.style.setProperty(prop, rule.style.getPropertyValue(prop), rule.style.getPropertyPriority(prop));
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ } catch (_unused3) {
269
+ // styleSheets API not supported in this environment
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
275
+ *
276
+ * This source code is licensed under the MIT license found in the
277
+ * LICENSE file in the root directory of this source tree.
278
+ *
279
+ */
280
+ const DOMRenderExtensionName = '@lexical/html/DOM';
281
+ const DOMRenderContextSymbol = Symbol.for('@lexical/html/DOMExportContext');
282
+ const DOMImportExtensionName = '@lexical/html/DOMImport';
283
+ const DOMImportContextSymbol = Symbol.for('@lexical/html/DOMImportContext');
284
+ const ALWAYS_TRUE = () => true;
285
+
286
+ /**
287
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
288
+ *
289
+ * This source code is licensed under the MIT license found in the
290
+ * LICENSE file in the root directory of this source tree.
291
+ *
292
+ */
293
+
294
+ /**
295
+ * Create a context state to be used during render.
296
+ *
297
+ * Note that to support the ValueOrUpdater pattern you can not use a
298
+ * function for V (but you may wrap it in an array or object).
299
+ *
300
+ * @experimental
301
+ * @__NO_SIDE_EFFECTS__
302
+ */
303
+ function createRenderState(name, getDefaultValue, isEqual) {
304
+ return createContextState(DOMRenderContextSymbol, name, getDefaultValue, isEqual);
305
+ }
306
+
307
+ /**
308
+ * Render context state that is true if the export was initiated from the root of the document.
309
+ * @experimental
310
+ */
311
+ const RenderContextRoot = createRenderState('root', Boolean);
312
+
313
+ /**
314
+ * Render context state that is true if this is an export operation ($generateHtmlFromNodes).
315
+ * @experimental
316
+ */
317
+ const RenderContextExport = createRenderState('isExport', Boolean);
318
+ function getDefaultRenderContext(editor) {
319
+ const dep = getPeerDependencyFromEditor(editor, DOMRenderExtensionName);
320
+ return dep ? dep.output.defaults : undefined;
321
+ }
322
+ function getRenderContext(editor) {
323
+ return getContextRecord(DOMRenderContextSymbol, editor) || getDefaultRenderContext(editor);
324
+ }
325
+
326
+ /**
327
+ * Get a render context value during a DOM render or export operation.
328
+ * @experimental
329
+ */
330
+ function $getRenderContextValue(cfg, editor = $getEditor()) {
331
+ return getContextValue(getRenderContext(editor), cfg);
332
+ }
333
+ function getRuntime(editor) {
334
+ const dep = getPeerDependencyFromEditor(editor, DOMRenderExtensionName);
335
+ return dep ? dep.output.runtime : undefined;
336
+ }
337
+
338
+ /**
339
+ * Imperatively set a value in the persistent editor render context.
340
+ *
341
+ * Unlike {@link $withRenderContext} (which scopes values to a callback), this
342
+ * persists on the editor. If the change flips any override's
343
+ * `disabledForEditor` result, the resident render config is recompiled and the
344
+ * affected nodes are re-rendered. No-op if {@link DOMRenderExtension} is not
345
+ * installed.
346
+ *
347
+ * @experimental
348
+ */
349
+ function $setRenderContextValue(cfg, value, editor = $getEditor()) {
350
+ const runtime = getRuntime(editor);
351
+ if (runtime) {
352
+ runtime.setContextValue(cfg, value);
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Imperatively update a value in the persistent editor render context with an
358
+ * updater function. See {@link $setRenderContextValue}.
359
+ *
360
+ * @experimental
361
+ */
362
+ function $updateRenderContextValue(cfg, updater, editor = $getEditor()) {
363
+ const runtime = getRuntime(editor);
364
+ if (runtime) {
365
+ runtime.setContextValue(cfg, updater(getContextValue(runtime.editorContext, cfg)));
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Resolve the {@link EditorDOMRenderConfig} to use for the current
371
+ * export/generate session, applying any `disabledForSession` overrides against
372
+ * the active session context. Falls back to the editor's resident config when
373
+ * {@link DOMRenderExtension} is not installed.
374
+ *
375
+ * @experimental
376
+ */
377
+ function $getSessionDOMRenderConfig(editor = $getEditor()) {
378
+ const runtime = getRuntime(editor);
379
+ return runtime ? runtime.getSessionConfig() : $getEditorDOMRenderConfig(editor);
380
+ }
381
+
382
+ /**
383
+ * Execute a callback within a render context with the given config pairs.
384
+ * @experimental
385
+ */
386
+ const $withRenderContext = $withContext(DOMRenderContextSymbol, getDefaultRenderContext);
387
+
388
+ /**
389
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
390
+ *
391
+ * This source code is licensed under the MIT license found in the
392
+ * LICENSE file in the root directory of this source tree.
393
+ *
394
+ */
395
+
396
+ /**
397
+ * A convenience function for type inference when constructing DOM overrides for
398
+ * use with {@link DOMRenderExtension}.
399
+ *
400
+ * The optional `options` argument controls *whether* the override is installed
401
+ * based only on render context — `disabledForEditor` gates residency in the
402
+ * editor's render pipeline (reconciliation), `disabledForSession` gates
403
+ * participation in a single export/generate session. See {@link DOMOverrideOptions}.
404
+ *
405
+ * @experimental
406
+ * @__NO_SIDE_EFFECTS__
407
+ */
408
+
409
+ function domOverride(nodes, config, options) {
410
+ return {
411
+ ...config,
412
+ ...options,
413
+ nodes
414
+ };
415
+ }
416
+
417
+ function buildTypeTree(editorConfig) {
418
+ const t = {};
419
+ const {
420
+ nodes
421
+ } = getKnownTypesAndNodes(editorConfig);
422
+ for (const klass of nodes) {
423
+ const type = klass.getType();
424
+ t[type] = {
425
+ klass,
426
+ types: {}
427
+ };
428
+ }
429
+ for (const baseRec of Object.values(t)) {
430
+ if (baseRec) {
431
+ const baseType = baseRec.klass.getType();
432
+ for (let {
433
+ klass
434
+ } = baseRec; $isLexicalNode(klass.prototype); klass = Object.getPrototypeOf(klass)) {
435
+ const {
436
+ ownNodeType
437
+ } = getStaticNodeConfig(klass);
438
+ const superRec = ownNodeType && t[ownNodeType];
439
+ if (superRec) {
440
+ superRec.types[baseType] = true;
441
+ }
442
+ }
443
+ }
444
+ }
445
+ return t;
446
+ }
447
+ function buildNodePredicate(klass) {
448
+ return node => node instanceof klass;
449
+ }
450
+ function getPredicate(typeTree, {
451
+ nodes
452
+ }) {
453
+ if (nodes === '*') {
454
+ return ALWAYS_TRUE;
455
+ }
456
+ let types = {};
457
+ const predicates = [];
458
+ for (const klassOrPredicate of nodes) {
459
+ if ('getType' in klassOrPredicate) {
460
+ const type = klassOrPredicate.getType();
461
+ if (types) {
462
+ const tree = typeTree[type];
463
+ if (!(tree !== undefined)) {
464
+ formatDevErrorMessage(`Node class ${klassOrPredicate.name} with type ${type} not registered in editor`);
465
+ }
466
+ types = Object.assign(types, tree.types);
467
+ }
468
+ predicates.push(buildNodePredicate(klassOrPredicate));
469
+ } else {
470
+ types = undefined;
471
+ predicates.push(klassOrPredicate);
472
+ }
473
+ }
474
+ if (types) {
475
+ return types;
476
+ } else if (predicates.length === 1) {
477
+ return predicates[0];
478
+ }
479
+ return node => {
480
+ for (const predicate of predicates) {
481
+ if (predicate(node)) {
482
+ return true;
483
+ }
484
+ }
485
+ return false;
486
+ };
487
+ }
488
+ function makePrerender() {
489
+ return {
490
+ $createDOM: [],
491
+ $decorateDOM: [],
492
+ $exportDOM: [],
493
+ $extractWithChild: [],
494
+ $getDOMSlot: [],
495
+ $shouldExclude: [],
496
+ $shouldInclude: [],
497
+ $updateDOM: []
498
+ };
499
+ }
500
+
501
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
502
+
503
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
504
+
505
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
506
+
507
+ function ignoreNext2(acc) {
508
+ return (node, _$next, editor) => acc(node, editor);
509
+ }
510
+ function ignoreNext3(acc) {
511
+ return (node, a, _$next, editor) => acc(node, a, editor);
512
+ }
513
+ function ignoreNext4(acc) {
514
+ return (node, a, b, _$next, editor) => acc(node, a, b, editor);
515
+ }
516
+ function ignoreNext5(acc) {
517
+ return (node, a, b, c, _$next, editor) => acc(node, a, b, c, editor);
518
+ }
519
+ function merge2($acc, $getOverride) {
520
+ return (node, editor) => {
521
+ const $next = () => $acc(node, editor);
522
+ const $override = $getOverride(node);
523
+ return $override ? $override(node, $next, editor) : $next();
524
+ };
525
+ }
526
+ function merge3(acc, $getOverride) {
527
+ return (node, a, editor) => {
528
+ const $next = () => acc(node, a, editor);
529
+ const $override = $getOverride(node);
530
+ return $override ? $override(node, a, $next, editor) : $next();
531
+ };
532
+ }
533
+ const merge3GetDOMSlot = merge3;
534
+ const ignoreNext3GetDOMSlot = ignoreNext3;
535
+ function merge4($acc, $getOverride) {
536
+ return (node, a, b, editor) => {
537
+ const $next = () => $acc(node, a, b, editor);
538
+ const $override = $getOverride(node);
539
+ return $override ? $override(node, a, b, $next, editor) : $next();
540
+ };
541
+ }
542
+ function merge5(acc, $getOverride) {
543
+ return (node, a, b, c, editor) => {
544
+ const $next = () => acc(node, a, b, c, editor);
545
+ const $override = $getOverride(node);
546
+ return $override ? $override(node, a, b, c, $next, editor) : $next();
547
+ };
548
+ }
549
+ function sequence4($acc, $getOverride) {
550
+ return (node, a, b, editor) => {
551
+ $acc(node, a, b, editor);
552
+ const $override = $getOverride(node);
553
+ if ($override) {
554
+ $override(node, a, b, editor);
555
+ }
556
+ };
557
+ }
558
+ function compilePrerenderKey(prerender, k, defaults, mergeFunction, ignoreNextFunction) {
559
+ let acc = defaults[k];
560
+ for (const pair of prerender[k]) {
561
+ if (typeof pair[0] === 'function') {
562
+ const [$predicate, $override] = pair;
563
+ acc = mergeFunction(acc, node => $predicate(node) && $override || undefined);
564
+ } else {
565
+ const typeOverrides = pair[1];
566
+ const compiled = {};
567
+ for (const type in typeOverrides) {
568
+ const arr = typeOverrides[type];
569
+ if (arr) {
570
+ compiled[type] = arr.reduce(($acc, $override) => mergeFunction($acc, () => $override), acc);
571
+ }
572
+ }
573
+ acc = mergeFunction(acc, node => {
574
+ const f = compiled[node.getType()];
575
+ return f && ignoreNextFunction(f);
576
+ });
577
+ }
578
+ }
579
+ defaults[k] = acc;
580
+ }
581
+ function addOverride(prerender, k, predicateOrTypes, override) {
582
+ if (!override) {
583
+ return;
584
+ }
585
+ const arr = prerender[k];
586
+ if (typeof predicateOrTypes === 'function') {
587
+ arr.push([predicateOrTypes, override]);
588
+ } else {
589
+ const last = arr[arr.length - 1];
590
+ let types;
591
+ if (last && last[0] === 'types') {
592
+ types = last[1];
593
+ } else {
594
+ types = {};
595
+ arr.push(['types', types]);
596
+ }
597
+ for (const type in predicateOrTypes) {
598
+ const typeArr = types[type] || [];
599
+ types[type] = typeArr;
600
+ typeArr.push(override);
601
+ }
602
+ }
603
+ }
604
+ function isWildcard(override) {
605
+ return override.nodes === '*';
606
+ }
607
+ function sortedOverrides(overrides) {
608
+ const byWildcard = [];
609
+ const byPredicate = [];
610
+ const byNode = [];
611
+ for (const override of overrides) {
612
+ if (isWildcard(override)) {
613
+ byWildcard.push(override);
614
+ } else if (Array.isArray(override.nodes)) {
615
+ for (const klassOrPredicate of override.nodes) {
616
+ if ($isLexicalNode(klassOrPredicate.prototype)) {
617
+ byNode.push(override.nodes.length === 1 ? override : {
618
+ ...override,
619
+ nodes: [klassOrPredicate]
620
+ });
621
+ } else {
622
+ byPredicate.push(override.nodes.length === 1 ? override : {
623
+ ...override,
624
+ nodes: [klassOrPredicate]
625
+ });
626
+ }
627
+ }
628
+ }
629
+ }
630
+ const depths = new Map();
631
+ const depthOf = klass => {
632
+ let depth = depths.get(klass);
633
+ if (depth === undefined) {
634
+ depth = 0;
635
+ for (let k = klass; $isLexicalNode(k.prototype); k = Object.getPrototypeOf(k)) {
636
+ depth++;
637
+ }
638
+ depths.set(klass, depth);
639
+ }
640
+ return depth;
641
+ };
642
+ byNode.sort((a, b) => depthOf(a.nodes[0]) - depthOf(b.nodes[0]));
643
+ return [...byNode, ...byPredicate, ...byWildcard];
644
+ }
645
+ function precompileDOMRenderConfigOverrides(editorConfig, overrides) {
646
+ const typeTree = buildTypeTree(editorConfig);
647
+ const prerender = makePrerender();
648
+ for (const override of sortedOverrides(overrides)) {
649
+ const predicateOrTypes = getPredicate(typeTree, override);
650
+ for (const k_ in prerender) {
651
+ const k = k_;
652
+ addOverride(prerender, k, predicateOrTypes, override[k]);
653
+ }
654
+ }
655
+ return prerender;
656
+ }
657
+ function identity(v) {
658
+ return v;
659
+ }
660
+ function compileDOMRenderConfigOverrides(editorConfig, {
661
+ overrides
662
+ }) {
663
+ const prerender = precompileDOMRenderConfigOverrides(editorConfig, overrides);
664
+ const dom = {
665
+ ...DEFAULT_EDITOR_DOM_CONFIG,
666
+ ...editorConfig.dom
667
+ };
668
+ compilePrerenderKey(prerender, '$createDOM', dom, merge2, ignoreNext2);
669
+ compilePrerenderKey(prerender, '$exportDOM', dom, merge2, ignoreNext2);
670
+ compilePrerenderKey(prerender, '$extractWithChild', dom, merge5, ignoreNext5);
671
+ compilePrerenderKey(prerender, '$getDOMSlot', dom, merge3GetDOMSlot, ignoreNext3GetDOMSlot);
672
+ compilePrerenderKey(prerender, '$shouldExclude', dom, merge3, ignoreNext3);
673
+ compilePrerenderKey(prerender, '$shouldInclude', dom, merge3, ignoreNext3);
674
+ compilePrerenderKey(prerender, '$updateDOM', dom, merge4, ignoreNext4);
675
+ compilePrerenderKey(prerender, '$decorateDOM', dom, sequence4, identity);
676
+ return dom;
677
+ }
678
+
679
+ /**
680
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
681
+ *
682
+ * This source code is licensed under the MIT license found in the
683
+ * LICENSE file in the root directory of this source tree.
684
+ *
685
+ */
686
+
687
+ function makeReader(record) {
688
+ return {
689
+ get(cfg) {
690
+ return getContextValue(record, cfg);
691
+ }
692
+ };
693
+ }
694
+
695
+ /**
696
+ * The mutable, writable editor-level context record. Reads of a render state
697
+ * during reconciliation (and as the base layer of a session) fall through to
698
+ * this record, and it is the layer the `disabledForEditor` predicates read.
699
+ *
700
+ * @internal
701
+ */
702
+ function createEditorContextRecord(contextDefaults) {
703
+ const parent = Object.create(null);
704
+ return contextFromPairs(contextDefaults, parent) || parent;
705
+ }
706
+
707
+ /**
708
+ * Filter the configured overrides down to those that are resident in the
709
+ * editor's render config, removing any whose `disabledForEditor` predicate
710
+ * returns `true` for the given editor context.
711
+ *
712
+ * @internal
713
+ */
714
+ function filterEditorInstalled(overrides, record) {
715
+ const reader = makeReader(record);
716
+ return overrides.filter(o => !(o.disabledForEditor && o.disabledForEditor(reader)));
717
+ }
718
+ function sameOverrides(a, b) {
719
+ if (a.length !== b.length) {
720
+ return false;
721
+ }
722
+ for (let i = 0; i < a.length; i++) {
723
+ if (a[i] !== b[i]) {
724
+ return false;
725
+ }
726
+ }
727
+ return true;
728
+ }
729
+ function symmetricDiff(prev, next) {
730
+ const prevSet = new Set(prev);
731
+ const nextSet = new Set(next);
732
+ const changed = [];
733
+ for (const o of prev) {
734
+ if (!nextSet.has(o)) {
735
+ changed.push(o);
736
+ }
737
+ }
738
+ for (const o of next) {
739
+ if (!prevSet.has(o)) {
740
+ changed.push(o);
741
+ }
742
+ }
743
+ return changed;
744
+ }
745
+
746
+ /**
747
+ * Build a predicate matching the nodes an override targets — `'*'` matches
748
+ * everything, a node class matches by `instanceof`, and a guard is used as-is.
749
+ */
750
+ function nodeMatcher(o) {
751
+ if (o.nodes === '*') {
752
+ return () => true;
753
+ }
754
+ const matchers = o.nodes.map(match => {
755
+ const klass = match;
756
+ return $isLexicalNode(klass.prototype) ? node => node instanceof klass : match;
757
+ });
758
+ return node => matchers.some(f => f(node));
759
+ }
760
+
761
+ /**
762
+ * Build a predicate matching the nodes whose DOM must be recreated for the
763
+ * given override change, or `null` when no live re-render is needed.
764
+ *
765
+ * `$createDOM`/`$getDOMSlot` produce the element and slot, and `$decorateDOM`
766
+ * may add DOM that only a fresh `$createDOM` can revert — so toggling any of
767
+ * them recreates the affected nodes. `$updateDOM` is diff-driven and applies on
768
+ * the next node update, and export-only hooks ($exportDOM/$shouldInclude/…)
769
+ * don't touch the live DOM, so neither needs a re-render. Recreating every
770
+ * affected node is the simple, always-correct choice; toggles are rare, so the
771
+ * cost is acceptable and can be optimized later if needed.
772
+ */
773
+ function recreatePredicate(changed) {
774
+ const matchers = [];
775
+ for (const o of changed) {
776
+ if (o.$createDOM || o.$getDOMSlot || o.$decorateDOM) {
777
+ matchers.push(nodeMatcher(o));
778
+ }
779
+ }
780
+ return matchers.length === 0 ? null : node => matchers.some(f => f(node));
781
+ }
782
+
783
+ /**
784
+ * Per-editor runtime backing {@link DOMRenderExtension}'s conditional
785
+ * overrides and imperative editor context. See {@link DOMRenderRuntime}.
786
+ *
787
+ * @internal
788
+ */
789
+ class DOMRenderRuntimeImpl {
790
+ editor;
791
+ /**
792
+ * The `nodes` and base `dom` captured at `init` (before `dom` was
793
+ * overwritten with the compiled config) — the clean base for every recompile.
794
+ */
795
+ initialEditorConfig;
796
+ overrides;
797
+ editorContext;
798
+ hasSessionGates;
799
+ installed;
800
+
801
+ /** Memoized session configs keyed by the set of session-disabled overrides. */
802
+ sessionCache = new Map();
803
+ constructor(editor, initialEditorConfig, overrides, editorContext) {
804
+ this.editor = editor;
805
+ this.initialEditorConfig = initialEditorConfig;
806
+ this.overrides = overrides;
807
+ this.editorContext = editorContext;
808
+ this.installed = filterEditorInstalled(overrides, editorContext);
809
+ this.hasSessionGates = overrides.some(o => o.disabledForSession);
810
+ }
811
+ setContextValue(cfg, value) {
812
+ const prev = this.installed;
813
+ this.editorContext[cfg.key] = value;
814
+ const next = filterEditorInstalled(this.overrides, this.editorContext);
815
+ if (sameOverrides(prev, next)) {
816
+ return;
817
+ }
818
+ const changed = symmetricDiff(prev, next);
819
+ this.installed = next;
820
+ this.sessionCache.clear();
821
+ const dom = compileDOMRenderConfigOverrides(this.initialEditorConfig, {
822
+ overrides: next
823
+ });
824
+ this.editor._config.dom = dom;
825
+ const recreate = recreatePredicate(changed);
826
+ if (!recreate) {
827
+ // $updateDOM-only or export-only change: the recompiled config is enough.
828
+ return;
829
+ }
830
+
831
+ // Re-render through a full reconcile, which reuses the existing node
832
+ // instances (no node-map mutation, so no spurious mutation/collaboration
833
+ // changes). The affected nodes must be unmounted and recreated — the removed
834
+ // override may have produced or decorated DOM that only a fresh $createDOM
835
+ // reverts — so install a transient $updateDOM that reports a recreate for
836
+ // matching nodes.
837
+ //
838
+ // This mutates the (shared) active config, so the reconcile MUST run and
839
+ // finish synchronously before the original is restored on the next line —
840
+ // hence `discrete`, and hence this must not be called from within an
841
+ // editor.update (where the commit would defer). A deferred update would
842
+ // either restore the wrapper before the reconcile reads it (no recreate) or
843
+ // leave it armed across a window where an unrelated reconcile would
844
+ // spuriously recreate matching nodes. No history tag is needed: a full
845
+ // reconcile marks no nodes dirty, which history merges/discards without
846
+ // pushing.
847
+ const base = dom.$updateDOM;
848
+ dom.$updateDOM = (nextNode, prevNode, el, editor) => recreate(nextNode) ? true : base(nextNode, prevNode, el, editor);
849
+ this.editor.update($fullReconcile, {
850
+ discrete: true
851
+ });
852
+ dom.$updateDOM = base;
853
+ }
854
+ getSessionConfig() {
855
+ const resident = this.editor._config.dom || DEFAULT_EDITOR_DOM_CONFIG;
856
+ if (!this.hasSessionGates) {
857
+ return resident;
858
+ }
859
+ const reader = makeReader(getContextRecord(DOMRenderContextSymbol, this.editor) || this.editorContext);
860
+ const disabledKeys = [];
861
+ const sessionSet = [];
862
+ this.installed.forEach((o, i) => {
863
+ if (o.disabledForSession && o.disabledForSession(reader)) {
864
+ disabledKeys.push(String(i));
865
+ } else {
866
+ sessionSet.push(o);
867
+ }
868
+ });
869
+ if (disabledKeys.length === 0) {
870
+ return resident;
871
+ }
872
+ const key = disabledKeys.join(',');
873
+ let cfg = this.sessionCache.get(key);
874
+ if (!cfg) {
875
+ cfg = compileDOMRenderConfigOverrides(this.initialEditorConfig, {
876
+ overrides: sessionSet
877
+ });
878
+ this.sessionCache.set(key, cfg);
879
+ }
880
+ return cfg;
881
+ }
882
+ }
883
+
884
+ /**
885
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
886
+ *
887
+ * This source code is licensed under the MIT license found in the
888
+ * LICENSE file in the root directory of this source tree.
889
+ *
890
+ */
891
+
892
+
893
+ /** @internal The result returned from {@link DOMRenderExtension}'s `init`. */
894
+
895
+ /**
896
+ * @experimental
897
+ *
898
+ * An extension that allows overriding the render and export behavior for an
899
+ * editor. This is highly experimental and subject to change from one version
900
+ * to the next.
901
+ **/
902
+ const DOMRenderExtension = defineExtension({
903
+ build(editor, config, state) {
904
+ const {
905
+ initialEditorConfig
906
+ } = state.getInitResult();
907
+ const editorContext = createEditorContextRecord(config.contextDefaults);
908
+ const runtime = new DOMRenderRuntimeImpl(editor, initialEditorConfig, config.overrides, editorContext);
909
+ return {
910
+ defaults: editorContext,
911
+ runtime
912
+ };
913
+ },
914
+ config: {
915
+ contextDefaults: [],
916
+ overrides: []
917
+ },
918
+ html: {
919
+ // Define a RootNode export for $generateDOMFromRoot
920
+ export: new Map([[RootNode, () => {
921
+ const element = document.createElement('div');
922
+ element.role = 'textbox';
923
+ return {
924
+ element
925
+ };
926
+ }]])
927
+ },
928
+ init(editorConfig, config) {
929
+ // Capture the user's base `dom` (before we overwrite it) and `nodes` so the
930
+ // runtime can recompile from scratch when overrides toggle.
931
+ const initialEditorConfig = {
932
+ dom: editorConfig.dom,
933
+ nodes: editorConfig.nodes
934
+ };
935
+ const editorContext = createEditorContextRecord(config.contextDefaults);
936
+ const installed = filterEditorInstalled(config.overrides, editorContext);
937
+ editorConfig.dom = compileDOMRenderConfigOverrides(editorConfig, {
938
+ overrides: installed
939
+ });
940
+ return {
941
+ initialEditorConfig
942
+ };
943
+ },
944
+ mergeConfig(config, partial) {
945
+ const merged = shallowMergeConfig(config, partial);
946
+ for (const k of ['overrides', 'contextDefaults']) {
947
+ if (partial[k]) {
948
+ merged[k] = [...config[k], ...partial[k]];
949
+ }
950
+ }
951
+ return merged;
952
+ },
953
+ name: DOMRenderExtensionName
954
+ });
955
+
956
+ /**
957
+ * @internal
958
+ *
959
+ * A predicate that may write into the per-invocation `captures` map. Returns
960
+ * `true` if the rule matches; `false` otherwise.
961
+ */
962
+
963
+ /** @internal */
964
+
965
+ /** @internal The runtime shape of a {@link CompiledSelector}. */
966
+
967
+ const IMPL = Symbol.for('@lexical/html/SelectorImpl');
968
+
969
+ /** @internal */
970
+ function getSelectorImpl(sel) {
971
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
972
+ const impl = sel[IMPL];
973
+ if (!(impl !== undefined)) {
974
+ formatDevErrorMessage(`match must be a CompiledSelector produced by sel.* or sel.css(); received a raw object.`);
975
+ }
976
+ return impl;
977
+ }
978
+ function combinePredicates(preds) {
979
+ if (preds.length === 0) {
980
+ return isHTMLElement;
981
+ }
982
+ if (preds.length === 1) {
983
+ return preds[0];
984
+ }
985
+ return (node, captures) => {
986
+ for (const p of preds) {
987
+ if (!p(node, captures)) {
988
+ return false;
989
+ }
990
+ }
991
+ return true;
992
+ };
993
+ }
994
+
995
+ /**
996
+ * @internal
997
+ *
998
+ * Build a selector value from a tag set and a predicate list. Used by the
999
+ * combinator API and the CSS parser.
1000
+ */
1001
+ function buildSelector(tags, predicates) {
1002
+ const impl = {
1003
+ kind: 'element',
1004
+ predicate: combinePredicates(predicates),
1005
+ tags
1006
+ };
1007
+ const refine = additional => buildSelector(tags, [...predicates, additional]);
1008
+ const builder = {
1009
+ [IMPL]: impl,
1010
+ attr: (name, value, options) => refine(buildAttrPredicate(name, value, options)),
1011
+ classAll: (...classes) => refine(buildClassAllPredicate(classes)),
1012
+ classAny: (...classes) => refine(buildClassAnyPredicate(classes)),
1013
+ styleAny: (prop, value, options) => refine(buildStylePredicate(prop, value, options))
1014
+ };
1015
+ // The runtime is fully type-erased; cast to satisfy the surface.
1016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1017
+ return builder;
1018
+ }
1019
+ function normalizeClassList(classes) {
1020
+ const out = [];
1021
+ for (const c of classes) {
1022
+ if (c) {
1023
+ out.push(c);
1024
+ }
1025
+ }
1026
+ return out;
1027
+ }
1028
+
1029
+ /** @internal */
1030
+ function buildClassAllPredicate(classes) {
1031
+ const ns = normalizeClassList(classes);
1032
+ if (ns.length === 0) {
1033
+ return () => true;
1034
+ }
1035
+ return node => {
1036
+ if (!isHTMLElement(node)) {
1037
+ return false;
1038
+ }
1039
+ const cl = node.classList;
1040
+ for (const c of ns) {
1041
+ if (!cl.contains(c)) {
1042
+ return false;
1043
+ }
1044
+ }
1045
+ return true;
1046
+ };
1047
+ }
1048
+
1049
+ /** @internal */
1050
+ function buildClassAnyPredicate(classes) {
1051
+ const ns = normalizeClassList(classes);
1052
+ if (ns.length === 0) {
1053
+ return () => false;
1054
+ }
1055
+ return node => {
1056
+ if (!isHTMLElement(node)) {
1057
+ return false;
1058
+ }
1059
+ const cl = node.classList;
1060
+ for (const c of ns) {
1061
+ if (cl.contains(c)) {
1062
+ return true;
1063
+ }
1064
+ }
1065
+ return false;
1066
+ };
1067
+ }
1068
+
1069
+ /** @internal */
1070
+ function buildAttrPredicate(name, value, options) {
1071
+ if (value === true) {
1072
+ return node => isHTMLElement(node) && node.hasAttribute(name);
1073
+ }
1074
+ if (typeof value === 'string') {
1075
+ return node => isHTMLElement(node) && node.getAttribute(name) === value;
1076
+ }
1077
+ if (value instanceof RegExp) {
1078
+ const capture = options && options.capture;
1079
+ const re = value;
1080
+ return (node, captures) => {
1081
+ if (!isHTMLElement(node)) {
1082
+ return false;
1083
+ }
1084
+ const v = node.getAttribute(name);
1085
+ if (v == null) {
1086
+ return false;
1087
+ }
1088
+ const m = v.match(re);
1089
+ if (m === null) {
1090
+ return false;
1091
+ }
1092
+ if (capture !== undefined) {
1093
+ captures[capture] = m;
1094
+ }
1095
+ return true;
1096
+ };
1097
+ }
1098
+ {
1099
+ formatDevErrorMessage(`sel.attr(${JSON.stringify(name)}, ...) requires true, a string, or a RegExp`);
1100
+ }
1101
+ }
1102
+ function buildStylePredicate(prop, value, options) {
1103
+ if (typeof value === 'string') {
1104
+ return node => isHTMLElement(node) && node.style.getPropertyValue(prop) === value;
1105
+ }
1106
+ if (value instanceof RegExp) {
1107
+ const capture = options && options.capture;
1108
+ const re = value;
1109
+ return (node, captures) => {
1110
+ if (!isHTMLElement(node)) {
1111
+ return false;
1112
+ }
1113
+ const v = node.style.getPropertyValue(prop);
1114
+ if (!v) {
1115
+ return false;
1116
+ }
1117
+ const m = v.match(re);
1118
+ if (m === null) {
1119
+ return false;
1120
+ }
1121
+ if (capture !== undefined) {
1122
+ captures[capture] = m;
1123
+ }
1124
+ return true;
1125
+ };
1126
+ }
1127
+ {
1128
+ formatDevErrorMessage(`sel.styleAny(${JSON.stringify(prop)}, ...) requires a string or a RegExp`);
1129
+ }
1130
+ }
1131
+ const TEXT_SELECTOR_IMPL = {
1132
+ kind: 'text',
1133
+ predicate: isDOMTextNode,
1134
+ tags: new Set()
1135
+ };
1136
+
1137
+ // The `as` cast is needed because `CompiledSelector` is an opaque
1138
+ // branded interface — neither the object literal nor a typed const can
1139
+ // declare the internal `IMPL` symbol without exposing it.
1140
+ const TEXT_SELECTOR = {
1141
+ [IMPL]: TEXT_SELECTOR_IMPL
1142
+ };
1143
+ const COMMENT_SELECTOR_IMPL = {
1144
+ kind: 'comment',
1145
+ predicate: node => node.nodeType === 8 /* COMMENT_NODE */,
1146
+ tags: new Set()
1147
+ };
1148
+ const COMMENT_SELECTOR = {
1149
+ [IMPL]: COMMENT_SELECTOR_IMPL
1150
+ };
1151
+
1152
+ /**
1153
+ * Combinator API for building {@link CompiledSelector}s. The public
1154
+ * `sel` is augmented from this in `./index.ts` (where the CSS parser is
1155
+ * available without a circular import); consumers outside `@lexical/html`
1156
+ * should always import the public `sel` from the package root.
1157
+ *
1158
+ * @internal
1159
+ */
1160
+ const selBase = {
1161
+ /** Match any {@link HTMLElement}. */
1162
+ any() {
1163
+ return buildSelector(new Set(), []);
1164
+ },
1165
+ /** Match DOM {@link Comment} nodes. */
1166
+ comment() {
1167
+ return COMMENT_SELECTOR;
1168
+ },
1169
+ /**
1170
+ * Match by tag name(s). With one literal tag the element type is narrowed
1171
+ * (e.g. `'a' → HTMLAnchorElement`); with multiple, it is the union of
1172
+ * their `HTMLElementTagNameMap` entries.
1173
+ */
1174
+ tag(...tags) {
1175
+ if (!(tags.length > 0)) {
1176
+ formatDevErrorMessage(`sel.tag() requires at least one tag name`);
1177
+ }
1178
+ const upper = new Set();
1179
+ for (const t of tags) {
1180
+ upper.add(t.toUpperCase());
1181
+ }
1182
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1183
+ return buildSelector(upper, []);
1184
+ },
1185
+ /** Match DOM {@link Text} nodes. */
1186
+ text() {
1187
+ return TEXT_SELECTOR;
1188
+ }
1189
+ };
1190
+
1191
+ /**
1192
+ * Cross-frame-safe replacement for `node instanceof HTMLXxxElement`. Returns
1193
+ * true when `node` is an HTMLElement whose `nodeName` equals `tag` (compared
1194
+ * case-insensitively).
1195
+ *
1196
+ * @experimental
1197
+ */
1198
+ function isElementOfTag(node, tag) {
1199
+ return isHTMLElement(node) && node.nodeName === tag.toUpperCase();
1200
+ }
1201
+
1202
+ const IDENT_CHAR = /[A-Za-z0-9_-]/;
1203
+ class Cursor {
1204
+ constructor(source, pos) {
1205
+ this.source = source;
1206
+ this.pos = pos;
1207
+ }
1208
+ peek(offset = 0) {
1209
+ return this.source[this.pos + offset] || '';
1210
+ }
1211
+ consume() {
1212
+ return this.source[this.pos++] || '';
1213
+ }
1214
+ eof() {
1215
+ return this.pos >= this.source.length;
1216
+ }
1217
+ skipWhitespace() {
1218
+ while (!this.eof() && /\s/.test(this.peek())) {
1219
+ this.pos++;
1220
+ }
1221
+ }
1222
+ readIdent() {
1223
+ const start = this.pos;
1224
+ while (!this.eof() && IDENT_CHAR.test(this.peek())) {
1225
+ this.pos++;
1226
+ }
1227
+ return this.source.slice(start, this.pos);
1228
+ }
1229
+ readQuoted() {
1230
+ const quote = this.consume();
1231
+ this.assert(quote === '"' || quote === "'", 'expected quote');
1232
+ const start = this.pos;
1233
+ while (!this.eof() && this.peek() !== quote) {
1234
+ if (this.peek() === '\\') {
1235
+ this.pos += 2;
1236
+ } else {
1237
+ this.pos++;
1238
+ }
1239
+ }
1240
+ this.assert(!this.eof(), 'unterminated string');
1241
+ const value = this.source.slice(start, this.pos);
1242
+ this.pos++; // consume closing quote
1243
+ return value.replace(/\\(.)/g, '$1');
1244
+ }
1245
+ /**
1246
+ * `invariant(cond, fmt, …)`-flavored assertion that also surfaces the
1247
+ * cursor's position context. Use for parse-time errors so a malformed
1248
+ * CSS selector gets a useful, position-annotated message.
1249
+ */
1250
+ assert(cond, msg) {
1251
+ if (!cond) {
1252
+ formatDevErrorMessage(`invalid CSS selector at col ${String(this.pos + 1)}: ${msg} in ${this.source}`);
1253
+ }
1254
+ }
1255
+ }
1256
+ function parseSimpleSelector(c) {
1257
+ const tags = new Set();
1258
+ const predicates = [];
1259
+ const classes = [];
1260
+ c.skipWhitespace();
1261
+
1262
+ // Optional tag or '*'
1263
+ if (c.peek() === '*') {
1264
+ c.consume();
1265
+ } else if (IDENT_CHAR.test(c.peek())) {
1266
+ const tag = c.readIdent();
1267
+ if (tag) {
1268
+ tags.add(tag.toUpperCase());
1269
+ }
1270
+ }
1271
+
1272
+ // Zero or more refinements: .class, #id, [attr]
1273
+ while (!c.eof()) {
1274
+ const ch = c.peek();
1275
+ if (ch === '.') {
1276
+ c.consume();
1277
+ const cls = c.readIdent();
1278
+ c.assert(cls !== '', 'expected class name after "."');
1279
+ classes.push(cls);
1280
+ } else if (ch === '#') {
1281
+ c.consume();
1282
+ const id = c.readIdent();
1283
+ c.assert(id !== '', 'expected id after "#"');
1284
+ predicates.push(buildAttrPredicate('id', id));
1285
+ } else if (ch === '[') {
1286
+ c.consume();
1287
+ c.skipWhitespace();
1288
+ const name = c.readIdent();
1289
+ c.assert(name !== '', 'expected attribute name after "["');
1290
+ c.skipWhitespace();
1291
+ let value = true;
1292
+ if (c.peek() === '=') {
1293
+ c.consume();
1294
+ c.skipWhitespace();
1295
+ const next = c.peek();
1296
+ if (next === '"' || next === "'") {
1297
+ value = c.readQuoted();
1298
+ } else {
1299
+ value = c.readIdent();
1300
+ c.assert(value !== '', 'expected attribute value');
1301
+ }
1302
+ c.skipWhitespace();
1303
+ }
1304
+ c.assert(c.peek() === ']', 'expected "]"');
1305
+ c.consume();
1306
+ predicates.push(buildAttrPredicate(name, value));
1307
+ } else {
1308
+ break;
1309
+ }
1310
+ }
1311
+ if (classes.length > 0) {
1312
+ predicates.push(buildClassAllPredicate(classes));
1313
+ }
1314
+ return {
1315
+ predicates,
1316
+ tags
1317
+ };
1318
+ }
1319
+
1320
+ /**
1321
+ * Parse a reduced CSS-selector subset and return a {@link CompiledSelector}.
1322
+ * Supported:
1323
+ * - Tag (`p`), wildcard (`*`).
1324
+ * - Tag list (`h1, h2, h3`).
1325
+ * - Class (`.foo`, `.foo.bar`).
1326
+ * - ID (`#foo`).
1327
+ * - Attribute presence (`[name]`).
1328
+ * - Attribute equality (`[name="value"]`, `[name=value]`).
1329
+ *
1330
+ * Anything outside the subset (regex attribute, inline-style match,
1331
+ * combinators, pseudo-classes) is intentionally rejected — chain combinator
1332
+ * methods off the returned builder instead.
1333
+ *
1334
+ * @experimental
1335
+ */
1336
+ function parseSelector(source) {
1337
+ const c = new Cursor(source, 0);
1338
+ const groups = [];
1339
+ while (true) {
1340
+ const group = parseSimpleSelector(c);
1341
+ groups.push(group);
1342
+ c.skipWhitespace();
1343
+ if (c.eof()) {
1344
+ break;
1345
+ }
1346
+ c.assert(c.peek() === ',', 'expected "," (selector lists are the only supported combinator)');
1347
+ c.consume();
1348
+ c.skipWhitespace();
1349
+ }
1350
+ if (groups.length === 1) {
1351
+ return buildSelector(groups[0].tags, groups[0].predicates);
1352
+ }
1353
+
1354
+ // Comma-separated list. Merge tag sets and OR-combine the per-group
1355
+ // refinement predicates so that each candidate node satisfies *some*
1356
+ // group entirely.
1357
+ const tags = new Set();
1358
+ for (const g of groups) {
1359
+ for (const t of g.tags) {
1360
+ tags.add(t);
1361
+ }
1362
+ }
1363
+ const orPredicate = (node, captures) => {
1364
+ for (const g of groups) {
1365
+ const upper = node.nodeName;
1366
+ if (g.tags.size > 0 && !g.tags.has(upper)) {
1367
+ continue;
1368
+ }
1369
+ let ok = true;
1370
+ for (const p of g.predicates) {
1371
+ if (!p(node, captures)) {
1372
+ ok = false;
1373
+ break;
1374
+ }
1375
+ }
1376
+ if (ok) {
1377
+ return true;
1378
+ }
1379
+ }
1380
+ return false;
1381
+ };
1382
+ return buildSelector(tags, [orPredicate]);
1383
+ }
1384
+
1385
+ /**
1386
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1387
+ *
1388
+ * This source code is licensed under the MIT license found in the
1389
+ * LICENSE file in the root directory of this source tree.
1390
+ *
1391
+ */
1392
+
1393
+ /**
1394
+ * Identity helper that infers a rule's matched node type and capture map
1395
+ * from its `match` selector and threads them into the `$import` signature.
1396
+ * Usage:
1397
+ *
1398
+ * ```ts
1399
+ * defineImportRule({
1400
+ * name: '@lexical/list/li',
1401
+ * match: sel.tag('li'),
1402
+ * $import: (ctx, el, $next) => {
1403
+ * // el: HTMLLIElement
1404
+ * return [$createListItemNode()];
1405
+ * },
1406
+ * });
1407
+ * ```
1408
+ *
1409
+ * @experimental
1410
+ * @__NO_SIDE_EFFECTS__
1411
+ */
1412
+ function defineImportRule(rule) {
1413
+ return rule;
1414
+ }
1415
+
1416
+ /**
1417
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1418
+ *
1419
+ * This source code is licensed under the MIT license found in the
1420
+ * LICENSE file in the root directory of this source tree.
1421
+ *
1422
+ */
1423
+
1424
+ /**
1425
+ * Create an import context state. The phantom symbol prevents accidental
1426
+ * use of a render-context state in an import context (and vice versa).
1427
+ *
1428
+ * Note: to support the value-or-updater pattern, `V` cannot be a function
1429
+ * type; wrap it in an array or object if needed.
1430
+ *
1431
+ * `getDefaultValue` is called **once at state creation** and the result is
1432
+ * shared between every session that reads the state without first writing
1433
+ * a value. Defaults must therefore be immutable (primitives, frozen
1434
+ * objects, or read-only arrays / records). If your state needs mutable
1435
+ * per-session storage, lazily initialize it inside your rule (e.g.
1436
+ * `if (!ctx.session.has(cfg)) ctx.session.set(cfg, new …())`).
1437
+ *
1438
+ * @experimental
1439
+ * @__NO_SIDE_EFFECTS__
1440
+ */
1441
+ function createImportState(name, getDefaultValue, isEqual) {
1442
+ return createContextState(DOMImportContextSymbol, name, getDefaultValue, isEqual);
1443
+ }
1444
+
1445
+ /**
1446
+ * The kind of operation that produced this import. Lets rules adapt
1447
+ * their behavior (e.g. preserve more whitespace on `'paste'`).
1448
+ * Defaults to `'unknown'`. Apps that need a different vocabulary can
1449
+ * define their own {@link ImportStateConfig} with whatever value type
1450
+ * they want.
1451
+ *
1452
+ * @experimental
1453
+ */
1454
+
1455
+ /**
1456
+ * Built-in import-context state identifying how this import was initiated.
1457
+ * Callers of `$generateNodesFromDOM` should set it via the `context` option.
1458
+ *
1459
+ * @experimental
1460
+ */
1461
+ const ImportSource = createImportState('importSource', () => 'unknown');
1462
+
1463
+ /**
1464
+ * Built-in import-context state holding the {@link DataTransfer} the
1465
+ * import was sourced from, if any. `null` outside paste/drop flows.
1466
+ *
1467
+ * The clipboard import pipeline passes the original `DataTransfer`
1468
+ * through to its per-MIME-type handler stack (see
1469
+ * {@link ImportMimeTypeFunction}); handlers that route HTML through
1470
+ * the {@link DOMImportExtension} pipeline should forward it into the
1471
+ * walk via `context: [contextValue(ImportSourceDataTransfer,
1472
+ * dataTransfer)]` so rules and preprocessors can call
1473
+ * `ctx.get(ImportSourceDataTransfer)` to inspect companion MIME types
1474
+ * (e.g. an `'application/rtf'` alternative or an attached
1475
+ * `'application/x-officedrawing'` payload), the file list, or any
1476
+ * custom drag-and-drop slot.
1477
+ *
1478
+ * Use sparingly: the safer pattern is to decide *which* MIME-type
1479
+ * payload to walk in the clipboard handler stack and hand a finalized
1480
+ * DOM to the rules; only fall back to peeking at `ImportSourceDataTransfer`
1481
+ * when the source-detection signal genuinely lives in a companion
1482
+ * slot.
1483
+ *
1484
+ * @experimental
1485
+ */
1486
+ const ImportSourceDataTransfer = createImportState('importSourceDataTransfer', () => null);
1487
+
1488
+ /**
1489
+ * Built-in import-context state holding the bit-packed
1490
+ * {@link TextFormatType} formats that should apply to {@link TextNode}s
1491
+ * produced during the current subtree. Used by inline-format wrappers
1492
+ * (`<b>`, `<i>`, `<u>`, …) to propagate formatting through the context
1493
+ * record instead of via the legacy `forChild` chain.
1494
+ *
1495
+ * @experimental
1496
+ */
1497
+ const ImportTextFormat = createImportState('textFormat', () => 0);
1498
+
1499
+ /**
1500
+ * Built-in import-context state holding a parsed CSS-style record
1501
+ * (the {@link getStyleObjectFromCSS} shape) that should apply to
1502
+ * {@link TextNode}s produced during the current subtree. Mirrors the
1503
+ * format-bit propagation in {@link ImportTextFormat} for properties
1504
+ * that don't fit into the format bit mask — `color`, `font-family`,
1505
+ * `font-size`, etc.
1506
+ *
1507
+ * Ancestor rules that contribute a style branch the context with a
1508
+ * merged record; the core `#text` rule materializes the non-empty
1509
+ * record to a CSS string and calls `setStyle` on the new TextNode.
1510
+ * Once TextNode adopts a parsed style record, the materialization
1511
+ * step will go away.
1512
+ *
1513
+ * @experimental
1514
+ */
1515
+ const ImportTextStyle = createImportState('textStyle', () => ({}));
1516
+
1517
+ /**
1518
+ * Determines whether a given DOM element should be treated as preserving
1519
+ * whitespace (i.e. text content under it is not collapsed and is split on
1520
+ * `\n` / `\t` into `LineBreakNode` / `TabNode`). The default matches the
1521
+ * legacy behavior: the element itself is `<pre>` or its inline
1522
+ * `white-space` style begins with `'pre'`.
1523
+ *
1524
+ * @experimental
1525
+ */
1526
+
1527
+ /**
1528
+ * Determines whether a given DOM node sits on the same visual line as its
1529
+ * adjacent text siblings, governing whether leading/trailing whitespace in
1530
+ * a `#text` is collapsed against neighbors. The default consults
1531
+ * {@link isInlineDomNode} from `lexical` (style.display or a fixed inline
1532
+ * tag-name set) and additionally treats elements with an explicit
1533
+ * non-inline `display` style as block.
1534
+ *
1535
+ * @experimental
1536
+ */
1537
+
1538
+ /**
1539
+ * Configuration for the core text whitespace-collapse logic. Override via
1540
+ * {@link ImportWhitespaceConfig} either as a `contextDefaults` entry on
1541
+ * the {@link DOMImportExtension} or per-call on `$generateNodesFromDOM`'s
1542
+ * `context` option.
1543
+ *
1544
+ * @experimental
1545
+ */
1546
+
1547
+ /**
1548
+ * Default {@link WhitespaceImportConfig.preservesWhitespace}: matches
1549
+ * `<pre>` and any element with `white-space: pre*`.
1550
+ *
1551
+ * @experimental
1552
+ */
1553
+ function defaultPreservesWhitespace(node) {
1554
+ if (!isHTMLElement(node)) {
1555
+ return false;
1556
+ }
1557
+ if (node.nodeName === 'PRE') {
1558
+ return true;
1559
+ }
1560
+ const ws = node.style.whiteSpace;
1561
+ return typeof ws === 'string' && ws.startsWith('pre');
1562
+ }
1563
+
1564
+ /**
1565
+ * Default {@link WhitespaceImportConfig.isInline}: treats an element as
1566
+ * inline iff its inline `display` style is `inline*` OR (no explicit
1567
+ * non-inline display) its nodeName is a known inline tag (`isInlineDomNode`).
1568
+ * Text nodes are always inline; comments and other non-elements are not.
1569
+ *
1570
+ * @experimental
1571
+ */
1572
+ function defaultIsInline(node) {
1573
+ if (isDOMTextNode(node)) {
1574
+ return true;
1575
+ }
1576
+ if (!isHTMLElement(node)) {
1577
+ return false;
1578
+ }
1579
+ const display = node.style.display;
1580
+ if (display) {
1581
+ return display.startsWith('inline');
1582
+ }
1583
+ if (isBlockDomNode(node)) {
1584
+ return false;
1585
+ }
1586
+ return isInlineDomNode(node);
1587
+ }
1588
+
1589
+ /**
1590
+ * Built-in import-context state controlling text-node whitespace handling
1591
+ * (collapse vs. preserve, what counts as an inline sibling). Override per
1592
+ * editor via {@link DOMImportConfig.contextDefaults} or per call via
1593
+ * {@link GenerateNodesFromDOMOptions.context}.
1594
+ *
1595
+ * @experimental
1596
+ */
1597
+ const ImportWhitespaceConfig = createImportState('whitespaceConfig', () => ({
1598
+ isInline: defaultIsInline,
1599
+ preservesWhitespace: defaultPreservesWhitespace
1600
+ }));
1601
+
1602
+ /**
1603
+ * Built-in session slot for runtime overlay rules that should be in
1604
+ * effect for the entire walk. A preprocessor writes here when it wants
1605
+ * to conditionally install handling for a particular paste source
1606
+ * (e.g. "if the Microsoft Word generator meta tag is present, push the
1607
+ * Word-paste overlay"). Each entry contributes an overlay dispatcher
1608
+ * to the runtime's overlay stack; later array entries are higher
1609
+ * priority. Use `ctx.session.update(ImportOverlays, prev => […])` to
1610
+ * append.
1611
+ *
1612
+ * This is the walk-wide counterpart to
1613
+ * `$importChildren({rules: …})` (which scopes an overlay to one
1614
+ * subtree): write to {@link ImportOverlays} when the overlay should
1615
+ * apply for the whole document; use `$importChildren`'s `rules` when
1616
+ * the overlay should only apply for a deeper region.
1617
+ *
1618
+ * @experimental
1619
+ */
1620
+ const ImportOverlays = createImportState('importOverlays', () => []);
1621
+
1622
+ /**
1623
+ * The session IS the root-layer {@link ContextRecord} of the walk. Reads
1624
+ * fall through the prototype chain to the editor's `contextDefaults`,
1625
+ * writes mutate the record's own properties, and any branch pushed by
1626
+ * `$importChildren({context})` sits above this layer and can shadow
1627
+ * (but does not overwrite) slots.
1628
+ *
1629
+ * @internal
1630
+ */
1631
+ class ImportSessionImpl {
1632
+ constructor(record) {
1633
+ this.record = record;
1634
+ }
1635
+ get(cfg) {
1636
+ return getContextValue(this.record, cfg);
1637
+ }
1638
+ set(cfg, value) {
1639
+ this.record[cfg.key] = value;
1640
+ }
1641
+ update(cfg, updater) {
1642
+ this.record[cfg.key] = updater(getContextValue(this.record, cfg));
1643
+ }
1644
+ has(cfg) {
1645
+ return Object.prototype.hasOwnProperty.call(this.record, cfg.key);
1646
+ }
1647
+ }
1648
+ function getDefaultImportContext(editor) {
1649
+ const dep = getPeerDependencyFromEditor(editor, DOMImportExtensionName);
1650
+ return dep ? dep.output.defaults : undefined;
1651
+ }
1652
+ function getImportContext(editor) {
1653
+ return getContextRecord(DOMImportContextSymbol, editor) || getDefaultImportContext(editor);
1654
+ }
1655
+
1656
+ /**
1657
+ * Read an import context value during an import operation.
1658
+ * @experimental
1659
+ */
1660
+ function $getImportContextValue(cfg, editor = $getEditor()) {
1661
+ return getContextValue(getImportContext(editor), cfg);
1662
+ }
1663
+
1664
+ /**
1665
+ * Run `f` with the given context pairs applied on top of the editor's
1666
+ * current import context.
1667
+ *
1668
+ * @experimental
1669
+ */
1670
+ const $withImportContext = $withContext(DOMImportContextSymbol, getDefaultImportContext);
1671
+
1672
+ /**
1673
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1674
+ *
1675
+ * This source code is licensed under the MIT license found in the
1676
+ * LICENSE file in the root directory of this source tree.
1677
+ *
1678
+ */
1679
+ const sel$1 = selBase;
1680
+ const ALIGNMENT_VALUES = new Set(['center', 'end', 'justify', 'left', 'right', 'start']);
1681
+
1682
+ /**
1683
+ * True if `value` is a non-empty {@link ElementFormatType} (matches one of
1684
+ * the supported `text-align` / legacy `align`-attribute values).
1685
+ *
1686
+ * @internal
1687
+ */
1688
+ function isAlignmentValue(value) {
1689
+ return ALIGNMENT_VALUES.has(value);
1690
+ }
1691
+
1692
+ /**
1693
+ * A pair of bitmasks describing which {@link TextFormatType} bits to set
1694
+ * and which to clear when descending into an element. The clear pass
1695
+ * matters for cases the legacy OR-merge mishandled, e.g. `<b
1696
+ * style="font-weight: normal">` clearing an inherited bold, or `<sub>` /
1697
+ * `<sup>` clearing each other.
1698
+ */
1699
+
1700
+ /**
1701
+ * The small subset of inline-style properties that affect text formatting
1702
+ * during import. Modeled as a plain object so tag-implicit defaults and
1703
+ * the element's own inline `style` can be merged with `{...defaults,
1704
+ * ...override-if-set}` semantics rather than relying on CSSStyleDeclaration.
1705
+ */
1706
+
1707
+ /**
1708
+ * Default style implied by each inline format tag. `<b>`/`<strong>` set
1709
+ * font-weight, `<sub>` sets vertical-align, etc. Any of these can be
1710
+ * overridden by the element's own inline `style` (so `<b
1711
+ * style="font-weight: normal">` ends up with `fontWeight: 'normal'` in
1712
+ * the effective style).
1713
+ */
1714
+ const TAG_DEFAULT_STYLE = {
1715
+ B: {
1716
+ fontWeight: 'bold'
1717
+ },
1718
+ EM: {
1719
+ fontStyle: 'italic'
1720
+ },
1721
+ I: {
1722
+ fontStyle: 'italic'
1723
+ },
1724
+ S: {
1725
+ textDecoration: 'line-through'
1726
+ },
1727
+ STRONG: {
1728
+ fontWeight: 'bold'
1729
+ },
1730
+ SUB: {
1731
+ verticalAlign: 'sub'
1732
+ },
1733
+ SUP: {
1734
+ verticalAlign: 'super'
1735
+ },
1736
+ U: {
1737
+ textDecoration: 'underline'
1738
+ }
1739
+ };
1740
+
1741
+ /**
1742
+ * Tags whose effect on TextFormat has no CSS analog (so the style-merge
1743
+ * path can't reach them). Applied as a pure "set" override.
1744
+ */
1745
+ const TAG_ONLY_SET = {
1746
+ CODE: IS_CODE,
1747
+ MARK: IS_HIGHLIGHT
1748
+ };
1749
+ function readElementFormatStyle(el) {
1750
+ return {
1751
+ fontStyle: el.style.fontStyle,
1752
+ fontWeight: el.style.fontWeight,
1753
+ textDecoration: el.style.textDecoration,
1754
+ verticalAlign: el.style.verticalAlign
1755
+ };
1756
+ }
1757
+ function mergeStyles(defaults, override) {
1758
+ return {
1759
+ fontStyle: override.fontStyle || defaults.fontStyle,
1760
+ fontWeight: override.fontWeight || defaults.fontWeight,
1761
+ textDecoration: override.textDecoration || defaults.textDecoration,
1762
+ verticalAlign: override.verticalAlign || defaults.verticalAlign
1763
+ };
1764
+ }
1765
+
1766
+ /**
1767
+ * The CSS property names {@link styleFormatOverride} reads — these are
1768
+ * "owned" by {@link ImportTextFormat} (the bit mask). When the
1769
+ * {@link ImportTextStyle} record is materialized onto a TextNode's
1770
+ * inline style by {@link styleObjectToCSS}, these are skipped so the
1771
+ * bit-mask side is the single source of truth and the same property
1772
+ * doesn't end up in both places (where the inline-style version would
1773
+ * shadow the format's themed CSS).
1774
+ */
1775
+ const FORMAT_BIT_STYLE_PROPS = new Set(['font-weight', 'font-style', 'text-decoration', 'vertical-align']);
1776
+
1777
+ /**
1778
+ * Translate a {@link FormatStyle} into a {@link FormatOverride}. Explicit
1779
+ * "non-decorating" values (`font-weight: normal`, `text-decoration: none`,
1780
+ * `vertical-align: baseline`) produce `clear` bits, so an inner element
1781
+ * can remove a format inherited from its ancestors.
1782
+ */
1783
+ function styleFormatOverride(style) {
1784
+ let set = 0;
1785
+ let clear = 0;
1786
+ const {
1787
+ fontWeight,
1788
+ fontStyle,
1789
+ textDecoration,
1790
+ verticalAlign
1791
+ } = style;
1792
+ if (fontWeight === '700' || fontWeight === 'bold') {
1793
+ set |= IS_BOLD;
1794
+ } else if (fontWeight === 'normal' || fontWeight === '400') {
1795
+ clear |= IS_BOLD;
1796
+ }
1797
+ if (fontStyle === 'italic') {
1798
+ set |= IS_ITALIC;
1799
+ } else if (fontStyle === 'normal') {
1800
+ clear |= IS_ITALIC;
1801
+ }
1802
+ if (textDecoration) {
1803
+ const parts = textDecoration.split(' ');
1804
+ if (parts.includes('underline')) {
1805
+ set |= IS_UNDERLINE;
1806
+ }
1807
+ if (parts.includes('line-through')) {
1808
+ set |= IS_STRIKETHROUGH;
1809
+ }
1810
+ if (parts.includes('none')) {
1811
+ clear |= IS_UNDERLINE | IS_STRIKETHROUGH;
1812
+ }
1813
+ }
1814
+ if (verticalAlign === 'sub') {
1815
+ set |= IS_SUBSCRIPT;
1816
+ clear |= IS_SUPERSCRIPT;
1817
+ } else if (verticalAlign === 'super') {
1818
+ set |= IS_SUPERSCRIPT;
1819
+ clear |= IS_SUBSCRIPT;
1820
+ } else if (verticalAlign === 'baseline') {
1821
+ clear |= IS_SUBSCRIPT | IS_SUPERSCRIPT;
1822
+ }
1823
+ return {
1824
+ clear,
1825
+ set
1826
+ };
1827
+ }
1828
+ function applyFormatOverride(format, ov) {
1829
+ return format & ~ov.clear | ov.set;
1830
+ }
1831
+
1832
+ /**
1833
+ * Unified rule for inline-format-bearing tags and `<span>`. The element's
1834
+ * effective style is its tag's {@link TAG_DEFAULT_STYLE} merged with its
1835
+ * inline `style` (element's own style wins for any property it sets), and
1836
+ * the resulting style is translated into a {@link FormatOverride}. Tags
1837
+ * with no CSS analog (`<code>`, `<mark>`) contribute their bit as a pure
1838
+ * `set` override.
1839
+ *
1840
+ * This shape lets:
1841
+ * - `<b style="font-weight: normal">` clear an inherited IS_BOLD.
1842
+ * - `<sub><sup>x</sup></sub>` resolve to IS_SUPERSCRIPT only (sub/sup
1843
+ * mutex via the vertical-align clear logic).
1844
+ * - `<span style="text-decoration: none">` strip inherited underline /
1845
+ * line-through.
1846
+ */
1847
+ const InlineFormatRule = defineImportRule({
1848
+ $import: (ctx, el) => {
1849
+ const inherited = ctx.get(ImportTextFormat);
1850
+ const tagDefault = TAG_DEFAULT_STYLE[el.nodeName];
1851
+ const elStyle = readElementFormatStyle(el);
1852
+ const effective = tagDefault ? mergeStyles(tagDefault, elStyle) : elStyle;
1853
+ let merged = applyFormatOverride(inherited, styleFormatOverride(effective));
1854
+ const tagOnly = TAG_ONLY_SET[el.nodeName];
1855
+ if (tagOnly) {
1856
+ merged |= tagOnly;
1857
+ }
1858
+ if (merged === inherited) {
1859
+ return ctx.$importChildren(el);
1860
+ }
1861
+ return ctx.$importChildren(el, {
1862
+ context: [contextValue(ImportTextFormat, merged)]
1863
+ });
1864
+ },
1865
+ match: sel$1.tag('b', 'strong', 'em', 'i', 'code', 'mark', 's', 'sub', 'sup', 'u', 'span'),
1866
+ name: '@lexical/html/inline-format'
1867
+ });
1868
+
1869
+ /**
1870
+ * Walk up the DOM ancestor chain to determine whether `node` is inside an
1871
+ * element whose whitespace should be preserved, per the supplied
1872
+ * {@link WhitespaceImportConfig.preservesWhitespace} predicate. Pure
1873
+ * ancestor walk, no caching.
1874
+ */
1875
+ function isInsidePreserveWhitespace(node, wsConfig) {
1876
+ let current = node.parentNode;
1877
+ while (current !== null) {
1878
+ if (wsConfig.preservesWhitespace(current)) {
1879
+ return true;
1880
+ }
1881
+ current = current.parentNode;
1882
+ }
1883
+ return false;
1884
+ }
1885
+ function findAdjacentTextOnLine(text, forward, wsConfig) {
1886
+ let node = text;
1887
+ while (true) {
1888
+ let sibling = null;
1889
+ while ((sibling = forward ? node.nextSibling : node.previousSibling) === null) {
1890
+ const parent = node.parentNode;
1891
+ if (parent === null) {
1892
+ return null;
1893
+ }
1894
+ node = parent;
1895
+ }
1896
+ node = sibling;
1897
+ if (!wsConfig.isInline(node)) {
1898
+ return null;
1899
+ }
1900
+ let descendant = node;
1901
+ while ((descendant = forward ? node.firstChild : node.lastChild) !== null) {
1902
+ node = descendant;
1903
+ }
1904
+ if (isDOMTextNode(node)) {
1905
+ return node;
1906
+ }
1907
+ if (node.nodeName === 'BR') {
1908
+ return null;
1909
+ }
1910
+ }
1911
+ }
1912
+ function collapseWhitespace(textNode, wsConfig) {
1913
+ let textContent = (textNode.textContent || '').replace(/\r/g, '').replace(/[ \t\n]+/g, ' ');
1914
+ if (textContent.length === 0) {
1915
+ return '';
1916
+ }
1917
+ if (textContent[0] === ' ') {
1918
+ let neighbor = textNode;
1919
+ let isStartOfLine = true;
1920
+ while (neighbor !== null && (neighbor = findAdjacentTextOnLine(neighbor, false, wsConfig)) !== null) {
1921
+ const neighborContent = neighbor.textContent || '';
1922
+ if (neighborContent.length > 0) {
1923
+ if (/[ \t\n]$/.test(neighborContent)) {
1924
+ textContent = textContent.slice(1);
1925
+ }
1926
+ isStartOfLine = false;
1927
+ break;
1928
+ }
1929
+ }
1930
+ if (isStartOfLine) {
1931
+ textContent = textContent.slice(1);
1932
+ }
1933
+ }
1934
+ if (textContent.length > 0 && textContent[textContent.length - 1] === ' ') {
1935
+ let neighbor = textNode;
1936
+ let isEndOfLine = true;
1937
+ while (neighbor !== null && (neighbor = findAdjacentTextOnLine(neighbor, true, wsConfig)) !== null) {
1938
+ const neighborContent = (neighbor.textContent || '').replace(/^( |\t|\r?\n)+/, '');
1939
+ if (neighborContent.length > 0) {
1940
+ isEndOfLine = false;
1941
+ break;
1942
+ }
1943
+ }
1944
+ if (isEndOfLine) {
1945
+ textContent = textContent.slice(0, -1);
1946
+ }
1947
+ }
1948
+ return textContent;
1949
+ }
1950
+ function $applyFormat(node, format) {
1951
+ return format !== 0 && $isTextNode(node) ? node.setFormat(format) : node;
1952
+ }
1953
+
1954
+ /**
1955
+ * Inverse of {@link getStyleObjectFromCSS}: serialize a parsed style
1956
+ * record back into a CSS declaration string suitable for
1957
+ * `TextNode.setStyle`. Returns the empty string for an empty record.
1958
+ */
1959
+ function styleObjectToCSS(style) {
1960
+ let css = '';
1961
+ for (const prop in style) {
1962
+ if (FORMAT_BIT_STYLE_PROPS.has(prop)) {
1963
+ // Owned by ImportTextFormat (bit mask) — skip so the format-bit
1964
+ // CSS is the single source of truth on the rendered TextNode.
1965
+ continue;
1966
+ }
1967
+ css += `${prop}: ${style[prop]}; `;
1968
+ }
1969
+ return css.trimEnd();
1970
+ }
1971
+ function $applyTextStyle(node, style) {
1972
+ if ($isTextNode(node)) {
1973
+ const css = styleObjectToCSS(style);
1974
+ if (css !== '') {
1975
+ node.setStyle(css);
1976
+ }
1977
+ }
1978
+ return node;
1979
+ }
1980
+
1981
+ /**
1982
+ * `#text` rule. Inside a `<pre>` ancestor, preserve whitespace and split
1983
+ * on `\n` and `\t` into `LineBreakNode`/`TabNode` siblings. Otherwise
1984
+ * collapse whitespace using the same neighbor-aware rules as the legacy
1985
+ * `$convertTextDOMNode`.
1986
+ */
1987
+ const TextRule = defineImportRule({
1988
+ $import: (ctx, el) => {
1989
+ const format = ctx.get(ImportTextFormat);
1990
+ const style = ctx.get(ImportTextStyle);
1991
+ const wsConfig = ctx.get(ImportWhitespaceConfig);
1992
+ if (isInsidePreserveWhitespace(el, wsConfig)) {
1993
+ const out = $generateNodesFromRawText(el.textContent || '');
1994
+ for (const node of out) {
1995
+ $applyFormat(node, format);
1996
+ $applyTextStyle(node, style);
1997
+ }
1998
+ return out;
1999
+ }
2000
+ const collapsed = collapseWhitespace(el, wsConfig);
2001
+ if (collapsed === '') {
2002
+ return [];
2003
+ }
2004
+ const text = $createTextNode(collapsed);
2005
+ $applyFormat(text, format);
2006
+ $applyTextStyle(text, style);
2007
+ return [text];
2008
+ },
2009
+ match: sel$1.text(),
2010
+ name: '@lexical/html/#text'
2011
+ });
2012
+
2013
+ /**
2014
+ * Drop `<style>` and `<script>` and skip descending into them — matches
2015
+ * the legacy `IGNORE_TAGS` set, but as a regular rule so apps can register
2016
+ * a higher-priority `<style>` rule to capture stylesheet text into the
2017
+ * import session for later use.
2018
+ */
2019
+ const IgnoreScriptStyleRule = defineImportRule({
2020
+ $import: () => [],
2021
+ match: sel$1.tag('script', 'style'),
2022
+ name: '@lexical/html/script-style-ignore'
2023
+ });
2024
+ const LineBreakRule = defineImportRule({
2025
+ $import: () => [$createLineBreakNode()],
2026
+ match: sel$1.tag('br'),
2027
+ name: '@lexical/html/br'
2028
+ });
2029
+
2030
+ /**
2031
+ * `<p>` rule. Re-applies format, indent, direction, and the legacy
2032
+ * `align` attribute fallback.
2033
+ */
2034
+ const ParagraphRule = defineImportRule({
2035
+ $import: (ctx, el) => {
2036
+ const p = $createParagraphNode();
2037
+ $setFormatFromDOM(p, el);
2038
+ setNodeIndentFromDOM(el, p);
2039
+ if (p.getFormatType() === '') {
2040
+ const align = el.getAttribute('align');
2041
+ if (align && isAlignmentValue(align)) {
2042
+ p.setFormat(align);
2043
+ }
2044
+ }
2045
+ $setDirectionFromDOM(p, el);
2046
+ // We deliberately pass no schema: paragraphs accept any inline run as-is.
2047
+ // The enclosing context (root / block) is responsible for ensuring the
2048
+ // paragraph itself is a valid block child.
2049
+ return [p.splice(0, 0, ctx.$importChildren(el))];
2050
+ },
2051
+ match: sel$1.tag('p'),
2052
+ name: '@lexical/html/p'
2053
+ });
2054
+
2055
+ /**
2056
+ * Rules covering the {@link ParagraphNode}, {@link TextNode},
2057
+ * {@link LineBreakNode}, and {@link TabNode} cases that the legacy
2058
+ * `importDOM` machinery in `@lexical/lexical` handled. Intended to be
2059
+ * registered as a dependency of every editor that uses
2060
+ * {@link DOMImportExtension}.
2061
+ *
2062
+ * @experimental
2063
+ */
2064
+ const CoreImportRules = [IgnoreScriptStyleRule, ParagraphRule, TextRule, LineBreakRule, InlineFormatRule];
2065
+
2066
+ /**
2067
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2068
+ *
2069
+ * This source code is licensed under the MIT license found in the
2070
+ * LICENSE file in the root directory of this source tree.
2071
+ *
2072
+ */
2073
+
2074
+
2075
+ /** @internal */
2076
+
2077
+ /** @internal */
2078
+
2079
+ function mergeSortedAsc(a, b) {
2080
+ const out = [];
2081
+ let i = 0;
2082
+ let j = 0;
2083
+ while (i < a.length && j < b.length) {
2084
+ if (a[i] <= b[j]) {
2085
+ out.push(a[i++]);
2086
+ } else {
2087
+ out.push(b[j++]);
2088
+ }
2089
+ }
2090
+ while (i < a.length) {
2091
+ out.push(a[i++]);
2092
+ }
2093
+ while (j < b.length) {
2094
+ out.push(b[j++]);
2095
+ }
2096
+ return out;
2097
+ }
2098
+
2099
+ /**
2100
+ * Compile an ordered list of {@link DOMImportRule}s into the dispatch tables
2101
+ * used by the import runtime. The rule at index 0 is the highest-priority
2102
+ * (`mergeConfig` prepends partial.rules so later-merged extensions land
2103
+ * first).
2104
+ *
2105
+ * @internal
2106
+ */
2107
+ function compileImportRules(rules) {
2108
+ const compiled = [];
2109
+ const byTag = new Map();
2110
+ const wildcardIndices = [];
2111
+ const textIndices = [];
2112
+ const commentIndices = [];
2113
+ const seenNames = new Set();
2114
+ rules.forEach((rule, i) => {
2115
+ const sel = getSelectorImpl(rule.match);
2116
+ const name = rule.name || defaultRuleName(sel, i);
2117
+ if (typeof rule.name === 'string' && seenNames.has(rule.name)) {
2118
+ console.warn(`[lexical] duplicate DOMImportRule name "${rule.name}" — keep names unique to aid debugging.`);
2119
+ }
2120
+ if (rule.name) {
2121
+ seenNames.add(rule.name);
2122
+ }
2123
+ compiled.push({
2124
+ $import: rule.$import,
2125
+ name,
2126
+ predicate: sel.predicate
2127
+ });
2128
+ if (sel.kind === 'text') {
2129
+ textIndices.push(i);
2130
+ } else if (sel.kind === 'comment') {
2131
+ commentIndices.push(i);
2132
+ } else if (sel.tags.size === 0) {
2133
+ wildcardIndices.push(i);
2134
+ } else {
2135
+ for (const tag of sel.tags) {
2136
+ let list = byTag.get(tag);
2137
+ if (!list) {
2138
+ list = [];
2139
+ byTag.set(tag, list);
2140
+ }
2141
+ list.push(i);
2142
+ }
2143
+ }
2144
+ });
2145
+
2146
+ // Interleave wildcard-element indices into each tag's list in registration
2147
+ // (ascending-index) order, so iterating a tag bucket visits both tag-
2148
+ // specific and wildcard rules in the same priority sequence.
2149
+ const finalByTag = new Map();
2150
+ if (wildcardIndices.length === 0) {
2151
+ for (const [tag, list] of byTag) {
2152
+ finalByTag.set(tag, list);
2153
+ }
2154
+ } else {
2155
+ for (const [tag, list] of byTag) {
2156
+ finalByTag.set(tag, mergeSortedAsc(list, wildcardIndices));
2157
+ }
2158
+ }
2159
+ return {
2160
+ byTag: finalByTag,
2161
+ commentIndices,
2162
+ rules: compiled,
2163
+ textIndices,
2164
+ wildcardIndices
2165
+ };
2166
+ }
2167
+ function defaultRuleName(sel, index) {
2168
+ if (sel.kind === 'text') {
2169
+ return `#text@${index}`;
2170
+ }
2171
+ if (sel.kind === 'comment') {
2172
+ return `#comment@${index}`;
2173
+ }
2174
+ if (sel.tags.size === 0) {
2175
+ return `*@${index}`;
2176
+ }
2177
+ const tagList = Array.from(sel.tags).join(',').toLowerCase();
2178
+ return `${tagList}@${index}`;
2179
+ }
2180
+
2181
+ /**
2182
+ * Look up the (already interleaved) rule indices relevant to `node`. Element
2183
+ * nodes hit `byTag` (with wildcards merged in) or fall back to the wildcard
2184
+ * bucket if no tag-specific rules exist; text and comment nodes use their
2185
+ * own buckets.
2186
+ *
2187
+ * @internal
2188
+ */
2189
+ function getDispatchIndices(dispatch, node) {
2190
+ if (isDOMTextNode(node)) {
2191
+ return dispatch.textIndices;
2192
+ }
2193
+ if (node.nodeType === 8 /* COMMENT_NODE */) {
2194
+ return dispatch.commentIndices;
2195
+ }
2196
+ if (isHTMLElement(node)) {
2197
+ return dispatch.byTag.get(node.nodeName) || dispatch.wildcardIndices;
2198
+ }
2199
+ return EMPTY_INDICES;
2200
+ }
2201
+ const EMPTY_INDICES = Object.freeze([]);
2202
+
2203
+ /**
2204
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2205
+ *
2206
+ * This source code is licensed under the MIT license found in the
2207
+ * LICENSE file in the root directory of this source tree.
2208
+ *
2209
+ */
2210
+
2211
+
2212
+ /**
2213
+ * Opaque handle for a pre-compiled set of overlay rules. Produce one with
2214
+ * {@link defineOverlayRules} and pass it to
2215
+ * {@link DOMImportContext.$importChildren} via
2216
+ * {@link ImportChildrenOpts.rules}.
2217
+ *
2218
+ * To merge two or more overlays into a single one, pass them (alongside
2219
+ * raw {@link DOMImportRule}s if desired) to a fresh
2220
+ * {@link defineOverlayRules} — earlier arguments are higher priority.
2221
+ *
2222
+ * The internal shape is intentionally not part of the public API: it's a
2223
+ * compiled dispatch table tagged with `__type` so callers cannot pass a
2224
+ * raw rule array where a compiled overlay is expected.
2225
+ *
2226
+ * @experimental
2227
+ */
2228
+
2229
+ /**
2230
+ * An entry accepted everywhere rules are configured (overlay
2231
+ * definitions, {@link DOMImportConfig.rules}). Either a single
2232
+ * {@link DOMImportRule} or a {@link CompiledOverlayRules} produced by
2233
+ * a previous {@link defineOverlayRules} call — passing the latter
2234
+ * inlines the overlay's rules at this position in priority order.
2235
+ *
2236
+ * @experimental
2237
+ */
2238
+
2239
+ /** @internal */
2240
+ function flattenRuleEntries(entries) {
2241
+ const out = [];
2242
+ for (const entry of entries) {
2243
+ if (isCompiledOverlayRules(entry)) {
2244
+ for (const r of entry.rules) {
2245
+ out.push(r);
2246
+ }
2247
+ } else {
2248
+ out.push(entry);
2249
+ }
2250
+ }
2251
+ return out;
2252
+ }
2253
+ function isCompiledOverlayRules(entry) {
2254
+ return typeof entry === 'object' && entry !== null && '__type' in entry && entry.__type === 'CompiledOverlayRules';
2255
+ }
2256
+
2257
+ /**
2258
+ * Pre-compile a set of {@link DOMImportRuleEntry}s into a
2259
+ * {@link CompiledOverlayRules} handle that can be installed via
2260
+ * `ctx.$importChildren(el, {rules: …})`.
2261
+ *
2262
+ * Entries can be raw {@link DOMImportRule}s or other
2263
+ * {@link CompiledOverlayRules} (the latter are inlined at their
2264
+ * position in priority order, so the same call composes any number of
2265
+ * overlays). Earlier entries are higher priority.
2266
+ *
2267
+ * Overlay rules installed as a raw array would be re-compiled on every
2268
+ * `$importChildren` call. For overlays that are reused (e.g. a GitHub
2269
+ * code-table rule that wraps every matching table), call this once at
2270
+ * module scope so the dispatch table is built up front.
2271
+ *
2272
+ * @experimental
2273
+ */
2274
+ function defineOverlayRules(entries) {
2275
+ const rules = flattenRuleEntries(entries);
2276
+ return {
2277
+ __type: 'CompiledOverlayRules',
2278
+ dispatch: compileImportRules(rules),
2279
+ rules
2280
+ };
2281
+ }
2282
+
2283
+ /**
2284
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2285
+ *
2286
+ * This source code is licensed under the MIT license found in the
2287
+ * LICENSE file in the root directory of this source tree.
2288
+ *
2289
+ */
2290
+
2291
+
2292
+ /**
2293
+ * True if the node fills a block slot at the root or inside another
2294
+ * block — covers both ElementNode-style blocks (paragraph, heading,
2295
+ * quote) and block-level DecoratorNodes (HorizontalRuleNode,
2296
+ * ImageNode-as-block, etc.). Used by {@link BlockSchema},
2297
+ * {@link RootSchema}, and {@link NestedBlockSchema}.
2298
+ *
2299
+ * @experimental
2300
+ */
2301
+ function $isBlockLevel(node) {
2302
+ return $isBlockElementNode(node) || $isDecoratorNode(node) && !node.isInline();
2303
+ }
2304
+
2305
+ /**
2306
+ * Distribute an inline wrapper (`LinkNode`, `MarkNode`, …) across a
2307
+ * heterogeneous run of children produced by `$importChildren`, lifting
2308
+ * any block children to the top level while keeping the wrapper around
2309
+ * the leaf inline content.
2310
+ *
2311
+ * Use from a rule whose DOM source is an inline element that the
2312
+ * browser permitted to enclose block elements — the canonical case is
2313
+ * `<a href="…"><h1>title</h1><div>body</div></a>`, which a link rule
2314
+ * wants to surface as two block siblings (heading + paragraph), each
2315
+ * with its own link wrapping the original inline content. Schemas
2316
+ * can't express this because they reason about a parent's children
2317
+ * only — they cannot lift the parent out of itself.
2318
+ *
2319
+ * For each top-level child:
2320
+ * - **Inline children** are collected into runs; each run is wrapped
2321
+ * in a single fresh wrapper (from `$makeWrapper()`).
2322
+ * - **Block children** are descended into: their own children are
2323
+ * recursively distributed with `$makeWrapper`, then re-attached so
2324
+ * the block keeps its position at the top level.
2325
+ *
2326
+ * The returned list will contain a mix of blocks and wrapped inline
2327
+ * runs. The enclosing schema (typically {@link BlockSchema}) will
2328
+ * then package those inline wrappers into paragraphs as usual.
2329
+ *
2330
+ * @experimental
2331
+ */
2332
+ function $distributeInlineWrapper(children, $makeWrapper) {
2333
+ const out = [];
2334
+ let inlineRun = [];
2335
+ const flushInline = () => {
2336
+ if (inlineRun.length === 0) {
2337
+ return;
2338
+ }
2339
+ out.push($makeWrapper().splice(0, 0, inlineRun));
2340
+ inlineRun = [];
2341
+ };
2342
+ for (const child of children) {
2343
+ if ($isBlockLevel(child)) {
2344
+ flushInline();
2345
+ // Recursively distribute the wrapper into the block's own
2346
+ // children. A block DecoratorNode (no children) is left alone.
2347
+ if ($isElementNode(child)) {
2348
+ const wrapped = $distributeInlineWrapper(child.getChildren(), $makeWrapper);
2349
+ child.splice(0, child.getChildrenSize(), wrapped);
2350
+ }
2351
+ out.push(child);
2352
+ } else {
2353
+ inlineRun.push(child);
2354
+ }
2355
+ }
2356
+ flushInline();
2357
+ return out;
2358
+ }
2359
+
2360
+ /**
2361
+ * Apply a {@link ChildSchema} to a flat list of children produced by
2362
+ * `$importChildren`. Walks the list once, partitions into accepted vs.
2363
+ * rejected runs, packages or drops rejected runs, then runs `$finalize`.
2364
+ *
2365
+ * @internal
2366
+ */
2367
+ function $applySchema(schema, children, parent, domParent) {
2368
+ const out = [];
2369
+ let run = null;
2370
+ const flushRun = () => {
2371
+ if (run === null) {
2372
+ return;
2373
+ }
2374
+ const rejected = run;
2375
+ run = null;
2376
+ if (schema.$packageRun) {
2377
+ const packaged = schema.$packageRun(rejected, parent, domParent);
2378
+ if (packaged.length > 0) {
2379
+ for (const n of packaged) {
2380
+ out.push(n);
2381
+ }
2382
+ return;
2383
+ }
2384
+ }
2385
+ // No $packageRun (or it returned []) — apply onReject. 'drop' (default)
2386
+ // discards the run. 'hoist' lets it through unchanged at this level.
2387
+ if (schema.onReject === 'hoist') {
2388
+ for (const n of rejected) {
2389
+ out.push(n);
2390
+ }
2391
+ }
2392
+ };
2393
+ for (const child of children) {
2394
+ if (schema.$accepts(child, parent)) {
2395
+ flushRun();
2396
+ out.push(child);
2397
+ } else {
2398
+ if (run === null) {
2399
+ run = [];
2400
+ }
2401
+ run.push(child);
2402
+ }
2403
+ }
2404
+ flushRun();
2405
+ return schema.$finalize ? schema.$finalize(out, parent) : out;
2406
+ }
2407
+
2408
+ /**
2409
+ * Wrap a run of inline lexical nodes in a fresh paragraph, propagating the
2410
+ * `text-align` of `domParent` as the paragraph's format type (matching the
2411
+ * legacy `wrapContinuousInlines` behavior).
2412
+ */
2413
+ function $paragraphPackageRun(run, _parent, domParent) {
2414
+ const paragraph = $createParagraphNode();
2415
+ if (isHTMLElement(domParent)) {
2416
+ const textAlign = domParent.style.textAlign;
2417
+ if (isAlignmentValue(textAlign)) {
2418
+ paragraph.setFormat(textAlign);
2419
+ }
2420
+ }
2421
+ return [paragraph.splice(0, 0, run)];
2422
+ }
2423
+
2424
+ /**
2425
+ * Default schema for block-level positions (root of the document, the body
2426
+ * of a block element node). Accepts block lexical nodes; packages runs of
2427
+ * inline children into fresh paragraph nodes.
2428
+ *
2429
+ * @experimental
2430
+ */
2431
+ const BlockSchema = {
2432
+ $accepts: $isBlockLevel,
2433
+ $packageRun: $paragraphPackageRun,
2434
+ name: 'BlockSchema'
2435
+ };
2436
+
2437
+ /**
2438
+ * Schema for inline-only positions (the body of an inline lexical node such
2439
+ * as a link). Accepts non-block lexical nodes; runs of block children are
2440
+ * dropped (`onReject: 'drop'` is the default).
2441
+ *
2442
+ * @experimental
2443
+ */
2444
+ const InlineSchema = {
2445
+ $accepts: child => !$isBlockLevel(child),
2446
+ name: 'InlineSchema'
2447
+ };
2448
+
2449
+ /**
2450
+ * Schema for nested block positions — the equivalent of the legacy
2451
+ * `ArtificialNode__DO_NOT_USE` flow used when a block DOM element appears
2452
+ * inside another block lexical ancestor. Accepts block nodes; runs of inline
2453
+ * children are emitted with a line break between consecutive runs (instead
2454
+ * of being wrapped in a paragraph, which would introduce an extra level of
2455
+ * nesting).
2456
+ *
2457
+ * @experimental
2458
+ */
2459
+ const NestedBlockSchema = {
2460
+ $accepts: $isBlockLevel,
2461
+ /**
2462
+ * Pass an inline run through unchanged. Because the schema iterator only
2463
+ * groups *maximal* rejected runs (each separated from the next by an
2464
+ * accepted block child), the legacy "linebreak between adjacent inline
2465
+ * groups" case never arises — adjacent inline siblings are already
2466
+ * coalesced into one run.
2467
+ */
2468
+ $packageRun: run => run,
2469
+ name: 'NestedBlockSchema'
2470
+ };
2471
+
2472
+ /**
2473
+ * Schema for the topmost level of `$generateNodesFromDOM`. Identical to
2474
+ * {@link BlockSchema}; aliased for clarity at the entry point and so it can
2475
+ * be overridden separately in the future (e.g. to synthesize a `ListNode`
2476
+ * around runs of orphan `ListItemNode`s).
2477
+ *
2478
+ * @experimental
2479
+ */
2480
+ const RootSchema = {
2481
+ $accepts: $isBlockLevel,
2482
+ $packageRun: $paragraphPackageRun,
2483
+ name: 'RootSchema'
2484
+ };
2485
+
2486
+ /**
2487
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2488
+ *
2489
+ * This source code is licensed under the MIT license found in the
2490
+ * LICENSE file in the root directory of this source tree.
2491
+ *
2492
+ */
2493
+
2494
+ const NO_CAPTURES = Object.freeze({});
2495
+ function makeContext(runtime, captures) {
2496
+ const ctx = {
2497
+ $importChildren: (parent, opts) => $importChildrenInternal(runtime, parent, opts),
2498
+ $importOne: (node, opts) => $importOneInternal(runtime, node, opts),
2499
+ captures,
2500
+ get(cfg) {
2501
+ return $getImportContextValue(cfg, runtime.editor);
2502
+ },
2503
+ session: runtime.session
2504
+ };
2505
+ return ctx;
2506
+ }
2507
+ function $importChildrenInternal(runtime, parent, opts) {
2508
+ const overlay = opts && opts.rules ? opts.rules.dispatch : undefined;
2509
+ if (overlay) {
2510
+ runtime.overlays.push(overlay);
2511
+ }
2512
+ try {
2513
+ const run = () => $importChildrenRun(runtime, parent, opts);
2514
+ return opts && opts.context ? $withImportContext(opts.context, runtime.editor)(run) : run();
2515
+ } finally {
2516
+ if (overlay) {
2517
+ runtime.overlays.pop();
2518
+ }
2519
+ }
2520
+ }
2521
+ function $importChildrenRun(runtime, parent, opts) {
2522
+ const onChild = opts && opts.$onChild;
2523
+ const collected = [];
2524
+ for (const child of Array.from(parent.childNodes)) {
2525
+ const produced = $importOneInternal(runtime, child, undefined);
2526
+ for (const lex of produced) {
2527
+ const result = onChild ? onChild(lex) : lex;
2528
+ if (result != null) {
2529
+ collected.push(result);
2530
+ }
2531
+ }
2532
+ }
2533
+ const afterApplied = opts && opts.$after ? opts.$after(collected) : collected;
2534
+ const schema = opts && opts.schema;
2535
+ if (!schema) {
2536
+ return afterApplied;
2537
+ }
2538
+ return $applySchema(schema, afterApplied, null, parent);
2539
+ }
2540
+ function $importOneInternal(runtime, node, opts) {
2541
+ const run = () => $dispatch(runtime, node);
2542
+ const out = opts && opts.context ? $withImportContext(opts.context, runtime.editor)(run) : run();
2543
+ // Surface to callers as a mutable array per the DOMImportContext contract.
2544
+ return out;
2545
+ }
2546
+
2547
+ /**
2548
+ * Build the candidate (dispatch, indices) list for `node`. Overlays are
2549
+ * tried first in top-of-stack order; the main dispatcher comes last. The
2550
+ * `$next()` chain walks through all of them in sequence — an overlay rule
2551
+ * can defer to a lower overlay rule, or all the way through to a main
2552
+ * rule, just by calling `$next()`.
2553
+ */
2554
+ function getCandidates(runtime, node) {
2555
+ const candidates = [];
2556
+ for (let i = runtime.overlays.length - 1; i >= 0; i--) {
2557
+ const d = runtime.overlays[i];
2558
+ const idx = getDispatchIndices(d, node);
2559
+ if (idx.length > 0) {
2560
+ candidates.push({
2561
+ dispatch: d,
2562
+ indices: idx
2563
+ });
2564
+ }
2565
+ }
2566
+ const mainIdx = getDispatchIndices(runtime.dispatch, node);
2567
+ if (mainIdx.length > 0) {
2568
+ candidates.push({
2569
+ dispatch: runtime.dispatch,
2570
+ indices: mainIdx
2571
+ });
2572
+ }
2573
+ return candidates;
2574
+ }
2575
+ function $dispatch(runtime, node) {
2576
+ const candidates = getCandidates(runtime, node);
2577
+ if (candidates.length === 0) {
2578
+ return $hoistChildrenOf(runtime, node);
2579
+ }
2580
+ let groupCursor = 0;
2581
+ let ruleCursor = 0;
2582
+ const $next = () => {
2583
+ while (groupCursor < candidates.length) {
2584
+ const {
2585
+ dispatch,
2586
+ indices
2587
+ } = candidates[groupCursor];
2588
+ while (ruleCursor < indices.length) {
2589
+ const idx = indices[ruleCursor++];
2590
+ const rule = dispatch.rules[idx];
2591
+ const captures = {};
2592
+ if (rule.predicate(node, captures)) {
2593
+ const ctx = makeContext(runtime, Object.keys(captures).length === 0 ? NO_CAPTURES : captures);
2594
+ try {
2595
+ return rule.$import(ctx, node, $next);
2596
+ } catch (e) {
2597
+ {
2598
+ console.error(`[lexical] DOM import rule "${rule.name}" threw on node`, node, e);
2599
+ }
2600
+ throw e;
2601
+ }
2602
+ }
2603
+ }
2604
+ groupCursor++;
2605
+ ruleCursor = 0;
2606
+ }
2607
+ return $hoistChildrenOf(runtime, node);
2608
+ };
2609
+ return $next();
2610
+ }
2611
+
2612
+ /**
2613
+ * Fallback when no rule matched and `$next()` was called past the end of the
2614
+ * chain: hoist the element's children to take its place, recursively. Pure
2615
+ * elements with no rule become invisible, matching the legacy
2616
+ * `$createNodesFromDOM` hoisting behavior.
2617
+ */
2618
+ function $hoistChildrenOf(runtime, node) {
2619
+ if (node.childNodes.length === 0) {
2620
+ return [];
2621
+ }
2622
+ const collected = [];
2623
+ for (const child of Array.from(node.childNodes)) {
2624
+ const produced = $importOneInternal(runtime, child, undefined);
2625
+ for (const lex of produced) {
2626
+ collected.push(lex);
2627
+ }
2628
+ }
2629
+ return collected;
2630
+ }
2631
+
2632
+ /**
2633
+ * Top-level walker for a compiled dispatcher. Iterates the DOM children of
2634
+ * `dom` (using the document body if a {@link Document} is passed) and
2635
+ * applies `RootSchema` to the produced lexical nodes so runs of inlines are
2636
+ * wrapped in paragraphs — same shape as the legacy `$generateNodesFromDOM`.
2637
+ *
2638
+ * @internal
2639
+ */
2640
+ function $runImport(dispatch, editor, dom, session) {
2641
+ // Prime the overlay stack with any overlays a preprocess wrote to
2642
+ // ImportOverlays. These remain in effect for the entire walk; nested
2643
+ // `$importChildren({rules})` calls push on top.
2644
+ const installed = session.get(ImportOverlays);
2645
+ const runtime = {
2646
+ dispatch,
2647
+ editor,
2648
+ overlays: installed.map(o => o.dispatch),
2649
+ session
2650
+ };
2651
+ const rootParent = isDOMDocumentNode(dom) ? dom.body : dom;
2652
+ return $importChildrenRun(runtime, rootParent, {
2653
+ schema: RootSchema
2654
+ });
2655
+ }
2656
+
2657
+ /**
2658
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2659
+ *
2660
+ * This source code is licensed under the MIT license found in the
2661
+ * LICENSE file in the root directory of this source tree.
2662
+ *
2663
+ */
2664
+
2665
+
2666
+ /**
2667
+ * Configuration for {@link DOMImportExtension}.
2668
+ *
2669
+ * @experimental
2670
+ */
2671
+
2672
+ /**
2673
+ * Drive a stack of {@link DOMPreprocessFn}s top-to-bottom: the highest-
2674
+ * index fn runs first and may call `$next()` to defer to the next-lower
2675
+ * one. Matches the export-side `callExportMimeTypeFunctionStack` shape.
2676
+ */
2677
+ function $runPreprocessStack(stack, dom, ctx) {
2678
+ let i = stack.length - 1;
2679
+ const $next = () => {
2680
+ while (i >= 0) {
2681
+ const cur = stack[i--];
2682
+ cur(dom, ctx, $next);
2683
+ return;
2684
+ }
2685
+ };
2686
+ $next();
2687
+ }
2688
+
2689
+ /**
2690
+ * Lowest-priority catch-all rule used as the default `config.rules` entry
2691
+ * for {@link DOMImportExtension}: descends into the element's children
2692
+ * and returns whatever they produced. With no other matching rule, an
2693
+ * element vanishes and its contents are inserted in its place — the
2694
+ * legacy `$createNodesFromDOM` hoisting behavior, but now expressed as a
2695
+ * regular rule that apps can override (e.g. with a `sel.any()` rule that
2696
+ * captures and discards unknown elements).
2697
+ *
2698
+ * @experimental
2699
+ */
2700
+ const DefaultHoistRule = defineImportRule({
2701
+ $import: (ctx, el) => ctx.$importChildren(el),
2702
+ match: selBase.any(),
2703
+ name: '@lexical/html/default-hoist'
2704
+ });
2705
+
2706
+ /**
2707
+ * @experimental
2708
+ *
2709
+ * Extension-based replacement for the legacy `importDOM` / `DOMConversion`
2710
+ * machinery. Rules are contributed via configuration (see
2711
+ * {@link DOMImportConfig.rules}), compiled into a tag-bucketed dispatcher at
2712
+ * editor build time, and consumed via the extension's
2713
+ * {@link DOMImportExtensionOutput.$generateNodesFromDOM} output.
2714
+ *
2715
+ * The legacy `$generateNodesFromDOM` continues to work in parallel; the
2716
+ * intent is to migrate node packages over to this extension incrementally.
2717
+ */
2718
+ const DOMImportExtension = defineExtension({
2719
+ build(editor, config) {
2720
+ const dispatch = compileImportRules(flattenRuleEntries(config.rules));
2721
+ const defaults = contextFromPairs(config.contextDefaults, undefined);
2722
+ const configPreprocess = config.preprocess;
2723
+ return {
2724
+ $generateNodesFromDOM: (dom, options) => {
2725
+ // The session record IS the root layer of the walk's context.
2726
+ // Start with per-call options.context applied on top of the
2727
+ // editor's contextDefaults, then ensure we have a *fresh*
2728
+ // mutable child (never the shared defaults record) so
2729
+ // session.set writes never leak into the editor's config.
2730
+ const fromOpts = options && options.context ? contextFromPairs(options.context, defaults) : defaults;
2731
+ const sessionRecord = fromOpts !== undefined && fromOpts !== defaults ? fromOpts : Object.create(defaults || null);
2732
+ const session = new ImportSessionImpl(sessionRecord);
2733
+ const preprocessCtx = {
2734
+ session
2735
+ };
2736
+ // Stack of preprocessors: config-level first, then per-call.
2737
+ // Top of stack (last in array) runs first; `next()` defers to
2738
+ // the next-lower one. Matches the GetClipboardDataExtension
2739
+ // convention so app-registered preprocessors can wrap built-in
2740
+ // ones via `next()`. Preprocess writes via `ctx.session.set`
2741
+ // mutate the session record directly.
2742
+ const stack = options && options.preprocess ? [...configPreprocess, ...options.preprocess] : configPreprocess;
2743
+ $runPreprocessStack(stack, dom, preprocessCtx);
2744
+ return $withFullContext(DOMImportContextSymbol, sessionRecord, () => $runImport(dispatch, editor, dom, session), editor);
2745
+ },
2746
+ defaults
2747
+ };
2748
+ },
2749
+ config: {
2750
+ contextDefaults: [],
2751
+ preprocess: [$inlineStylesFromStyleSheets],
2752
+ rules: [DefaultHoistRule]
2753
+ },
2754
+ mergeConfig(config, partial) {
2755
+ return shallowMergeConfig(config, {
2756
+ ...partial,
2757
+ ...(partial.contextDefaults && {
2758
+ contextDefaults: [...config.contextDefaults, ...partial.contextDefaults]
2759
+ }),
2760
+ ...(partial.preprocess && {
2761
+ preprocess: [...config.preprocess, ...partial.preprocess]
2762
+ }),
2763
+ ...(partial.rules && {
2764
+ rules: [...partial.rules, ...config.rules]
2765
+ })
2766
+ });
2767
+ },
2768
+ name: DOMImportExtensionName
2769
+ });
2770
+
2771
+ /**
2772
+ * Look up the editor's {@link DOMImportExtension} and run its
2773
+ * `$generateNodesFromDOM`. Designed as a drop-in replacement for the
2774
+ * legacy `$generateNodesFromDOM(editor, dom)` signature so it can be
2775
+ * supplied to `ClipboardImportExtension.$generateNodesFromDOM` (or any
2776
+ * other consumer that wants to route through the extension pipeline).
2777
+ *
2778
+ * Throws if the editor was not built with {@link DOMImportExtension} as a
2779
+ * dependency.
2780
+ *
2781
+ * @experimental
2782
+ */
2783
+ function $generateNodesFromDOMViaExtension(dom, options) {
2784
+ return $getExtensionOutput(DOMImportExtension).$generateNodesFromDOM(dom, options);
2785
+ }
2786
+
2787
+ /**
2788
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2789
+ *
2790
+ * This source code is licensed under the MIT license found in the
2791
+ * LICENSE file in the root directory of this source tree.
2792
+ *
2793
+ */
2794
+
2795
+ /**
2796
+ * Bundles {@link CoreImportRules} into a {@link DOMImportExtension}-aware
2797
+ * extension. Depend on this from your editor (directly or via richer
2798
+ * extensions like `RichTextImportExtension`) to get the equivalent of the
2799
+ * legacy core `importDOM` behavior for `<p>`, `<span>`, `<b>`,
2800
+ * `<strong>`, `<em>`, `<i>`, `<code>`, `<mark>`, `<s>`, `<sub>`, `<sup>`,
2801
+ * `<u>`, `<br>`, and `#text`.
2802
+ *
2803
+ * @experimental
2804
+ */
2805
+ const CoreImportExtension = defineExtension({
2806
+ dependencies: [configExtension(DOMImportExtension, {
2807
+ rules: CoreImportRules
2808
+ })],
2809
+ name: '@lexical/html/CoreImport'
2810
+ });
2811
+
2812
+ /**
2813
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2814
+ *
2815
+ * This source code is licensed under the MIT license found in the
2816
+ * LICENSE file in the root directory of this source tree.
2817
+ *
2818
+ */
2819
+
2820
+ const HorizontalRuleRule = defineImportRule({
2821
+ $import: () => [$createHorizontalRuleNode()],
2822
+ match: selBase.tag('hr'),
2823
+ name: '@lexical/html/hr'
2824
+ });
2825
+
2826
+ /**
2827
+ * Import rules for {@link HorizontalRuleNode}.
2828
+ *
2829
+ * @experimental
2830
+ */
2831
+ const HorizontalRuleImportRules = [HorizontalRuleRule];
2832
+
2833
+ /**
2834
+ * Bundles {@link HorizontalRuleImportRules} (plus
2835
+ * {@link CoreImportExtension}) into a single dependency. The legacy
2836
+ * {@link HorizontalRuleExtension.importDOM} continues to work in parallel;
2837
+ * depend on this extension to opt into the new pipeline.
2838
+ *
2839
+ * Lives in `@lexical/html` (not `@lexical/extension`) because
2840
+ * {@link DOMImportExtension} itself is in `@lexical/html`, and
2841
+ * `@lexical/extension` is upstream of `@lexical/html` in the dependency
2842
+ * graph — same arrangement as {@link CoreImportExtension}.
2843
+ *
2844
+ * @experimental
2845
+ */
2846
+ const HorizontalRuleImportExtension = defineExtension({
2847
+ dependencies: [CoreImportExtension, HorizontalRuleExtension, configExtension(DOMImportExtension, {
2848
+ rules: HorizontalRuleImportRules
2849
+ })],
2850
+ name: '@lexical/html/HorizontalRuleImport'
2851
+ });
2852
+
2853
+ /**
2854
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2855
+ *
2856
+ * This source code is licensed under the MIT license found in the
2857
+ * LICENSE file in the root directory of this source tree.
2858
+ *
2859
+ */
2860
+
2861
+ /**
2862
+ * Combinator-and-parser-based builder for {@link CompiledSelector}s. The
2863
+ * runtime shape returned by these factory methods is opaque; consumers
2864
+ * should never inspect or construct selector objects directly.
2865
+ *
2866
+ * @experimental
2867
+ */
2868
+ const sel = {
2869
+ any: selBase.any,
2870
+ comment: selBase.comment,
2871
+ /**
2872
+ * Parse a reduced CSS-selector subset and return a builder you can chain
2873
+ * combinator methods off of.
2874
+ */
2875
+ css: parseSelector,
2876
+ tag: selBase.tag,
2877
+ text: selBase.text
2878
+ };
2879
+
2880
+ const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
2881
+
2882
+ /**
2883
+ * How you parse your html string to get a document is left up to you. In the browser you can use the native
2884
+ * DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom
2885
+ * or an equivalent library and pass in the document here.
2886
+ */
2887
+ function $generateNodesFromDOM(editor, dom) {
2888
+ $inlineStylesFromStyleSheetsDOM(dom);
2889
+ const elements = isDOMDocumentNode(dom) ? dom.body.childNodes : dom.childNodes;
2890
+ const lexicalNodes = [];
2891
+ const allArtificialNodes = [];
2892
+ for (const element of elements) {
2893
+ if (!IGNORE_TAGS.has(element.nodeName)) {
2894
+ const lexicalNode = $createNodesFromDOM(element, editor, allArtificialNodes, false);
2895
+ if (lexicalNode !== null) {
2896
+ for (const node of lexicalNode) {
2897
+ lexicalNodes.push(node);
2898
+ }
2899
+ }
2900
+ }
2901
+ }
2902
+ $unwrapArtificialNodes(allArtificialNodes);
2903
+ return lexicalNodes;
2904
+ }
2905
+
2906
+ /**
2907
+ * Generate DOM nodes from the editor state into the given container element,
2908
+ * using the editor's {@link EditorDOMRenderConfig}.
2909
+ * @experimental
2910
+ */
2911
+ function $generateDOMFromNodes(container, selection = null, editor = $getEditor()) {
2912
+ return $withRenderContext([contextValue(RenderContextExport, true)], editor)(() => {
2913
+ const root = $getRoot();
2914
+ const domConfig = $getSessionDOMRenderConfig(editor);
2915
+ const parentElementAppend = container.append.bind(container);
2916
+ for (const topLevelNode of root.getChildren()) {
2917
+ $appendNodesToHTML(editor, topLevelNode, parentElementAppend, selection, domConfig);
2918
+ }
2919
+ return container;
2920
+ });
2921
+ }
2922
+
2923
+ /**
2924
+ * Generate DOM nodes from a root node into the given container element,
2925
+ * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}.
2926
+ * @experimental
2927
+ */
2928
+ function $generateDOMFromRoot(container, root = $getRoot()) {
2929
+ const editor = $getEditor();
2930
+ return $withRenderContext([contextValue(RenderContextExport, true), contextValue(RenderContextRoot, true)], editor)(() => {
2931
+ const selection = null;
2932
+ const domConfig = $getSessionDOMRenderConfig(editor);
2933
+ const parentElementAppend = container.append.bind(container);
2934
+ $appendNodesToHTML(editor, root, parentElementAppend, selection, domConfig);
2935
+ return container;
2936
+ });
2937
+ }
2938
+
2939
+ /**
2940
+ * Generate an HTML string from the editor's current state (or `selection`
2941
+ * if provided).
2942
+ *
2943
+ * Must be called inside an active editor scope — i.e. `editor.update(...)`,
2944
+ * `editor.read(...)`, or `editor.getEditorState().read(callback, {editor})`.
2945
+ * The legacy `editor.getEditorState().read(callback)` call (without the
2946
+ * `{editor}` option) does not set an active editor and is not supported;
2947
+ * `editor.read(...)` is the drop-in replacement.
2948
+ */
2949
+ function $generateHtmlFromNodes(editor, selection = null) {
2950
+ if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') {
2951
+ {
2952
+ formatDevErrorMessage(`To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom or use withDOM from @lexical/headless/dom before calling this function.`);
2953
+ }
2954
+ }
2955
+ return $generateDOMFromNodes(document.createElement('div'), selection, editor).innerHTML;
2956
+ }
2957
+ function $appendNodesToHTML(editor, currentNode, parentElementAppend, selection = null, domConfig = $getEditorDOMRenderConfig(editor)) {
2958
+ let shouldInclude = domConfig.$shouldInclude(currentNode, selection, editor);
2959
+ const shouldExclude = domConfig.$shouldExclude(currentNode, selection, editor);
2960
+ let target = currentNode;
2961
+ if (selection !== null && $isTextNode(currentNode)) {
2962
+ target = $sliceSelectedTextNodeContent(selection, currentNode, 'clone');
2963
+ }
2964
+ const exportProps = domConfig.$exportDOM(target, editor);
2965
+ const {
2966
+ element,
2967
+ after,
2968
+ append,
2969
+ $getChildNodes
2970
+ } = exportProps;
2971
+ if (!element) {
2972
+ return false;
2973
+ }
2974
+ const fragment = document.createDocumentFragment();
2975
+ const children = $getChildNodes ? $getChildNodes() : $isElementNode(target) ? target.getChildren() : [];
2976
+ const fragmentAppend = fragment.append.bind(fragment);
2977
+ for (const childNode of children) {
2978
+ const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragmentAppend, selection, domConfig);
2979
+ if (!shouldInclude && shouldIncludeChild && domConfig.$extractWithChild(currentNode, childNode, selection, 'html', editor)) {
2980
+ shouldInclude = true;
2981
+ }
2982
+ }
2983
+ if (shouldInclude && !shouldExclude) {
2984
+ if (isHTMLElement(element) || isDocumentFragment(element)) {
2985
+ if (append) {
2986
+ append(fragment);
2987
+ } else {
2988
+ element.append(fragment);
2989
+ }
2990
+ }
2991
+ parentElementAppend(element);
2992
+ if (after) {
2993
+ const newElement = after.call(target, element);
2994
+ if (newElement) {
2995
+ if (isDocumentFragment(element)) {
2996
+ element.replaceChildren(newElement);
2997
+ } else {
2998
+ element.replaceWith(newElement);
2999
+ }
3000
+ }
3001
+ }
3002
+ } else {
3003
+ parentElementAppend(fragment);
3004
+ }
3005
+ return shouldInclude;
3006
+ }
3007
+ function getConversionFunction(domNode, editor) {
3008
+ const {
3009
+ nodeName
3010
+ } = domNode;
3011
+ const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase());
3012
+ let currentConversion = null;
3013
+ if (cachedConversions !== undefined) {
3014
+ for (const cachedConversion of cachedConversions) {
3015
+ const domConversion = cachedConversion(domNode);
3016
+ if (domConversion !== null && (currentConversion === null ||
3017
+ // Given equal priority, prefer the last registered importer
3018
+ // which is typically an application custom node or HTMLConfig['import']
3019
+ (currentConversion.priority || 0) <= (domConversion.priority || 0))) {
3020
+ currentConversion = domConversion;
3021
+ }
3022
+ }
3023
+ }
3024
+ return currentConversion !== null ? currentConversion.conversion : null;
3025
+ }
3026
+ function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorLexicalNode, forChildMap = new Map(), parentLexicalNode) {
3027
+ const lexicalNodes = [];
3028
+ if (IGNORE_TAGS.has(node.nodeName)) {
3029
+ return lexicalNodes;
3030
+ }
3031
+ let currentLexicalNode = null;
3032
+ const transformFunction = getConversionFunction(node, editor);
3033
+ const transformOutput = transformFunction ? transformFunction(node) : null;
3034
+ let postTransform = null;
3035
+ if (transformOutput !== null) {
3036
+ postTransform = transformOutput.after;
3037
+ const transformNodes = transformOutput.node;
3038
+ currentLexicalNode = Array.isArray(transformNodes) ? transformNodes[transformNodes.length - 1] : transformNodes;
3039
+ if (currentLexicalNode !== null) {
3040
+ for (const [, forChildFunction] of forChildMap) {
3041
+ currentLexicalNode = forChildFunction(currentLexicalNode, parentLexicalNode);
3042
+ if (!currentLexicalNode) {
3043
+ break;
3044
+ }
3045
+ }
3046
+ if (currentLexicalNode) {
3047
+ lexicalNodes.push(...(Array.isArray(transformNodes) ? transformNodes : [currentLexicalNode]));
3048
+ }
3049
+ }
3050
+ if (transformOutput.forChild != null) {
3051
+ forChildMap.set(node.nodeName, transformOutput.forChild);
3052
+ }
3053
+ }
3054
+
3055
+ // If the DOM node doesn't have a transformer, we don't know what
3056
+ // to do with it but we still need to process any childNodes.
3057
+ const children = node.childNodes;
3058
+ let childLexicalNodes = [];
3059
+ const hasBlockAncestorLexicalNodeForChildren = currentLexicalNode != null && $isRootOrShadowRoot(currentLexicalNode) ? false : currentLexicalNode != null && $isBlockElementNode(currentLexicalNode) || hasBlockAncestorLexicalNode;
3060
+ for (let i = 0; i < children.length; i++) {
3061
+ childLexicalNodes.push(...$createNodesFromDOM(children[i], editor, allArtificialNodes, hasBlockAncestorLexicalNodeForChildren, new Map(forChildMap), currentLexicalNode));
3062
+ }
3063
+ if (postTransform != null) {
3064
+ childLexicalNodes = postTransform(childLexicalNodes);
3065
+ }
3066
+ if (isBlockDomNode(node)) {
3067
+ if (!hasBlockAncestorLexicalNodeForChildren) {
3068
+ childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, $createParagraphNode);
3069
+ } else {
3070
+ childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => {
3071
+ const artificialNode = new ArtificialNode__DO_NOT_USE();
3072
+ allArtificialNodes.push(artificialNode);
3073
+ return artificialNode;
3074
+ });
3075
+ }
3076
+ }
3077
+ if (currentLexicalNode == null) {
3078
+ if (childLexicalNodes.length > 0) {
3079
+ // If it hasn't been converted to a LexicalNode, we hoist its children
3080
+ // up to the same level as it.
3081
+ for (const childNode of childLexicalNodes) {
3082
+ lexicalNodes.push(childNode);
3083
+ }
3084
+ } else {
3085
+ if (isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) {
3086
+ // Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes
3087
+ lexicalNodes.push($createLineBreakNode());
3088
+ }
3089
+ }
3090
+ } else {
3091
+ if ($isElementNode(currentLexicalNode)) {
3092
+ // If the current node is a ElementNode after conversion,
3093
+ // we can append all the children to it.
3094
+ currentLexicalNode.append(...childLexicalNodes);
3095
+ }
3096
+ }
3097
+ return lexicalNodes;
3098
+ }
3099
+ function wrapContinuousInlines(domNode, nodes, createWrapperFn) {
3100
+ const textAlign = domNode.style.textAlign;
3101
+ const out = [];
3102
+ let continuousInlines = [];
3103
+ // wrap contiguous inline child nodes in para
3104
+ for (let i = 0; i < nodes.length; i++) {
3105
+ const node = nodes[i];
3106
+ if ($isBlockElementNode(node)) {
3107
+ if (textAlign && !node.getFormat()) {
3108
+ node.setFormat(textAlign);
3109
+ }
3110
+ out.push(node);
3111
+ } else {
3112
+ continuousInlines.push(node);
3113
+ if (i === nodes.length - 1 || i < nodes.length - 1 && $isBlockElementNode(nodes[i + 1])) {
3114
+ const wrapper = createWrapperFn();
3115
+ wrapper.setFormat(textAlign);
3116
+ wrapper.append(...continuousInlines);
3117
+ out.push(wrapper);
3118
+ continuousInlines = [];
3119
+ }
3120
+ }
3121
+ }
3122
+ return out;
3123
+ }
3124
+ function $unwrapArtificialNodes(allArtificialNodes) {
3125
+ // Replace artificial node with its children, inserting a linebreak
3126
+ // between adjacent artificial nodes
3127
+ for (const node of allArtificialNodes) {
3128
+ if (node.getParent() && node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE) {
3129
+ node.insertAfter($createLineBreakNode());
3130
+ }
3131
+ }
3132
+ for (const node of allArtificialNodes) {
3133
+ const parent = node.getParent();
3134
+ if (parent) {
3135
+ parent.splice(node.getIndexWithinParent(), 1, node.getChildren());
3136
+ }
3137
+ }
3138
+ }
3139
+ function isDomNodeBetweenTwoInlineNodes(node) {
3140
+ if (node.nextSibling == null || node.previousSibling == null) {
3141
+ return false;
3142
+ }
3143
+ return isInlineDomNode(node.nextSibling) && isInlineDomNode(node.previousSibling);
3144
+ }
3145
+
3146
+ export { $distributeInlineWrapper, $generateDOMFromNodes, $generateDOMFromRoot, $generateHtmlFromNodes, $generateNodesFromDOM, $generateNodesFromDOMViaExtension, $getImportContextValue, $getRenderContextValue, $getSessionDOMRenderConfig, $inlineStylesFromStyleSheets, $isBlockLevel, $setRenderContextValue, $updateRenderContextValue, $withImportContext, $withRenderContext, BlockSchema, CoreImportExtension, CoreImportRules, DOMImportExtension, DOMRenderExtension, HorizontalRuleImportExtension, HorizontalRuleImportRules, ImportOverlays, ImportSource, ImportSourceDataTransfer, ImportTextFormat, ImportTextStyle, ImportWhitespaceConfig, InlineSchema, NestedBlockSchema, RenderContextExport, RenderContextRoot, RootSchema, contextUpdater, contextValue, createImportState, createRenderState, defaultIsInline, defaultPreservesWhitespace, defineImportRule, defineOverlayRules, domOverride, isElementOfTag, parseSelector, sel };