@lexical/html 0.44.1-nightly.20260519.0 → 0.45.1-dev.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 +3289 -0
  4. package/dist/LexicalHtml.dev.mjs +3242 -0
  5. package/{LexicalHtml.js.flow → dist/LexicalHtml.js.flow} +16 -16
  6. package/dist/LexicalHtml.mjs +57 -0
  7. package/dist/LexicalHtml.node.mjs +55 -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 +28 -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 +106 -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 +52 -0
  42. package/src/import/ImportContext.ts +339 -0
  43. package/src/import/compileImportRules.ts +178 -0
  44. package/src/import/coreImportRules.ts +545 -0
  45. package/src/import/defineImportRule.ts +40 -0
  46. package/src/import/defineOverlayRules.ts +105 -0
  47. package/src/import/index.ts +97 -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 +280 -0
  52. package/src/import/sel.ts +314 -0
  53. package/src/import/types.ts +471 -0
  54. package/src/index.ts +561 -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,97 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import {parseSelector} from './parseCss';
9
+ import {selBase} from './sel';
10
+
11
+ /**
12
+ * Combinator-and-parser-based builder for {@link CompiledSelector}s. The
13
+ * runtime shape returned by these factory methods is opaque; consumers
14
+ * should never inspect or construct selector objects directly.
15
+ *
16
+ * @experimental
17
+ */
18
+ export const sel = {
19
+ any: selBase.any,
20
+ comment: selBase.comment,
21
+ /**
22
+ * Parse a reduced CSS-selector subset and return a builder you can chain
23
+ * combinator methods off of.
24
+ */
25
+ css: parseSelector,
26
+ tag: selBase.tag,
27
+ text: selBase.text,
28
+ } as const;
29
+
30
+ export {CoreImportExtension} from './CoreImportExtension';
31
+ export {CoreImportRules} from './coreImportRules';
32
+ export {defineImportRule} from './defineImportRule';
33
+ export {
34
+ type CompiledOverlayRules,
35
+ defineOverlayRules,
36
+ type DOMImportRuleEntry,
37
+ } from './defineOverlayRules';
38
+ export {
39
+ $generateNodesFromDOMViaExtension,
40
+ type DOMImportConfig,
41
+ DOMImportExtension,
42
+ } from './DOMImportExtension';
43
+ export {
44
+ HorizontalRuleImportExtension,
45
+ HorizontalRuleImportRules,
46
+ } from './HorizontalRuleImportExtension';
47
+ export {
48
+ $getImportContextValue,
49
+ $withImportContext,
50
+ createImportState,
51
+ defaultIsInline,
52
+ defaultPreservesWhitespace,
53
+ ImportOverlays,
54
+ ImportSource,
55
+ ImportSourceDataTransfer,
56
+ type ImportSourceKind,
57
+ ImportTextFormat,
58
+ ImportTextStyle,
59
+ ImportWhitespaceConfig,
60
+ type IsInlineForWhitespace,
61
+ type IsPreserveWhitespaceDom,
62
+ type WhitespaceImportConfig,
63
+ } from './ImportContext';
64
+ export {$inlineStylesFromStyleSheets} from './inlineStylesFromStyleSheets';
65
+ export {parseSelector} from './parseCss';
66
+ export {
67
+ $distributeInlineWrapper,
68
+ $isBlockLevel,
69
+ $propagateTextAlignToBlockChildren,
70
+ BlockSchema,
71
+ InlineSchema,
72
+ NestedBlockSchema,
73
+ RootSchema,
74
+ } from './schemas';
75
+ export {isElementOfTag} from './sel';
76
+ export type {
77
+ AnyDOMImportRule,
78
+ AttrMatchOptions,
79
+ CapturesOfSelector,
80
+ ChildSchema,
81
+ CompiledSelector,
82
+ DOMImportContext,
83
+ DOMImportExtensionOutput,
84
+ DOMImportFn,
85
+ DOMImportRule,
86
+ DOMPreprocessContext,
87
+ DOMPreprocessFn,
88
+ ElementSelectorBuilder,
89
+ GenerateNodesFromDOMOptions,
90
+ ImportChildrenOpts,
91
+ ImportContextPairOrUpdater,
92
+ ImportNodeOpts,
93
+ ImportSession,
94
+ ImportStateConfig,
95
+ NodeOfSelector,
96
+ StyleMatchOptions,
97
+ } from './types';
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type {DOMPreprocessFn} from './types';
9
+
10
+ import {objectKlassEquals} from '@lexical/utils';
11
+ import {isDOMDocumentNode, isHTMLElement} from 'lexical';
12
+
13
+ /**
14
+ * Inlines CSS rules from `<style>` tags onto matching elements as inline
15
+ * styles.
16
+ *
17
+ * Used by apps like Excel that generate HTML where styles live in
18
+ * class-based `<style>` rules (e.g. `.xl65 { background: #FFFF00; color:
19
+ * blue; }`) rather than inline styles. Since Lexical's import converters
20
+ * read inline styles, we resolve stylesheet rules into inline styles
21
+ * before conversion.
22
+ *
23
+ * Mutates the DOM in-place. Original inline styles always take
24
+ * precedence over stylesheet rules (matching CSS specificity behavior).
25
+ *
26
+ * No-op for {@link ParentNode}s that are not {@link Document}s — only a
27
+ * full document carries `styleSheets` we can iterate.
28
+ *
29
+ * @experimental
30
+ */
31
+ export const $inlineStylesFromStyleSheets: DOMPreprocessFn = (
32
+ dom,
33
+ _ctx,
34
+ $next,
35
+ ) => {
36
+ $inlineStylesFromStyleSheetsDOM(dom);
37
+ $next();
38
+ };
39
+
40
+ export function $inlineStylesFromStyleSheetsDOM(
41
+ dom: Document | ParentNode,
42
+ ): void {
43
+ if (!isDOMDocumentNode(dom)) {
44
+ return;
45
+ }
46
+ const doc = dom;
47
+ if (doc.querySelector('style') === null) {
48
+ return;
49
+ }
50
+
51
+ const originalInlineStyles = new Map<HTMLElement, Set<string>>();
52
+
53
+ function getOriginalInlineProps(el: HTMLElement): Set<string> {
54
+ let props = originalInlineStyles.get(el);
55
+ if (props === undefined) {
56
+ props = new Set<string>();
57
+ for (let i = 0; i < el.style.length; i++) {
58
+ props.add(el.style[i]);
59
+ }
60
+ originalInlineStyles.set(el, props);
61
+ }
62
+ return props;
63
+ }
64
+
65
+ try {
66
+ for (const sheet of Array.from(doc.styleSheets)) {
67
+ let rules: CSSRuleList;
68
+ try {
69
+ rules = sheet.cssRules;
70
+ } catch {
71
+ continue;
72
+ }
73
+ for (const rule of Array.from(rules)) {
74
+ if (!objectKlassEquals(rule, CSSStyleRule)) {
75
+ continue;
76
+ }
77
+ let elements: NodeListOf<Element>;
78
+ try {
79
+ elements = doc.querySelectorAll(rule.selectorText);
80
+ } catch {
81
+ continue;
82
+ }
83
+ for (const el of Array.from(elements)) {
84
+ if (!isHTMLElement(el)) {
85
+ continue;
86
+ }
87
+ const originalProps = getOriginalInlineProps(el);
88
+ for (let i = 0; i < rule.style.length; i++) {
89
+ const prop = rule.style[i];
90
+ if (!originalProps.has(prop)) {
91
+ el.style.setProperty(
92
+ prop,
93
+ rule.style.getPropertyValue(prop),
94
+ rule.style.getPropertyPriority(prop),
95
+ );
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ } catch {
102
+ // styleSheets API not supported in this environment
103
+ }
104
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type {Predicate} from './sel';
9
+ import type {ElementSelectorBuilder} from './types';
10
+
11
+ import invariant from '@lexical/internal/invariant';
12
+
13
+ import {buildAttrPredicate, buildClassAllPredicate, buildSelector} from './sel';
14
+
15
+ const IDENT_CHAR = /[A-Za-z0-9_-]/;
16
+
17
+ class Cursor {
18
+ constructor(
19
+ public readonly source: string,
20
+ public pos: number,
21
+ ) {}
22
+ peek(offset = 0): string {
23
+ return this.source[this.pos + offset] || '';
24
+ }
25
+ consume(): string {
26
+ return this.source[this.pos++] || '';
27
+ }
28
+ eof(): boolean {
29
+ return this.pos >= this.source.length;
30
+ }
31
+ skipWhitespace(): void {
32
+ while (!this.eof() && /\s/.test(this.peek())) {
33
+ this.pos++;
34
+ }
35
+ }
36
+ readIdent(): string {
37
+ const start = this.pos;
38
+ while (!this.eof() && IDENT_CHAR.test(this.peek())) {
39
+ this.pos++;
40
+ }
41
+ return this.source.slice(start, this.pos);
42
+ }
43
+ readQuoted(): string {
44
+ const quote = this.consume();
45
+ this.assert(quote === '"' || quote === "'", 'expected quote');
46
+ const start = this.pos;
47
+ while (!this.eof() && this.peek() !== quote) {
48
+ if (this.peek() === '\\') {
49
+ this.pos += 2;
50
+ } else {
51
+ this.pos++;
52
+ }
53
+ }
54
+ this.assert(!this.eof(), 'unterminated string');
55
+ const value = this.source.slice(start, this.pos);
56
+ this.pos++; // consume closing quote
57
+ return value.replace(/\\(.)/g, '$1');
58
+ }
59
+ /**
60
+ * `invariant(cond, fmt, …)`-flavored assertion that also surfaces the
61
+ * cursor's position context. Use for parse-time errors so a malformed
62
+ * CSS selector gets a useful, position-annotated message.
63
+ */
64
+ assert(cond: boolean, msg: string): asserts cond {
65
+ invariant(
66
+ cond,
67
+ 'invalid CSS selector at col %s: %s in %s',
68
+ String(this.pos + 1),
69
+ msg,
70
+ this.source,
71
+ );
72
+ }
73
+ }
74
+
75
+ interface ParsedSimpleSelector {
76
+ readonly tags: Set<string>;
77
+ readonly predicates: Predicate[];
78
+ }
79
+
80
+ function parseSimpleSelector(c: Cursor): ParsedSimpleSelector {
81
+ const tags = new Set<string>();
82
+ const predicates: Predicate[] = [];
83
+ const classes: string[] = [];
84
+
85
+ c.skipWhitespace();
86
+
87
+ // Optional tag or '*'
88
+ if (c.peek() === '*') {
89
+ c.consume();
90
+ } else if (IDENT_CHAR.test(c.peek())) {
91
+ const tag = c.readIdent();
92
+ if (tag) {
93
+ tags.add(tag.toUpperCase());
94
+ }
95
+ }
96
+
97
+ // Zero or more refinements: .class, #id, [attr]
98
+ while (!c.eof()) {
99
+ const ch = c.peek();
100
+ if (ch === '.') {
101
+ c.consume();
102
+ const cls = c.readIdent();
103
+ c.assert(cls !== '', 'expected class name after "."');
104
+ classes.push(cls);
105
+ } else if (ch === '#') {
106
+ c.consume();
107
+ const id = c.readIdent();
108
+ c.assert(id !== '', 'expected id after "#"');
109
+ predicates.push(buildAttrPredicate('id', id));
110
+ } else if (ch === '[') {
111
+ c.consume();
112
+ c.skipWhitespace();
113
+ const name = c.readIdent();
114
+ c.assert(name !== '', 'expected attribute name after "["');
115
+ c.skipWhitespace();
116
+ let value: true | string = true;
117
+ if (c.peek() === '=') {
118
+ c.consume();
119
+ c.skipWhitespace();
120
+ const next = c.peek();
121
+ if (next === '"' || next === "'") {
122
+ value = c.readQuoted();
123
+ } else {
124
+ value = c.readIdent();
125
+ c.assert(value !== '', 'expected attribute value');
126
+ }
127
+ c.skipWhitespace();
128
+ }
129
+ c.assert(c.peek() === ']', 'expected "]"');
130
+ c.consume();
131
+ predicates.push(buildAttrPredicate(name, value));
132
+ } else {
133
+ break;
134
+ }
135
+ }
136
+
137
+ if (classes.length > 0) {
138
+ predicates.push(buildClassAllPredicate(classes));
139
+ }
140
+
141
+ return {predicates, tags};
142
+ }
143
+
144
+ /**
145
+ * Parse a reduced CSS-selector subset and return a {@link CompiledSelector}.
146
+ * Supported:
147
+ * - Tag (`p`), wildcard (`*`).
148
+ * - Tag list (`h1, h2, h3`).
149
+ * - Class (`.foo`, `.foo.bar`).
150
+ * - ID (`#foo`).
151
+ * - Attribute presence (`[name]`).
152
+ * - Attribute equality (`[name="value"]`, `[name=value]`).
153
+ *
154
+ * Anything outside the subset (regex attribute, inline-style match,
155
+ * combinators, pseudo-classes) is intentionally rejected — chain combinator
156
+ * methods off the returned builder instead.
157
+ *
158
+ * @experimental
159
+ */
160
+ export function parseSelector(
161
+ source: string,
162
+ ): ElementSelectorBuilder<HTMLElement> {
163
+ const c: Cursor = new Cursor(source, 0);
164
+ const groups: ParsedSimpleSelector[] = [];
165
+
166
+ while (true) {
167
+ const group = parseSimpleSelector(c);
168
+ if (group.tags.size === 0 && group.predicates.length === 0) {
169
+ // Empty group with neither tag nor refinement — only OK if it came
170
+ // from the lone `*` (which produces zero tags but no preds either).
171
+ // We accept this as "wildcard element".
172
+ }
173
+ groups.push(group);
174
+ c.skipWhitespace();
175
+ if (c.eof()) {
176
+ break;
177
+ }
178
+ c.assert(
179
+ c.peek() === ',',
180
+ 'expected "," (selector lists are the only supported combinator)',
181
+ );
182
+ c.consume();
183
+ c.skipWhitespace();
184
+ }
185
+
186
+ if (groups.length === 1) {
187
+ return buildSelector(groups[0].tags, groups[0].predicates);
188
+ }
189
+
190
+ // Comma-separated list. Merge tag sets and OR-combine the per-group
191
+ // refinement predicates so that each candidate node satisfies *some*
192
+ // group entirely.
193
+ const tags = new Set<string>();
194
+ for (const g of groups) {
195
+ for (const t of g.tags) {
196
+ tags.add(t);
197
+ }
198
+ }
199
+ const orPredicate: Predicate = (node, captures) => {
200
+ for (const g of groups) {
201
+ const upper = node.nodeName;
202
+ if (g.tags.size > 0 && !g.tags.has(upper)) {
203
+ continue;
204
+ }
205
+ let ok = true;
206
+ for (const p of g.predicates) {
207
+ if (!p(node, captures)) {
208
+ ok = false;
209
+ break;
210
+ }
211
+ }
212
+ if (ok) {
213
+ return true;
214
+ }
215
+ }
216
+ return false;
217
+ };
218
+ return buildSelector(tags, [orPredicate]);
219
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type {
9
+ ChildSchema,
10
+ DOMImportContext,
11
+ ImportChildrenOpts,
12
+ ImportNodeOpts,
13
+ ImportSession,
14
+ ImportStateConfig,
15
+ } from './types';
16
+
17
+ import {isDOMDocumentNode, type LexicalEditor, type LexicalNode} from 'lexical';
18
+
19
+ import {
20
+ type CompiledDispatch,
21
+ type CompiledRule,
22
+ getDispatchIndices,
23
+ } from './compileImportRules';
24
+ import {
25
+ $getImportContextValue,
26
+ $withImportContext,
27
+ ImportOverlays,
28
+ } from './ImportContext';
29
+ import {$applySchema, RootSchema} from './schemas';
30
+
31
+ const __DEV__ = process.env.NODE_ENV !== 'production';
32
+
33
+ const NO_CAPTURES: Record<string, RegExpMatchArray> = Object.freeze(
34
+ {} as Record<string, RegExpMatchArray>,
35
+ );
36
+
37
+ interface Runtime {
38
+ readonly dispatch: CompiledDispatch;
39
+ readonly editor: LexicalEditor;
40
+ readonly session: ImportSession;
41
+ /**
42
+ * Stack of overlay dispatchers installed via `$importChildren({rules})`.
43
+ * The most recently pushed overlay is at the highest index and is
44
+ * tried first; rules within an overlay are dispatched in their own
45
+ * registration order. When all overlay rules for a node have been
46
+ * exhausted (or have called `$next()` to defer), the main dispatcher
47
+ * is consulted. This lets app rules scope cost-bearing predicates to
48
+ * the subtrees where they apply.
49
+ */
50
+ readonly overlays: CompiledDispatch[];
51
+ }
52
+
53
+ function makeContext(
54
+ runtime: Runtime,
55
+ captures: Readonly<Record<string, RegExpMatchArray>>,
56
+ ): DOMImportContext<Record<string, RegExpMatchArray>> {
57
+ const ctx: DOMImportContext<Record<string, RegExpMatchArray>> = {
58
+ $importChildren: (parent, opts) =>
59
+ $importChildrenInternal(runtime, parent, opts),
60
+ $importOne: (node, opts) => $importOneInternal(runtime, node, opts),
61
+ captures,
62
+ get<V>(cfg: ImportStateConfig<V>): V {
63
+ return $getImportContextValue(cfg, runtime.editor);
64
+ },
65
+ session: runtime.session,
66
+ };
67
+ return ctx;
68
+ }
69
+
70
+ function $importChildrenInternal(
71
+ runtime: Runtime,
72
+ parent: ParentNode,
73
+ opts: ImportChildrenOpts | undefined,
74
+ ): LexicalNode[] {
75
+ const overlay = opts && opts.rules ? opts.rules.dispatch : undefined;
76
+ if (overlay) {
77
+ runtime.overlays.push(overlay);
78
+ }
79
+ try {
80
+ const run = () => $importChildrenRun(runtime, parent, opts);
81
+ return opts && opts.context
82
+ ? $withImportContext(opts.context, runtime.editor)(run)
83
+ : run();
84
+ } finally {
85
+ if (overlay) {
86
+ runtime.overlays.pop();
87
+ }
88
+ }
89
+ }
90
+
91
+ function $importChildrenRun(
92
+ runtime: Runtime,
93
+ parent: ParentNode,
94
+ opts: ImportChildrenOpts | undefined,
95
+ ): LexicalNode[] {
96
+ const onChild = opts && opts.$onChild;
97
+ const collected: LexicalNode[] = [];
98
+ for (const child of Array.from(parent.childNodes)) {
99
+ const produced = $importOneInternal(runtime, child, undefined);
100
+ for (const lex of produced) {
101
+ const result = onChild ? onChild(lex) : lex;
102
+ if (result != null) {
103
+ collected.push(result);
104
+ }
105
+ }
106
+ }
107
+ const afterApplied = opts && opts.$after ? opts.$after(collected) : collected;
108
+ const schema: ChildSchema | undefined = opts && opts.schema;
109
+ if (!schema) {
110
+ return afterApplied;
111
+ }
112
+ return $applySchema(schema, afterApplied, null, parent);
113
+ }
114
+
115
+ function $importOneInternal(
116
+ runtime: Runtime,
117
+ node: Node,
118
+ opts: ImportNodeOpts | undefined,
119
+ ): LexicalNode[] {
120
+ const run = () => $dispatch(runtime, node);
121
+ const out =
122
+ opts && opts.context
123
+ ? $withImportContext(opts.context, runtime.editor)(run)
124
+ : run();
125
+ // Surface to callers as a mutable array per the DOMImportContext contract.
126
+ return out as LexicalNode[];
127
+ }
128
+
129
+ /**
130
+ * Build the candidate (dispatch, indices) list for `node`. Overlays are
131
+ * tried first in top-of-stack order; the main dispatcher comes last. The
132
+ * `$next()` chain walks through all of them in sequence — an overlay rule
133
+ * can defer to a lower overlay rule, or all the way through to a main
134
+ * rule, just by calling `$next()`.
135
+ */
136
+ function getCandidates(
137
+ runtime: Runtime,
138
+ node: Node,
139
+ ): readonly {dispatch: CompiledDispatch; indices: readonly number[]}[] {
140
+ const candidates: {
141
+ dispatch: CompiledDispatch;
142
+ indices: readonly number[];
143
+ }[] = [];
144
+ for (let i = runtime.overlays.length - 1; i >= 0; i--) {
145
+ const d = runtime.overlays[i];
146
+ const idx = getDispatchIndices(d, node);
147
+ if (idx.length > 0) {
148
+ candidates.push({dispatch: d, indices: idx});
149
+ }
150
+ }
151
+ const mainIdx = getDispatchIndices(runtime.dispatch, node);
152
+ if (mainIdx.length > 0) {
153
+ candidates.push({dispatch: runtime.dispatch, indices: mainIdx});
154
+ }
155
+ return candidates;
156
+ }
157
+
158
+ function $dispatch(runtime: Runtime, node: Node): readonly LexicalNode[] {
159
+ const candidates = getCandidates(runtime, node);
160
+ if (candidates.length === 0) {
161
+ return $hoistChildrenOf(runtime, node);
162
+ }
163
+ let groupCursor = 0;
164
+ let ruleCursor = 0;
165
+ const $next = (): readonly LexicalNode[] => {
166
+ while (groupCursor < candidates.length) {
167
+ const {dispatch, indices} = candidates[groupCursor];
168
+ while (ruleCursor < indices.length) {
169
+ const idx = indices[ruleCursor++];
170
+ const rule: CompiledRule = dispatch.rules[idx];
171
+ const captures: Record<string, RegExpMatchArray> = {};
172
+ if (rule.predicate(node, captures)) {
173
+ const ctx = makeContext(
174
+ runtime,
175
+ Object.keys(captures).length === 0 ? NO_CAPTURES : captures,
176
+ );
177
+ try {
178
+ return rule.$import(ctx, node, $next);
179
+ } catch (e) {
180
+ if (__DEV__) {
181
+ console.error(
182
+ `[lexical] DOM import rule "${rule.name}" threw on node`,
183
+ node,
184
+ e,
185
+ );
186
+ }
187
+ throw e;
188
+ }
189
+ }
190
+ }
191
+ groupCursor++;
192
+ ruleCursor = 0;
193
+ }
194
+ return $hoistChildrenOf(runtime, node);
195
+ };
196
+ return $next();
197
+ }
198
+
199
+ /**
200
+ * Fallback when no rule matched and `$next()` was called past the end of the
201
+ * chain: hoist the element's children to take its place, recursively. Pure
202
+ * elements with no rule become invisible, matching the legacy
203
+ * `$createNodesFromDOM` hoisting behavior.
204
+ */
205
+ function $hoistChildrenOf(runtime: Runtime, node: Node): LexicalNode[] {
206
+ if (node.childNodes.length === 0) {
207
+ return [];
208
+ }
209
+ const collected: LexicalNode[] = [];
210
+ for (const child of Array.from(node.childNodes)) {
211
+ const produced = $importOneInternal(runtime, child, undefined);
212
+ for (const lex of produced) {
213
+ collected.push(lex);
214
+ }
215
+ }
216
+ return collected;
217
+ }
218
+
219
+ /**
220
+ * Top-level walker for a compiled dispatcher. Iterates the DOM children of
221
+ * `dom` (using the document body if a {@link Document} is passed) and
222
+ * applies `RootSchema` to the produced lexical nodes so runs of inlines are
223
+ * wrapped in paragraphs — same shape as the legacy `$generateNodesFromDOM`.
224
+ *
225
+ * @internal
226
+ */
227
+ export function $runImport(
228
+ dispatch: CompiledDispatch,
229
+ editor: LexicalEditor,
230
+ dom: Document | ParentNode,
231
+ session: ImportSession,
232
+ ): LexicalNode[] {
233
+ // Prime the overlay stack with any overlays a preprocess wrote to
234
+ // ImportOverlays. These remain in effect for the entire walk; nested
235
+ // `$importChildren({rules})` calls push on top.
236
+ const installed = session.get(ImportOverlays);
237
+ const runtime: Runtime = {
238
+ dispatch,
239
+ editor,
240
+ overlays: installed.map(o => o.dispatch),
241
+ session,
242
+ };
243
+ const rootParent: ParentNode = isDOMDocumentNode(dom) ? dom.body : dom;
244
+ return $importChildrenRun(runtime, rootParent, {schema: RootSchema});
245
+ }