@lexical/html 0.43.1-nightly.20260416.0 → 0.44.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.
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  import { $sliceSelectedTextNodeContent } from '@lexical/selection';
10
- import { isHTMLElement, isBlockDomNode } from '@lexical/utils';
11
- import { isDOMDocumentNode, $getRoot, $isElementNode, $isTextNode, getRegisteredNode, isDocumentFragment, $isRootOrShadowRoot, $isBlockElementNode, $createLineBreakNode, ArtificialNode__DO_NOT_USE, isInlineDomNode, $createParagraphNode } from 'lexical';
10
+ import { $getEditor, createState, DEFAULT_EDITOR_DOM_CONFIG, $isLexicalNode, getStaticNodeConfig, defineExtension, shallowMergeConfig, RootNode, $getRoot, $getEditorDOMRenderConfig, isDOMDocumentNode, $isTextNode, $isElementNode, isHTMLElement, isDocumentFragment, $isRootOrShadowRoot, $isBlockElementNode, isBlockDomNode, $createLineBreakNode, ArtificialNode__DO_NOT_USE, isInlineDomNode, $createParagraphNode } from 'lexical';
11
+ import { getKnownTypesAndNodes, LexicalBuilder, getExtensionDependencyFromEditor } from '@lexical/extension';
12
12
 
13
13
  /**
14
14
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -18,6 +18,565 @@ import { isDOMDocumentNode, $getRoot, $isElementNode, $isTextNode, getRegistered
18
18
  *
19
19
  */
20
20
 
21
+ // Do not require this module directly! Use normal `invariant` calls.
22
+
23
+ function formatDevErrorMessage(message) {
24
+ throw new Error(message);
25
+ }
26
+
27
+ /**
28
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
29
+ *
30
+ * This source code is licensed under the MIT license found in the
31
+ * LICENSE file in the root directory of this source tree.
32
+ *
33
+ */
34
+
35
+ let activeContext;
36
+
37
+ /**
38
+ * @experimental
39
+ *
40
+ * The LexicalEditor with context
41
+ */
42
+
43
+ /**
44
+ * @experimental
45
+ *
46
+ * @param contextRecord The ContextRecord
47
+ * @param cfg The configuration
48
+ * @returns The value or defaultValue of cfg
49
+ */
50
+ function getContextValue(contextRecord, cfg) {
51
+ const {
52
+ key
53
+ } = cfg;
54
+ return contextRecord && key in contextRecord ? contextRecord[key] : cfg.defaultValue;
55
+ }
56
+ function getEditorContext(editor) {
57
+ return activeContext && activeContext.editor === editor ? activeContext : undefined;
58
+ }
59
+
60
+ /**
61
+ * @experimental
62
+ *
63
+ * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol)
64
+ * @param editor The editor
65
+ * @returns The current context or undefined
66
+ */
67
+ function getContextRecord(sym, editor) {
68
+ const editorContext = getEditorContext(editor);
69
+ return editorContext && editorContext[sym];
70
+ }
71
+ function toPair(contextRecord, pairOrUpdater) {
72
+ if ('cfg' in pairOrUpdater) {
73
+ const {
74
+ cfg,
75
+ updater
76
+ } = pairOrUpdater;
77
+ return [cfg, updater(getContextValue(contextRecord, cfg))];
78
+ }
79
+ return pairOrUpdater;
80
+ }
81
+
82
+ /**
83
+ * Construct a new context from a parent context and pairs
84
+ *
85
+ * @param pairs The pairs and updaters to build the context from
86
+ * @param parent The parent context
87
+ * @returns The new context
88
+ */
89
+ function contextFromPairs(pairs, parent) {
90
+ let rval = parent;
91
+ for (const pairOrUpdater of pairs) {
92
+ const [k, v] = toPair(rval, pairOrUpdater);
93
+ const key = k.key;
94
+ if (rval === parent && getContextValue(rval, k) === v) {
95
+ continue;
96
+ }
97
+ const ctx = rval || createChildContext(parent);
98
+ ctx[key] = v;
99
+ rval = ctx;
100
+ }
101
+ return rval;
102
+ }
103
+ function createChildContext(parent) {
104
+ return Object.create(parent || null);
105
+ }
106
+
107
+ /**
108
+ * Create a context config pair that sets a value in the render context.
109
+ * @experimental
110
+ */
111
+ function contextValue(cfg, value) {
112
+ return [cfg, value];
113
+ }
114
+
115
+ /**
116
+ * Create a context config updater that transforms a value in the render context.
117
+ * @experimental
118
+ */
119
+ function contextUpdater(cfg, updater) {
120
+ return {
121
+ cfg,
122
+ updater
123
+ };
124
+ }
125
+
126
+ /**
127
+ * @internal
128
+ * @experimental
129
+ * @__NO_SIDE_EFFECTS__
130
+ */
131
+ function $withFullContext(sym, contextRecord, f, editor = $getEditor()) {
132
+ const prevDOMContext = activeContext;
133
+ const parentEditorContext = getEditorContext(editor);
134
+ try {
135
+ activeContext = {
136
+ ...parentEditorContext,
137
+ editor,
138
+ [sym]: contextRecord
139
+ };
140
+ return f();
141
+ } finally {
142
+ activeContext = prevDOMContext;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * @internal
148
+ * @experimental
149
+ * @__NO_SIDE_EFFECTS__
150
+ */
151
+ function $withContext(sym, $defaults = () => undefined) {
152
+ return (cfg, editor = $getEditor()) => {
153
+ return f => {
154
+ const parentEditorContext = getEditorContext(editor);
155
+ const parentContextRecord = parentEditorContext && parentEditorContext[sym];
156
+ const contextRecord = contextFromPairs(cfg, parentContextRecord || $defaults(editor));
157
+ if (!contextRecord || contextRecord === parentContextRecord) {
158
+ return f();
159
+ }
160
+ return $withFullContext(sym, contextRecord, f, editor);
161
+ };
162
+ };
163
+ }
164
+
165
+ /**
166
+ * @experimental
167
+ * @internal
168
+ * @__NO_SIDE_EFFECTS__
169
+ */
170
+ function createContextState(tag, name, getDefaultValue, isEqual) {
171
+ return Object.assign(createState(Symbol(name), {
172
+ isEqual,
173
+ parse: getDefaultValue
174
+ }), {
175
+ [tag]: true
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
181
+ *
182
+ * This source code is licensed under the MIT license found in the
183
+ * LICENSE file in the root directory of this source tree.
184
+ *
185
+ */
186
+ const DOMRenderExtensionName = '@lexical/html/DOM';
187
+ const DOMRenderContextSymbol = Symbol.for('@lexical/html/DOMExportContext');
188
+ const ALWAYS_TRUE = () => true;
189
+
190
+ function buildTypeTree(editorConfig) {
191
+ const t = {};
192
+ const {
193
+ nodes
194
+ } = getKnownTypesAndNodes(editorConfig);
195
+ for (const klass of nodes) {
196
+ const type = klass.getType();
197
+ t[type] = {
198
+ klass,
199
+ types: {}
200
+ };
201
+ }
202
+ for (const baseRec of Object.values(t)) {
203
+ if (baseRec) {
204
+ const baseType = baseRec.klass.getType();
205
+ for (let {
206
+ klass
207
+ } = baseRec; $isLexicalNode(klass.prototype); klass = Object.getPrototypeOf(klass)) {
208
+ const {
209
+ ownNodeType
210
+ } = getStaticNodeConfig(klass);
211
+ const superRec = ownNodeType && t[ownNodeType];
212
+ if (superRec) {
213
+ superRec.types[baseType] = true;
214
+ }
215
+ }
216
+ }
217
+ }
218
+ return t;
219
+ }
220
+ function buildNodePredicate(klass) {
221
+ return node => node instanceof klass;
222
+ }
223
+ function getPredicate(typeTree, {
224
+ nodes
225
+ }) {
226
+ if (nodes === '*') {
227
+ return ALWAYS_TRUE;
228
+ }
229
+ let types = {};
230
+ const predicates = [];
231
+ for (const klassOrPredicate of nodes) {
232
+ if ('getType' in klassOrPredicate) {
233
+ const type = klassOrPredicate.getType();
234
+ if (types) {
235
+ const tree = typeTree[type];
236
+ if (!(tree !== undefined)) {
237
+ formatDevErrorMessage(`Node class ${klassOrPredicate.name} with type ${type} not registered in editor`);
238
+ }
239
+ types = Object.assign(types, tree.types);
240
+ }
241
+ predicates.push(buildNodePredicate(klassOrPredicate));
242
+ } else {
243
+ types = undefined;
244
+ predicates.push(klassOrPredicate);
245
+ }
246
+ }
247
+ if (types) {
248
+ return types;
249
+ } else if (predicates.length === 1) {
250
+ return predicates[0];
251
+ }
252
+ return node => {
253
+ for (const predicate of predicates) {
254
+ if (predicate(node)) {
255
+ return true;
256
+ }
257
+ }
258
+ return false;
259
+ };
260
+ }
261
+ function makePrerender() {
262
+ return {
263
+ $createDOM: [],
264
+ $decorateDOM: [],
265
+ $exportDOM: [],
266
+ $extractWithChild: [],
267
+ $getDOMSlot: [],
268
+ $shouldExclude: [],
269
+ $shouldInclude: [],
270
+ $updateDOM: []
271
+ };
272
+ }
273
+
274
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
+
276
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
277
+
278
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
279
+
280
+ function ignoreNext2(acc) {
281
+ return (node, _$next, editor) => acc(node, editor);
282
+ }
283
+ function ignoreNext3(acc) {
284
+ return (node, a, _$next, editor) => acc(node, a, editor);
285
+ }
286
+ function ignoreNext4(acc) {
287
+ return (node, a, b, _$next, editor) => acc(node, a, b, editor);
288
+ }
289
+ function ignoreNext5(acc) {
290
+ return (node, a, b, c, _$next, editor) => acc(node, a, b, c, editor);
291
+ }
292
+ function merge2($acc, $getOverride) {
293
+ return (node, editor) => {
294
+ const $next = () => $acc(node, editor);
295
+ const $override = $getOverride(node);
296
+ return $override ? $override(node, $next, editor) : $next();
297
+ };
298
+ }
299
+ function merge3(acc, $getOverride) {
300
+ return (node, a, editor) => {
301
+ const $next = () => acc(node, a, editor);
302
+ const $override = $getOverride(node);
303
+ return $override ? $override(node, a, $next, editor) : $next();
304
+ };
305
+ }
306
+ function merge4($acc, $getOverride) {
307
+ return (node, a, b, editor) => {
308
+ const $next = () => $acc(node, a, b, editor);
309
+ const $override = $getOverride(node);
310
+ return $override ? $override(node, a, b, $next, editor) : $next();
311
+ };
312
+ }
313
+ function merge5(acc, $getOverride) {
314
+ return (node, a, b, c, editor) => {
315
+ const $next = () => acc(node, a, b, c, editor);
316
+ const $override = $getOverride(node);
317
+ return $override ? $override(node, a, b, c, $next, editor) : $next();
318
+ };
319
+ }
320
+ function sequence4($acc, $getOverride) {
321
+ return (node, a, b, editor) => {
322
+ $acc(node, a, b, editor);
323
+ const $override = $getOverride(node);
324
+ if ($override) {
325
+ $override(node, a, b, editor);
326
+ }
327
+ };
328
+ }
329
+ function compilePrerenderKey(prerender, k, defaults, mergeFunction, ignoreNextFunction) {
330
+ let acc = defaults[k];
331
+ for (const pair of prerender[k]) {
332
+ if (typeof pair[0] === 'function') {
333
+ const [$predicate, $override] = pair;
334
+ acc = mergeFunction(acc, node => $predicate(node) && $override || undefined);
335
+ } else {
336
+ const typeOverrides = pair[1];
337
+ const compiled = {};
338
+ for (const type in typeOverrides) {
339
+ const arr = typeOverrides[type];
340
+ if (arr) {
341
+ compiled[type] = arr.reduce(($acc, $override) => mergeFunction($acc, () => $override), acc);
342
+ }
343
+ }
344
+ acc = mergeFunction(acc, node => {
345
+ const f = compiled[node.getType()];
346
+ return f && ignoreNextFunction(f);
347
+ });
348
+ }
349
+ }
350
+ defaults[k] = acc;
351
+ }
352
+ function addOverride(prerender, k, predicateOrTypes, override) {
353
+ if (!override) {
354
+ return;
355
+ }
356
+ const arr = prerender[k];
357
+ if (typeof predicateOrTypes === 'function') {
358
+ arr.push([predicateOrTypes, override]);
359
+ } else {
360
+ const last = arr[arr.length - 1];
361
+ let types;
362
+ if (last && last[0] === 'types') {
363
+ types = last[1];
364
+ } else {
365
+ types = {};
366
+ arr.push(['types', types]);
367
+ }
368
+ for (const type in predicateOrTypes) {
369
+ const typeArr = types[type] || [];
370
+ types[type] = typeArr;
371
+ typeArr.push(override);
372
+ }
373
+ }
374
+ }
375
+ function isWildcard(override) {
376
+ return override.nodes === '*';
377
+ }
378
+ function sortedOverrides(overrides) {
379
+ const byWildcard = [];
380
+ const byPredicate = [];
381
+ const byNode = [];
382
+ for (const override of overrides) {
383
+ if (isWildcard(override)) {
384
+ byWildcard.push(override);
385
+ } else if (Array.isArray(override.nodes)) {
386
+ for (const klassOrPredicate of override.nodes) {
387
+ if ($isLexicalNode(klassOrPredicate.prototype)) {
388
+ byNode.push(override.nodes.length === 1 ? override : {
389
+ ...override,
390
+ nodes: [klassOrPredicate]
391
+ });
392
+ } else {
393
+ byPredicate.push(override.nodes.length === 1 ? override : {
394
+ ...override,
395
+ nodes: [klassOrPredicate]
396
+ });
397
+ }
398
+ }
399
+ }
400
+ }
401
+ const depths = new Map();
402
+ const depthOf = klass => {
403
+ let depth = depths.get(klass);
404
+ if (depth === undefined) {
405
+ depth = 0;
406
+ for (let k = klass; $isLexicalNode(k.prototype); k = Object.getPrototypeOf(k)) {
407
+ depth++;
408
+ }
409
+ depths.set(klass, depth);
410
+ }
411
+ return depth;
412
+ };
413
+ byNode.sort((a, b) => depthOf(a.nodes[0]) - depthOf(b.nodes[0]));
414
+ return [...byNode, ...byPredicate, ...byWildcard];
415
+ }
416
+ function precompileDOMRenderConfigOverrides(editorConfig, overrides) {
417
+ const typeTree = buildTypeTree(editorConfig);
418
+ const prerender = makePrerender();
419
+ for (const override of sortedOverrides(overrides)) {
420
+ const predicateOrTypes = getPredicate(typeTree, override);
421
+ for (const k_ in prerender) {
422
+ const k = k_;
423
+ addOverride(prerender, k, predicateOrTypes, override[k]);
424
+ }
425
+ }
426
+ return prerender;
427
+ }
428
+ function identity(v) {
429
+ return v;
430
+ }
431
+ function compileDOMRenderConfigOverrides(editorConfig, {
432
+ overrides
433
+ }) {
434
+ const prerender = precompileDOMRenderConfigOverrides(editorConfig, overrides);
435
+ const dom = {
436
+ ...DEFAULT_EDITOR_DOM_CONFIG,
437
+ ...editorConfig.dom
438
+ };
439
+ compilePrerenderKey(prerender, '$createDOM', dom, merge2, ignoreNext2);
440
+ compilePrerenderKey(prerender, '$exportDOM', dom, merge2, ignoreNext2);
441
+ compilePrerenderKey(prerender, '$extractWithChild', dom, merge5, ignoreNext5);
442
+ compilePrerenderKey(prerender, '$getDOMSlot', dom, merge3, ignoreNext3);
443
+ compilePrerenderKey(prerender, '$shouldExclude', dom, merge3, ignoreNext3);
444
+ compilePrerenderKey(prerender, '$shouldInclude', dom, merge3, ignoreNext3);
445
+ compilePrerenderKey(prerender, '$updateDOM', dom, merge4, ignoreNext4);
446
+ compilePrerenderKey(prerender, '$decorateDOM', dom, sequence4, identity);
447
+ return dom;
448
+ }
449
+
450
+ /**
451
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
452
+ *
453
+ * This source code is licensed under the MIT license found in the
454
+ * LICENSE file in the root directory of this source tree.
455
+ *
456
+ */
457
+
458
+
459
+ /**
460
+ * @experimental
461
+ *
462
+ * An extension that allows overriding the render and export behavior for an
463
+ * editor. This is highly experimental and subject to change from one version
464
+ * to the next.
465
+ **/
466
+ const DOMRenderExtension = defineExtension({
467
+ build(editor, config, state) {
468
+ return {
469
+ defaults: contextFromPairs(config.contextDefaults, undefined)
470
+ };
471
+ },
472
+ config: {
473
+ contextDefaults: [],
474
+ overrides: []
475
+ },
476
+ html: {
477
+ // Define a RootNode export for $generateDOMFromRoot
478
+ export: new Map([[RootNode, () => {
479
+ const element = document.createElement('div');
480
+ element.role = 'textbox';
481
+ return {
482
+ element
483
+ };
484
+ }]])
485
+ },
486
+ init(editorConfig, config) {
487
+ editorConfig.dom = compileDOMRenderConfigOverrides(editorConfig, config);
488
+ },
489
+ mergeConfig(config, partial) {
490
+ const merged = shallowMergeConfig(config, partial);
491
+ for (const k of ['overrides', 'contextDefaults']) {
492
+ if (partial[k]) {
493
+ merged[k] = [...config[k], ...partial[k]];
494
+ }
495
+ }
496
+ return merged;
497
+ },
498
+ name: DOMRenderExtensionName
499
+ });
500
+
501
+ /**
502
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
503
+ *
504
+ * This source code is licensed under the MIT license found in the
505
+ * LICENSE file in the root directory of this source tree.
506
+ *
507
+ */
508
+ /**
509
+ * Create a context state to be used during render.
510
+ *
511
+ * Note that to support the ValueOrUpdater pattern you can not use a
512
+ * function for V (but you may wrap it in an array or object).
513
+ *
514
+ * @experimental
515
+ * @__NO_SIDE_EFFECTS__
516
+ */
517
+ function createRenderState(name, getDefaultValue, isEqual) {
518
+ return createContextState(DOMRenderContextSymbol, name, getDefaultValue, isEqual);
519
+ }
520
+
521
+ /**
522
+ * Render context state that is true if the export was initiated from the root of the document.
523
+ * @experimental
524
+ */
525
+ const RenderContextRoot = createRenderState('root', Boolean);
526
+
527
+ /**
528
+ * Render context state that is true if this is an export operation ($generateHtmlFromNodes).
529
+ * @experimental
530
+ */
531
+ const RenderContextExport = createRenderState('isExport', Boolean);
532
+ function getDefaultRenderContext(editor) {
533
+ const builder = LexicalBuilder.maybeFromEditor(editor);
534
+ return builder && builder.hasExtensionByName(DOMRenderExtensionName) ? getExtensionDependencyFromEditor(editor, DOMRenderExtension).output.defaults : undefined;
535
+ }
536
+ function getRenderContext(editor) {
537
+ return getContextRecord(DOMRenderContextSymbol, editor) || getDefaultRenderContext(editor);
538
+ }
539
+
540
+ /**
541
+ * Get a render context value during a DOM render or export operation.
542
+ * @experimental
543
+ */
544
+ function $getRenderContextValue(cfg, editor = $getEditor()) {
545
+ return getContextValue(getRenderContext(editor), cfg);
546
+ }
547
+
548
+ /**
549
+ * Execute a callback within a render context with the given config pairs.
550
+ * @experimental
551
+ */
552
+ const $withRenderContext = $withContext(DOMRenderContextSymbol, getDefaultRenderContext);
553
+
554
+ /**
555
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
556
+ *
557
+ * This source code is licensed under the MIT license found in the
558
+ * LICENSE file in the root directory of this source tree.
559
+ *
560
+ */
561
+
562
+ /**
563
+ * A convenience function for type inference when constructing DOM overrides for
564
+ * use with {@link DOMRenderExtension}.
565
+ *
566
+ * @experimental
567
+ * @__NO_SIDE_EFFECTS__
568
+ */
569
+
570
+ function domOverride(nodes, config) {
571
+ return {
572
+ ...config,
573
+ nodes
574
+ };
575
+ }
576
+
577
+ function isStyleRule(rule) {
578
+ return rule.constructor.name === CSSStyleRule.name;
579
+ }
21
580
 
22
581
  /**
23
582
  * Inlines CSS rules from <style> tags onto matching elements as inline styles.
@@ -54,7 +613,7 @@ function inlineStylesFromStyleSheets(doc) {
54
613
  continue;
55
614
  }
56
615
  for (const rule of Array.from(rules)) {
57
- if (!(rule instanceof CSSStyleRule)) {
616
+ if (!isStyleRule(rule)) {
58
617
  continue;
59
618
  }
60
619
  let elements;
@@ -64,7 +623,7 @@ function inlineStylesFromStyleSheets(doc) {
64
623
  continue;
65
624
  }
66
625
  for (const el of Array.from(elements)) {
67
- if (!(el instanceof HTMLElement)) {
626
+ if (!isHTMLElement(el)) {
68
627
  continue;
69
628
  }
70
629
  const originalProps = getOriginalInlineProps(el);
@@ -81,6 +640,7 @@ function inlineStylesFromStyleSheets(doc) {
81
640
  // styleSheets API not supported in this environment
82
641
  }
83
642
  }
643
+ const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
84
644
 
85
645
  /**
86
646
  * How you parse your html string to get a document is left up to you. In the browser you can use the native
@@ -92,69 +652,97 @@ function $generateNodesFromDOM(editor, dom) {
92
652
  inlineStylesFromStyleSheets(dom);
93
653
  }
94
654
  const elements = isDOMDocumentNode(dom) ? dom.body.childNodes : dom.childNodes;
95
- let lexicalNodes = [];
655
+ const lexicalNodes = [];
96
656
  const allArtificialNodes = [];
97
657
  for (const element of elements) {
98
658
  if (!IGNORE_TAGS.has(element.nodeName)) {
99
659
  const lexicalNode = $createNodesFromDOM(element, editor, allArtificialNodes, false);
100
660
  if (lexicalNode !== null) {
101
- lexicalNodes = lexicalNodes.concat(lexicalNode);
661
+ for (const node of lexicalNode) {
662
+ lexicalNodes.push(node);
663
+ }
102
664
  }
103
665
  }
104
666
  }
105
667
  $unwrapArtificialNodes(allArtificialNodes);
106
668
  return lexicalNodes;
107
669
  }
108
- function $generateHtmlFromNodes(editor, selection) {
670
+
671
+ /**
672
+ * Generate DOM nodes from the editor state into the given container element,
673
+ * using the editor's {@link EditorDOMRenderConfig}.
674
+ * @experimental
675
+ */
676
+ function $generateDOMFromNodes(container, selection = null, editor = $getEditor()) {
677
+ return $withRenderContext([contextValue(RenderContextExport, true)], editor)(() => {
678
+ const root = $getRoot();
679
+ const domConfig = $getEditorDOMRenderConfig(editor);
680
+ const parentElementAppend = container.append.bind(container);
681
+ for (const topLevelNode of root.getChildren()) {
682
+ $appendNodesToHTML(editor, topLevelNode, parentElementAppend, selection, domConfig);
683
+ }
684
+ return container;
685
+ });
686
+ }
687
+
688
+ /**
689
+ * Generate DOM nodes from a root node into the given container element,
690
+ * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}.
691
+ * @experimental
692
+ */
693
+ function $generateDOMFromRoot(container, root = $getRoot()) {
694
+ const editor = $getEditor();
695
+ return $withRenderContext([contextValue(RenderContextExport, true), contextValue(RenderContextRoot, true)], editor)(() => {
696
+ const selection = null;
697
+ const domConfig = $getEditorDOMRenderConfig(editor);
698
+ const parentElementAppend = container.append.bind(container);
699
+ $appendNodesToHTML(editor, root, parentElementAppend, selection, domConfig);
700
+ return container;
701
+ });
702
+ }
703
+ function $generateHtmlFromNodes(editor, selection = null) {
109
704
  if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') {
110
- throw new Error('To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.');
111
- }
112
- const container = document.createElement('div');
113
- const root = $getRoot();
114
- const topLevelChildren = root.getChildren();
115
- for (let i = 0; i < topLevelChildren.length; i++) {
116
- const topLevelNode = topLevelChildren[i];
117
- $appendNodesToHTML(editor, topLevelNode, container, selection);
705
+ {
706
+ 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.`);
707
+ }
118
708
  }
119
- return container.innerHTML;
709
+ return $generateDOMFromNodes(document.createElement('div'), selection, editor).innerHTML;
120
710
  }
121
- function $appendNodesToHTML(editor, currentNode, parentElement, selection = null) {
122
- let shouldInclude = selection !== null ? currentNode.isSelected(selection) : true;
123
- const shouldExclude = $isElementNode(currentNode) && currentNode.excludeFromCopy('html');
711
+ function $appendNodesToHTML(editor, currentNode, parentElementAppend, selection = null, domConfig = $getEditorDOMRenderConfig(editor)) {
712
+ let shouldInclude = domConfig.$shouldInclude(currentNode, selection, editor);
713
+ const shouldExclude = domConfig.$shouldExclude(currentNode, selection, editor);
124
714
  let target = currentNode;
125
715
  if (selection !== null && $isTextNode(currentNode)) {
126
716
  target = $sliceSelectedTextNodeContent(selection, currentNode, 'clone');
127
717
  }
128
- const children = $isElementNode(target) ? target.getChildren() : [];
129
- const registeredNode = getRegisteredNode(editor, target.getType());
130
- let exportOutput;
131
-
132
- // Use HTMLConfig overrides, if available.
133
- if (registeredNode && registeredNode.exportDOM !== undefined) {
134
- exportOutput = registeredNode.exportDOM(editor, target);
135
- } else {
136
- exportOutput = target.exportDOM(editor);
137
- }
718
+ const exportProps = domConfig.$exportDOM(target, editor);
138
719
  const {
139
720
  element,
140
- after
141
- } = exportOutput;
721
+ after,
722
+ append,
723
+ $getChildNodes
724
+ } = exportProps;
142
725
  if (!element) {
143
726
  return false;
144
727
  }
145
728
  const fragment = document.createDocumentFragment();
146
- for (let i = 0; i < children.length; i++) {
147
- const childNode = children[i];
148
- const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragment, selection);
149
- if (!shouldInclude && $isElementNode(currentNode) && shouldIncludeChild && currentNode.extractWithChild(childNode, selection, 'html')) {
729
+ const children = $getChildNodes ? $getChildNodes() : $isElementNode(target) ? target.getChildren() : [];
730
+ const fragmentAppend = fragment.append.bind(fragment);
731
+ for (const childNode of children) {
732
+ const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragmentAppend, selection, domConfig);
733
+ if (!shouldInclude && shouldIncludeChild && domConfig.$extractWithChild(currentNode, childNode, selection, 'html', editor)) {
150
734
  shouldInclude = true;
151
735
  }
152
736
  }
153
737
  if (shouldInclude && !shouldExclude) {
154
738
  if (isHTMLElement(element) || isDocumentFragment(element)) {
155
- element.append(fragment);
739
+ if (append) {
740
+ append(fragment);
741
+ } else {
742
+ element.append(fragment);
743
+ }
156
744
  }
157
- parentElement.append(element);
745
+ parentElementAppend(element);
158
746
  if (after) {
159
747
  const newElement = after.call(target, element);
160
748
  if (newElement) {
@@ -166,7 +754,7 @@ function $appendNodesToHTML(editor, currentNode, parentElement, selection = null
166
754
  }
167
755
  }
168
756
  } else {
169
- parentElement.append(fragment);
757
+ parentElementAppend(fragment);
170
758
  }
171
759
  return shouldInclude;
172
760
  }
@@ -189,9 +777,8 @@ function getConversionFunction(domNode, editor) {
189
777
  }
190
778
  return currentConversion !== null ? currentConversion.conversion : null;
191
779
  }
192
- const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']);
193
780
  function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorLexicalNode, forChildMap = new Map(), parentLexicalNode) {
194
- let lexicalNodes = [];
781
+ const lexicalNodes = [];
195
782
  if (IGNORE_TAGS.has(node.nodeName)) {
196
783
  return lexicalNodes;
197
784
  }
@@ -245,11 +832,13 @@ function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorL
245
832
  if (childLexicalNodes.length > 0) {
246
833
  // If it hasn't been converted to a LexicalNode, we hoist its children
247
834
  // up to the same level as it.
248
- lexicalNodes = lexicalNodes.concat(childLexicalNodes);
835
+ for (const childNode of childLexicalNodes) {
836
+ lexicalNodes.push(childNode);
837
+ }
249
838
  } else {
250
839
  if (isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) {
251
840
  // Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes
252
- lexicalNodes = lexicalNodes.concat($createLineBreakNode());
841
+ lexicalNodes.push($createLineBreakNode());
253
842
  }
254
843
  }
255
844
  } else {
@@ -287,18 +876,18 @@ function wrapContinuousInlines(domNode, nodes, createWrapperFn) {
287
876
  return out;
288
877
  }
289
878
  function $unwrapArtificialNodes(allArtificialNodes) {
879
+ // Replace artificial node with its children, inserting a linebreak
880
+ // between adjacent artificial nodes
290
881
  for (const node of allArtificialNodes) {
291
- if (node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE) {
882
+ if (node.getParent() && node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE) {
292
883
  node.insertAfter($createLineBreakNode());
293
884
  }
294
885
  }
295
- // Replace artificial node with it's children
296
886
  for (const node of allArtificialNodes) {
297
- const children = node.getChildren();
298
- for (const child of children) {
299
- node.insertBefore(child);
887
+ const parent = node.getParent();
888
+ if (parent) {
889
+ parent.splice(node.getIndexWithinParent(), 1, node.getChildren());
300
890
  }
301
- node.remove();
302
891
  }
303
892
  }
304
893
  function isDomNodeBetweenTwoInlineNodes(node) {
@@ -308,4 +897,4 @@ function isDomNodeBetweenTwoInlineNodes(node) {
308
897
  return isInlineDomNode(node.nextSibling) && isInlineDomNode(node.previousSibling);
309
898
  }
310
899
 
311
- export { $generateHtmlFromNodes, $generateNodesFromDOM };
900
+ export { $generateDOMFromNodes, $generateDOMFromRoot, $generateHtmlFromNodes, $generateNodesFromDOM, $getRenderContextValue, $withRenderContext, DOMRenderExtension, RenderContextExport, RenderContextRoot, contextUpdater, contextValue, createRenderState, domOverride };