@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,105 @@
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 {AnyDOMImportRule} from './types';
9
+
10
+ import {type CompiledDispatch, compileImportRules} from './compileImportRules';
11
+
12
+ /**
13
+ * Opaque handle for a pre-compiled set of overlay rules. Produce one with
14
+ * {@link defineOverlayRules} and pass it to
15
+ * {@link DOMImportContext.$importChildren} via
16
+ * {@link ImportChildrenOpts.rules}.
17
+ *
18
+ * To merge two or more overlays into a single one, pass them (alongside
19
+ * raw {@link DOMImportRule}s if desired) to a fresh
20
+ * {@link defineOverlayRules} — earlier arguments are higher priority.
21
+ *
22
+ * The internal shape is intentionally not part of the public API: it's a
23
+ * compiled dispatch table tagged with `__type` so callers cannot pass a
24
+ * raw rule array where a compiled overlay is expected.
25
+ *
26
+ * @experimental
27
+ */
28
+ export interface CompiledOverlayRules {
29
+ readonly __type: 'CompiledOverlayRules';
30
+ /** @internal */
31
+ readonly dispatch: CompiledDispatch;
32
+ /**
33
+ * @internal — flattened source rules retained so an overlay can be
34
+ * recompiled when it is passed to another {@link defineOverlayRules}
35
+ * call or as part of {@link DOMImportConfig.rules}.
36
+ */
37
+ readonly rules: readonly AnyDOMImportRule[];
38
+ }
39
+
40
+ /**
41
+ * An entry accepted everywhere rules are configured (overlay
42
+ * definitions, {@link DOMImportConfig.rules}). Either a single
43
+ * {@link DOMImportRule} or a {@link CompiledOverlayRules} produced by
44
+ * a previous {@link defineOverlayRules} call — passing the latter
45
+ * inlines the overlay's rules at this position in priority order.
46
+ *
47
+ * @experimental
48
+ */
49
+ export type DOMImportRuleEntry = AnyDOMImportRule | CompiledOverlayRules;
50
+
51
+ /** @internal */
52
+ export function flattenRuleEntries(
53
+ entries: readonly DOMImportRuleEntry[],
54
+ ): AnyDOMImportRule[] {
55
+ const out: AnyDOMImportRule[] = [];
56
+ for (const entry of entries) {
57
+ if (isCompiledOverlayRules(entry)) {
58
+ for (const r of entry.rules) {
59
+ out.push(r);
60
+ }
61
+ } else {
62
+ out.push(entry);
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+
68
+ function isCompiledOverlayRules(
69
+ entry: DOMImportRuleEntry,
70
+ ): entry is CompiledOverlayRules {
71
+ return (
72
+ typeof entry === 'object' &&
73
+ entry !== null &&
74
+ '__type' in entry &&
75
+ entry.__type === 'CompiledOverlayRules'
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Pre-compile a set of {@link DOMImportRuleEntry}s into a
81
+ * {@link CompiledOverlayRules} handle that can be installed via
82
+ * `ctx.$importChildren(el, {rules: …})`.
83
+ *
84
+ * Entries can be raw {@link DOMImportRule}s or other
85
+ * {@link CompiledOverlayRules} (the latter are inlined at their
86
+ * position in priority order, so the same call composes any number of
87
+ * overlays). Earlier entries are higher priority.
88
+ *
89
+ * Overlay rules installed as a raw array would be re-compiled on every
90
+ * `$importChildren` call. For overlays that are reused (e.g. a GitHub
91
+ * code-table rule that wraps every matching table), call this once at
92
+ * module scope so the dispatch table is built up front.
93
+ *
94
+ * @experimental
95
+ */
96
+ export function defineOverlayRules(
97
+ entries: readonly DOMImportRuleEntry[],
98
+ ): CompiledOverlayRules {
99
+ const rules = flattenRuleEntries(entries);
100
+ return {
101
+ __type: 'CompiledOverlayRules',
102
+ dispatch: compileImportRules(rules),
103
+ rules,
104
+ };
105
+ }
@@ -0,0 +1,96 @@
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
+ BlockSchema,
70
+ InlineSchema,
71
+ NestedBlockSchema,
72
+ RootSchema,
73
+ } from './schemas';
74
+ export {isElementOfTag} from './sel';
75
+ export type {
76
+ AnyDOMImportRule,
77
+ AttrMatchOptions,
78
+ CapturesOfSelector,
79
+ ChildSchema,
80
+ CompiledSelector,
81
+ DOMImportContext,
82
+ DOMImportExtensionOutput,
83
+ DOMImportFn,
84
+ DOMImportRule,
85
+ DOMPreprocessContext,
86
+ DOMPreprocessFn,
87
+ ElementSelectorBuilder,
88
+ GenerateNodesFromDOMOptions,
89
+ ImportChildrenOpts,
90
+ ImportContextPairOrUpdater,
91
+ ImportNodeOpts,
92
+ ImportSession,
93
+ ImportStateConfig,
94
+ NodeOfSelector,
95
+ StyleMatchOptions,
96
+ } 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
+ }