@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.
- package/{DOMRenderExtension.d.ts → dist/DOMRenderExtension.d.ts} +12 -1
- package/dist/DOMRenderRuntime.d.ts +51 -0
- package/dist/LexicalHtml.dev.js +3289 -0
- package/dist/LexicalHtml.dev.mjs +3242 -0
- package/{LexicalHtml.js.flow → dist/LexicalHtml.js.flow} +16 -16
- package/dist/LexicalHtml.mjs +57 -0
- package/dist/LexicalHtml.node.mjs +55 -0
- package/dist/LexicalHtml.prod.js +9 -0
- package/dist/LexicalHtml.prod.mjs +9 -0
- package/dist/RenderContext.d.ts +68 -0
- package/{compileDOMRenderConfigOverrides.d.ts → dist/compileDOMRenderConfigOverrides.d.ts} +1 -1
- package/{constants.d.ts → dist/constants.d.ts} +2 -0
- package/dist/domOverride.d.ts +23 -0
- package/dist/import/CoreImportExtension.d.ts +11 -0
- package/dist/import/DOMImportExtension.d.ts +82 -0
- package/dist/import/HorizontalRuleImportExtension.d.ts +28 -0
- package/dist/import/ImportContext.d.ts +208 -0
- package/dist/import/compileImportRules.d.ts +50 -0
- package/dist/import/coreImportRules.d.ts +25 -0
- package/dist/import/defineImportRule.d.ts +32 -0
- package/dist/import/defineOverlayRules.d.ts +66 -0
- package/dist/import/index.d.ts +38 -0
- package/dist/import/inlineStylesFromStyleSheets.d.ts +28 -0
- package/dist/import/parseCss.d.ts +18 -0
- package/dist/import/runImport.d.ts +19 -0
- package/dist/import/schemas.d.ts +106 -0
- package/dist/import/sel.d.ts +74 -0
- package/dist/import/types.d.ts +394 -0
- package/dist/index.d.ts +44 -0
- package/{types.d.ts → dist/types.d.ts} +96 -8
- package/package.json +33 -18
- package/src/ContextRecord.ts +243 -0
- package/src/DOMRenderExtension.ts +96 -0
- package/src/DOMRenderRuntime.ts +265 -0
- package/src/RenderContext.ts +168 -0
- package/src/compileDOMRenderConfigOverrides.ts +416 -0
- package/src/constants.ts +18 -0
- package/src/domOverride.ts +46 -0
- package/src/import/CoreImportExtension.ts +26 -0
- package/src/import/DOMImportExtension.ts +221 -0
- package/src/import/HorizontalRuleImportExtension.ts +52 -0
- package/src/import/ImportContext.ts +339 -0
- package/src/import/compileImportRules.ts +178 -0
- package/src/import/coreImportRules.ts +545 -0
- package/src/import/defineImportRule.ts +40 -0
- package/src/import/defineOverlayRules.ts +105 -0
- package/src/import/index.ts +97 -0
- package/src/import/inlineStylesFromStyleSheets.ts +104 -0
- package/src/import/parseCss.ts +219 -0
- package/src/import/runImport.ts +245 -0
- package/src/import/schemas.ts +280 -0
- package/src/import/sel.ts +314 -0
- package/src/import/types.ts +471 -0
- package/src/index.ts +561 -0
- package/src/types.ts +470 -0
- package/LexicalHtml.dev.js +0 -914
- package/LexicalHtml.dev.mjs +0 -900
- package/LexicalHtml.mjs +0 -24
- package/LexicalHtml.node.mjs +0 -22
- package/LexicalHtml.prod.js +0 -9
- package/LexicalHtml.prod.mjs +0 -9
- package/RenderContext.d.ts +0 -32
- package/domOverride.d.ts +0 -18
- package/index.d.ts +0 -32
- /package/{ContextRecord.d.ts → dist/ContextRecord.d.ts} +0 -0
- /package/{LexicalHtml.js → dist/LexicalHtml.js} +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
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 {ContextRecord} from '../types';
|
|
9
|
+
import type {DOMImportRuleEntry} from './defineOverlayRules';
|
|
10
|
+
import type {
|
|
11
|
+
DOMImportExtensionOutput,
|
|
12
|
+
DOMPreprocessContext,
|
|
13
|
+
DOMPreprocessFn,
|
|
14
|
+
GenerateNodesFromDOMOptions,
|
|
15
|
+
ImportContextPairOrUpdater,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
import {$getExtensionOutput} from '@lexical/extension';
|
|
19
|
+
import {defineExtension, type LexicalNode, shallowMergeConfig} from 'lexical';
|
|
20
|
+
|
|
21
|
+
import {DOMImportContextSymbol, DOMImportExtensionName} from '../constants';
|
|
22
|
+
import {$withFullContext, contextFromPairs} from '../ContextRecord';
|
|
23
|
+
import {type CompiledDispatch, compileImportRules} from './compileImportRules';
|
|
24
|
+
import {defineImportRule} from './defineImportRule';
|
|
25
|
+
import {flattenRuleEntries} from './defineOverlayRules';
|
|
26
|
+
import {ImportSessionImpl} from './ImportContext';
|
|
27
|
+
import {$inlineStylesFromStyleSheets} from './inlineStylesFromStyleSheets';
|
|
28
|
+
import {$runImport} from './runImport';
|
|
29
|
+
import {selBase} from './sel';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for {@link DOMImportExtension}.
|
|
33
|
+
*
|
|
34
|
+
* @experimental
|
|
35
|
+
*/
|
|
36
|
+
export interface DOMImportConfig {
|
|
37
|
+
/**
|
|
38
|
+
* The set of rules contributed by this extension and its dependencies.
|
|
39
|
+
* Entries can be raw {@link DOMImportRule}s or a
|
|
40
|
+
* {@link CompiledOverlayRules} produced by {@link defineOverlayRules}
|
|
41
|
+
* (the latter is inlined in priority order — useful for libraries
|
|
42
|
+
* that already publish a compiled overlay).
|
|
43
|
+
*
|
|
44
|
+
* Rules are dispatched in priority order: rules contributed by
|
|
45
|
+
* extensions merged later (i.e. closer to the editor root) run first
|
|
46
|
+
* and may call `$next()` to delegate to lower-priority rules.
|
|
47
|
+
*
|
|
48
|
+
* `mergeConfig` prepends `partial.rules` to existing `rules`, so later
|
|
49
|
+
* configuration carries higher priority.
|
|
50
|
+
*/
|
|
51
|
+
readonly rules: readonly DOMImportRuleEntry[];
|
|
52
|
+
/**
|
|
53
|
+
* Default context pairs applied to every `$generateNodesFromDOM` call.
|
|
54
|
+
* Per-call overrides can be supplied via
|
|
55
|
+
* {@link GenerateNodesFromDOMOptions.context}.
|
|
56
|
+
*/
|
|
57
|
+
readonly contextDefaults: readonly ImportContextPairOrUpdater[];
|
|
58
|
+
/**
|
|
59
|
+
* Functions run in order on the DOM before walking begins, mutating in
|
|
60
|
+
* place. The default config registers
|
|
61
|
+
* {@link $inlineStylesFromStyleSheets} (resolves `<style>` rules to
|
|
62
|
+
* inline styles so the rules' style-driven matchers see them); apps
|
|
63
|
+
* append additional preprocessors (e.g. strip unsafe elements,
|
|
64
|
+
* normalize attributes, resolve relative URLs).
|
|
65
|
+
*
|
|
66
|
+
* `mergeConfig` appends, so each contributing extension's preprocessors
|
|
67
|
+
* run in dependency order. Per-call preprocessors registered via
|
|
68
|
+
* {@link GenerateNodesFromDOMOptions.preprocess} run AFTER these.
|
|
69
|
+
*/
|
|
70
|
+
readonly preprocess: readonly DOMPreprocessFn[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Drive a stack of {@link DOMPreprocessFn}s top-to-bottom: the highest-
|
|
75
|
+
* index fn runs first and may call `$next()` to defer to the next-lower
|
|
76
|
+
* one. Matches the export-side `callExportMimeTypeFunctionStack` shape.
|
|
77
|
+
*/
|
|
78
|
+
function $runPreprocessStack(
|
|
79
|
+
stack: readonly DOMPreprocessFn[],
|
|
80
|
+
dom: Document | ParentNode,
|
|
81
|
+
ctx: DOMPreprocessContext,
|
|
82
|
+
): void {
|
|
83
|
+
let i = stack.length - 1;
|
|
84
|
+
const $next = () => {
|
|
85
|
+
while (i >= 0) {
|
|
86
|
+
const cur = stack[i--];
|
|
87
|
+
cur(dom, ctx, $next);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
$next();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Lowest-priority catch-all rule used as the default `config.rules` entry
|
|
96
|
+
* for {@link DOMImportExtension}: descends into the element's children
|
|
97
|
+
* and returns whatever they produced. With no other matching rule, an
|
|
98
|
+
* element vanishes and its contents are inserted in its place — the
|
|
99
|
+
* legacy `$createNodesFromDOM` hoisting behavior, but now expressed as a
|
|
100
|
+
* regular rule that apps can override (e.g. with a `sel.any()` rule that
|
|
101
|
+
* captures and discards unknown elements).
|
|
102
|
+
*
|
|
103
|
+
* @experimental
|
|
104
|
+
*/
|
|
105
|
+
export const DefaultHoistRule = defineImportRule({
|
|
106
|
+
$import: (ctx, el) => ctx.$importChildren(el),
|
|
107
|
+
match: selBase.any(),
|
|
108
|
+
name: '@lexical/html/default-hoist',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @experimental
|
|
113
|
+
*
|
|
114
|
+
* Extension-based replacement for the legacy `importDOM` / `DOMConversion`
|
|
115
|
+
* machinery. Rules are contributed via configuration (see
|
|
116
|
+
* {@link DOMImportConfig.rules}), compiled into a tag-bucketed dispatcher at
|
|
117
|
+
* editor build time, and consumed via the extension's
|
|
118
|
+
* {@link DOMImportExtensionOutput.$generateNodesFromDOM} output.
|
|
119
|
+
*
|
|
120
|
+
* The legacy `$generateNodesFromDOM` continues to work in parallel; the
|
|
121
|
+
* intent is to migrate node packages over to this extension incrementally.
|
|
122
|
+
*/
|
|
123
|
+
export const DOMImportExtension = defineExtension<
|
|
124
|
+
DOMImportConfig,
|
|
125
|
+
typeof DOMImportExtensionName,
|
|
126
|
+
DOMImportExtensionOutput,
|
|
127
|
+
void
|
|
128
|
+
>({
|
|
129
|
+
build(editor, config) {
|
|
130
|
+
const dispatch: CompiledDispatch = compileImportRules(
|
|
131
|
+
flattenRuleEntries(config.rules),
|
|
132
|
+
);
|
|
133
|
+
const defaults = contextFromPairs(config.contextDefaults, undefined);
|
|
134
|
+
const configPreprocess = config.preprocess;
|
|
135
|
+
return {
|
|
136
|
+
$generateNodesFromDOM: (
|
|
137
|
+
dom: Document | ParentNode,
|
|
138
|
+
options?: GenerateNodesFromDOMOptions,
|
|
139
|
+
) => {
|
|
140
|
+
// The session record IS the root layer of the walk's context.
|
|
141
|
+
// Start with per-call options.context applied on top of the
|
|
142
|
+
// editor's contextDefaults, then ensure we have a *fresh*
|
|
143
|
+
// mutable child (never the shared defaults record) so
|
|
144
|
+
// session.set writes never leak into the editor's config.
|
|
145
|
+
const fromOpts =
|
|
146
|
+
options && options.context
|
|
147
|
+
? contextFromPairs(options.context, defaults)
|
|
148
|
+
: defaults;
|
|
149
|
+
const sessionRecord: ContextRecord<typeof DOMImportContextSymbol> =
|
|
150
|
+
fromOpts !== undefined && fromOpts !== defaults
|
|
151
|
+
? fromOpts
|
|
152
|
+
: Object.create(defaults || null);
|
|
153
|
+
const session = new ImportSessionImpl(sessionRecord);
|
|
154
|
+
const preprocessCtx: DOMPreprocessContext = {session};
|
|
155
|
+
// Stack of preprocessors: config-level first, then per-call.
|
|
156
|
+
// Top of stack (last in array) runs first; `next()` defers to
|
|
157
|
+
// the next-lower one. Matches the GetClipboardDataExtension
|
|
158
|
+
// convention so app-registered preprocessors can wrap built-in
|
|
159
|
+
// ones via `next()`. Preprocess writes via `ctx.session.set`
|
|
160
|
+
// mutate the session record directly.
|
|
161
|
+
const stack: readonly DOMPreprocessFn[] =
|
|
162
|
+
options && options.preprocess
|
|
163
|
+
? [...configPreprocess, ...options.preprocess]
|
|
164
|
+
: configPreprocess;
|
|
165
|
+
$runPreprocessStack(stack, dom, preprocessCtx);
|
|
166
|
+
return $withFullContext(
|
|
167
|
+
DOMImportContextSymbol,
|
|
168
|
+
sessionRecord,
|
|
169
|
+
() => $runImport(dispatch, editor, dom, session),
|
|
170
|
+
editor,
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
defaults,
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
config: {
|
|
177
|
+
contextDefaults: [],
|
|
178
|
+
preprocess: [$inlineStylesFromStyleSheets],
|
|
179
|
+
rules: [DefaultHoistRule],
|
|
180
|
+
},
|
|
181
|
+
mergeConfig(config, partial) {
|
|
182
|
+
return shallowMergeConfig(config, {
|
|
183
|
+
...partial,
|
|
184
|
+
...(partial.contextDefaults && {
|
|
185
|
+
contextDefaults: [
|
|
186
|
+
...config.contextDefaults,
|
|
187
|
+
...partial.contextDefaults,
|
|
188
|
+
],
|
|
189
|
+
}),
|
|
190
|
+
...(partial.preprocess && {
|
|
191
|
+
preprocess: [...config.preprocess, ...partial.preprocess],
|
|
192
|
+
}),
|
|
193
|
+
...(partial.rules && {
|
|
194
|
+
rules: [...partial.rules, ...config.rules],
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
name: DOMImportExtensionName,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Look up the editor's {@link DOMImportExtension} and run its
|
|
203
|
+
* `$generateNodesFromDOM`. Designed as a drop-in replacement for the
|
|
204
|
+
* legacy `$generateNodesFromDOM(editor, dom)` signature so it can be
|
|
205
|
+
* supplied to `ClipboardImportExtension.$generateNodesFromDOM` (or any
|
|
206
|
+
* other consumer that wants to route through the extension pipeline).
|
|
207
|
+
*
|
|
208
|
+
* Throws if the editor was not built with {@link DOMImportExtension} as a
|
|
209
|
+
* dependency.
|
|
210
|
+
*
|
|
211
|
+
* @experimental
|
|
212
|
+
*/
|
|
213
|
+
export function $generateNodesFromDOMViaExtension(
|
|
214
|
+
dom: Document | ParentNode,
|
|
215
|
+
options?: GenerateNodesFromDOMOptions,
|
|
216
|
+
): LexicalNode[] {
|
|
217
|
+
return $getExtensionOutput(DOMImportExtension).$generateNodesFromDOM(
|
|
218
|
+
dom,
|
|
219
|
+
options,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 {
|
|
10
|
+
$createHorizontalRuleNode,
|
|
11
|
+
HorizontalRuleExtension,
|
|
12
|
+
} from '@lexical/extension';
|
|
13
|
+
import {configExtension, defineExtension} from 'lexical';
|
|
14
|
+
|
|
15
|
+
import {defineImportRule} from './defineImportRule';
|
|
16
|
+
import {DOMImportExtension} from './DOMImportExtension';
|
|
17
|
+
import {selBase} from './sel';
|
|
18
|
+
|
|
19
|
+
const HorizontalRuleRule = defineImportRule({
|
|
20
|
+
$import: () => [$createHorizontalRuleNode()],
|
|
21
|
+
match: selBase.tag('hr'),
|
|
22
|
+
name: '@lexical/html/hr',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Import rules for {@link HorizontalRuleNode}.
|
|
27
|
+
*
|
|
28
|
+
* @experimental
|
|
29
|
+
*/
|
|
30
|
+
export const HorizontalRuleImportRules = [HorizontalRuleRule];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Bundles {@link HorizontalRuleImportRules} together with the runtime
|
|
34
|
+
* {@link HorizontalRuleExtension}. The application is expected to
|
|
35
|
+
* already have `CoreImportExtension` (or some equivalent) in its
|
|
36
|
+
* dependency graph — the core/text/paragraph/inline-format rules are a
|
|
37
|
+
* shared baseline, not something this leaf importer should re-declare.
|
|
38
|
+
*
|
|
39
|
+
* Lives in `@lexical/html` (not `@lexical/extension`) because
|
|
40
|
+
* {@link DOMImportExtension} itself is in `@lexical/html`, and
|
|
41
|
+
* `@lexical/extension` is upstream of `@lexical/html` in the dependency
|
|
42
|
+
* graph.
|
|
43
|
+
*
|
|
44
|
+
* @experimental
|
|
45
|
+
*/
|
|
46
|
+
export const HorizontalRuleImportExtension = defineExtension({
|
|
47
|
+
dependencies: [
|
|
48
|
+
HorizontalRuleExtension,
|
|
49
|
+
configExtension(DOMImportExtension, {rules: HorizontalRuleImportRules}),
|
|
50
|
+
],
|
|
51
|
+
name: '@lexical/html/HorizontalRuleImport',
|
|
52
|
+
});
|
|
@@ -0,0 +1,339 @@
|
|
|
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 {ContextRecord} from '../types';
|
|
9
|
+
import type {CompiledOverlayRules} from './defineOverlayRules';
|
|
10
|
+
import type {DOMImportExtension} from './DOMImportExtension';
|
|
11
|
+
import type {
|
|
12
|
+
ImportContextPairOrUpdater,
|
|
13
|
+
ImportSession,
|
|
14
|
+
ImportStateConfig,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
import {getPeerDependencyFromEditor} from '@lexical/extension';
|
|
18
|
+
import {
|
|
19
|
+
$getEditor,
|
|
20
|
+
isBlockDomNode,
|
|
21
|
+
isDOMTextNode,
|
|
22
|
+
isHTMLElement,
|
|
23
|
+
isInlineDomNode,
|
|
24
|
+
type LexicalEditor,
|
|
25
|
+
} from 'lexical';
|
|
26
|
+
|
|
27
|
+
import {DOMImportContextSymbol, DOMImportExtensionName} from '../constants';
|
|
28
|
+
import {
|
|
29
|
+
$withContext,
|
|
30
|
+
createContextState,
|
|
31
|
+
getContextRecord,
|
|
32
|
+
getContextValue,
|
|
33
|
+
} from '../ContextRecord';
|
|
34
|
+
|
|
35
|
+
type ImportContextRecord = ContextRecord<typeof DOMImportContextSymbol>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create an import context state. The phantom symbol prevents accidental
|
|
39
|
+
* use of a render-context state in an import context (and vice versa).
|
|
40
|
+
*
|
|
41
|
+
* Note: to support the value-or-updater pattern, `V` cannot be a function
|
|
42
|
+
* type; wrap it in an array or object if needed.
|
|
43
|
+
*
|
|
44
|
+
* `getDefaultValue` is called **once at state creation** and the result is
|
|
45
|
+
* shared between every session that reads the state without first writing
|
|
46
|
+
* a value. Defaults must therefore be immutable (primitives, frozen
|
|
47
|
+
* objects, or read-only arrays / records). If your state needs mutable
|
|
48
|
+
* per-session storage, lazily initialize it inside your rule (e.g.
|
|
49
|
+
* `if (!ctx.session.has(cfg)) ctx.session.set(cfg, new …())`).
|
|
50
|
+
*
|
|
51
|
+
* @experimental
|
|
52
|
+
* @__NO_SIDE_EFFECTS__
|
|
53
|
+
*/
|
|
54
|
+
export function createImportState<V>(
|
|
55
|
+
name: string,
|
|
56
|
+
getDefaultValue: () => V,
|
|
57
|
+
isEqual?: (a: V, b: V) => boolean,
|
|
58
|
+
): ImportStateConfig<V> {
|
|
59
|
+
return createContextState(
|
|
60
|
+
DOMImportContextSymbol,
|
|
61
|
+
name,
|
|
62
|
+
getDefaultValue,
|
|
63
|
+
isEqual,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The kind of operation that produced this import. Lets rules adapt
|
|
69
|
+
* their behavior (e.g. preserve more whitespace on `'paste'`).
|
|
70
|
+
* Defaults to `'unknown'`. Apps that need a different vocabulary can
|
|
71
|
+
* define their own {@link ImportStateConfig} with whatever value type
|
|
72
|
+
* they want.
|
|
73
|
+
*
|
|
74
|
+
* @experimental
|
|
75
|
+
*/
|
|
76
|
+
export type ImportSourceKind = 'paste' | 'unknown';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Built-in import-context state identifying how this import was initiated.
|
|
80
|
+
* Callers of `$generateNodesFromDOM` should set it via the `context` option.
|
|
81
|
+
*
|
|
82
|
+
* @experimental
|
|
83
|
+
*/
|
|
84
|
+
export const ImportSource: ImportStateConfig<ImportSourceKind> =
|
|
85
|
+
createImportState<ImportSourceKind>('importSource', () => 'unknown');
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Built-in import-context state holding the {@link DataTransfer} the
|
|
89
|
+
* import was sourced from, if any. `null` outside paste/drop flows.
|
|
90
|
+
*
|
|
91
|
+
* The clipboard import pipeline passes the original `DataTransfer`
|
|
92
|
+
* through to its per-MIME-type handler stack (see
|
|
93
|
+
* {@link ImportMimeTypeFunction}); handlers that route HTML through
|
|
94
|
+
* the {@link DOMImportExtension} pipeline should forward it into the
|
|
95
|
+
* walk via `context: [contextValue(ImportSourceDataTransfer,
|
|
96
|
+
* dataTransfer)]` so rules and preprocessors can call
|
|
97
|
+
* `ctx.get(ImportSourceDataTransfer)` to inspect companion MIME types
|
|
98
|
+
* (e.g. an `'application/rtf'` alternative or an attached
|
|
99
|
+
* `'application/x-officedrawing'` payload), the file list, or any
|
|
100
|
+
* custom drag-and-drop slot.
|
|
101
|
+
*
|
|
102
|
+
* Use sparingly: the safer pattern is to decide *which* MIME-type
|
|
103
|
+
* payload to walk in the clipboard handler stack and hand a finalized
|
|
104
|
+
* DOM to the rules; only fall back to peeking at `ImportSourceDataTransfer`
|
|
105
|
+
* when the source-detection signal genuinely lives in a companion
|
|
106
|
+
* slot.
|
|
107
|
+
*
|
|
108
|
+
* @experimental
|
|
109
|
+
*/
|
|
110
|
+
export const ImportSourceDataTransfer: ImportStateConfig<DataTransfer | null> =
|
|
111
|
+
createImportState<DataTransfer | null>(
|
|
112
|
+
'importSourceDataTransfer',
|
|
113
|
+
() => null,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Built-in import-context state holding the bit-packed
|
|
118
|
+
* {@link TextFormatType} formats that should apply to {@link TextNode}s
|
|
119
|
+
* produced during the current subtree. Used by inline-format wrappers
|
|
120
|
+
* (`<b>`, `<i>`, `<u>`, …) to propagate formatting through the context
|
|
121
|
+
* record instead of via the legacy `forChild` chain.
|
|
122
|
+
*
|
|
123
|
+
* @experimental
|
|
124
|
+
*/
|
|
125
|
+
export const ImportTextFormat: ImportStateConfig<number> = createImportState(
|
|
126
|
+
'textFormat',
|
|
127
|
+
() => 0,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Built-in import-context state holding a parsed CSS-style record
|
|
132
|
+
* (the {@link getStyleObjectFromCSS} shape) that should apply to
|
|
133
|
+
* {@link TextNode}s produced during the current subtree. Mirrors the
|
|
134
|
+
* format-bit propagation in {@link ImportTextFormat} for properties
|
|
135
|
+
* that don't fit into the format bit mask — `color`, `font-family`,
|
|
136
|
+
* `font-size`, etc.
|
|
137
|
+
*
|
|
138
|
+
* Ancestor rules that contribute a style branch the context with a
|
|
139
|
+
* merged record; the core `#text` rule materializes the non-empty
|
|
140
|
+
* record to a CSS string and calls `setStyle` on the new TextNode.
|
|
141
|
+
* Once TextNode adopts a parsed style record, the materialization
|
|
142
|
+
* step will go away.
|
|
143
|
+
*
|
|
144
|
+
* @experimental
|
|
145
|
+
*/
|
|
146
|
+
export const ImportTextStyle: ImportStateConfig<
|
|
147
|
+
Readonly<Record<string, string>>
|
|
148
|
+
> = createImportState<Readonly<Record<string, string>>>(
|
|
149
|
+
'textStyle',
|
|
150
|
+
() => ({}),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Determines whether a given DOM element should be treated as preserving
|
|
155
|
+
* whitespace (i.e. text content under it is not collapsed and is split on
|
|
156
|
+
* `\n` / `\t` into `LineBreakNode` / `TabNode`). The default matches the
|
|
157
|
+
* legacy behavior: the element itself is `<pre>` or its inline
|
|
158
|
+
* `white-space` style begins with `'pre'`.
|
|
159
|
+
*
|
|
160
|
+
* @experimental
|
|
161
|
+
*/
|
|
162
|
+
export type IsPreserveWhitespaceDom = (node: Node) => boolean;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Determines whether a given DOM node sits on the same visual line as its
|
|
166
|
+
* adjacent text siblings, governing whether leading/trailing whitespace in
|
|
167
|
+
* a `#text` is collapsed against neighbors. The default consults
|
|
168
|
+
* {@link isInlineDomNode} from `lexical` (style.display or a fixed inline
|
|
169
|
+
* tag-name set) and additionally treats elements with an explicit
|
|
170
|
+
* non-inline `display` style as block.
|
|
171
|
+
*
|
|
172
|
+
* @experimental
|
|
173
|
+
*/
|
|
174
|
+
export type IsInlineForWhitespace = (node: Node) => boolean;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Configuration for the core text whitespace-collapse logic. Override via
|
|
178
|
+
* {@link ImportWhitespaceConfig} either as a `contextDefaults` entry on
|
|
179
|
+
* the {@link DOMImportExtension} or per-call on `$generateNodesFromDOM`'s
|
|
180
|
+
* `context` option.
|
|
181
|
+
*
|
|
182
|
+
* @experimental
|
|
183
|
+
*/
|
|
184
|
+
export interface WhitespaceImportConfig {
|
|
185
|
+
/** See {@link IsPreserveWhitespaceDom}. */
|
|
186
|
+
readonly preservesWhitespace: IsPreserveWhitespaceDom;
|
|
187
|
+
/** See {@link IsInlineForWhitespace}. */
|
|
188
|
+
readonly isInline: IsInlineForWhitespace;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Default {@link WhitespaceImportConfig.preservesWhitespace}: matches
|
|
193
|
+
* `<pre>` and any element with `white-space: pre*`.
|
|
194
|
+
*
|
|
195
|
+
* @experimental
|
|
196
|
+
*/
|
|
197
|
+
export function defaultPreservesWhitespace(node: Node): boolean {
|
|
198
|
+
if (!isHTMLElement(node)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (node.nodeName === 'PRE') {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
const ws = node.style.whiteSpace;
|
|
205
|
+
return typeof ws === 'string' && ws.startsWith('pre');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Default {@link WhitespaceImportConfig.isInline}: treats an element as
|
|
210
|
+
* inline iff its inline `display` style is `inline*` OR (no explicit
|
|
211
|
+
* non-inline display) its nodeName is a known inline tag (`isInlineDomNode`).
|
|
212
|
+
* Text nodes are always inline; comments and other non-elements are not.
|
|
213
|
+
*
|
|
214
|
+
* @experimental
|
|
215
|
+
*/
|
|
216
|
+
export function defaultIsInline(node: Node): boolean {
|
|
217
|
+
if (isDOMTextNode(node)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (!isHTMLElement(node)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const display = node.style.display;
|
|
224
|
+
if (display) {
|
|
225
|
+
return display.startsWith('inline');
|
|
226
|
+
}
|
|
227
|
+
if (isBlockDomNode(node)) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return isInlineDomNode(node);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Built-in import-context state controlling text-node whitespace handling
|
|
235
|
+
* (collapse vs. preserve, what counts as an inline sibling). Override per
|
|
236
|
+
* editor via {@link DOMImportConfig.contextDefaults} or per call via
|
|
237
|
+
* {@link GenerateNodesFromDOMOptions.context}.
|
|
238
|
+
*
|
|
239
|
+
* @experimental
|
|
240
|
+
*/
|
|
241
|
+
export const ImportWhitespaceConfig: ImportStateConfig<WhitespaceImportConfig> =
|
|
242
|
+
createImportState<WhitespaceImportConfig>('whitespaceConfig', () => ({
|
|
243
|
+
isInline: defaultIsInline,
|
|
244
|
+
preservesWhitespace: defaultPreservesWhitespace,
|
|
245
|
+
}));
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Built-in session slot for runtime overlay rules that should be in
|
|
249
|
+
* effect for the entire walk. A preprocessor writes here when it wants
|
|
250
|
+
* to conditionally install handling for a particular paste source
|
|
251
|
+
* (e.g. "if the Microsoft Word generator meta tag is present, push the
|
|
252
|
+
* Word-paste overlay"). Each entry contributes an overlay dispatcher
|
|
253
|
+
* to the runtime's overlay stack; later array entries are higher
|
|
254
|
+
* priority. Use `ctx.session.update(ImportOverlays, prev => […])` to
|
|
255
|
+
* append.
|
|
256
|
+
*
|
|
257
|
+
* This is the walk-wide counterpart to
|
|
258
|
+
* `$importChildren({rules: …})` (which scopes an overlay to one
|
|
259
|
+
* subtree): write to {@link ImportOverlays} when the overlay should
|
|
260
|
+
* apply for the whole document; use `$importChildren`'s `rules` when
|
|
261
|
+
* the overlay should only apply for a deeper region.
|
|
262
|
+
*
|
|
263
|
+
* @experimental
|
|
264
|
+
*/
|
|
265
|
+
export const ImportOverlays: ImportStateConfig<
|
|
266
|
+
readonly CompiledOverlayRules[]
|
|
267
|
+
> = createImportState<readonly CompiledOverlayRules[]>(
|
|
268
|
+
'importOverlays',
|
|
269
|
+
() => [],
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The session IS the root-layer {@link ContextRecord} of the walk. Reads
|
|
274
|
+
* fall through the prototype chain to the editor's `contextDefaults`,
|
|
275
|
+
* writes mutate the record's own properties, and any branch pushed by
|
|
276
|
+
* `$importChildren({context})` sits above this layer and can shadow
|
|
277
|
+
* (but does not overwrite) slots.
|
|
278
|
+
*
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
export class ImportSessionImpl implements ImportSession {
|
|
282
|
+
constructor(readonly record: ImportContextRecord) {}
|
|
283
|
+
get<V>(cfg: ImportStateConfig<V>): V {
|
|
284
|
+
return getContextValue(this.record, cfg);
|
|
285
|
+
}
|
|
286
|
+
set<V>(cfg: ImportStateConfig<V>, value: V): void {
|
|
287
|
+
this.record[cfg.key] = value;
|
|
288
|
+
}
|
|
289
|
+
update<V>(cfg: ImportStateConfig<V>, updater: (prev: V) => V): void {
|
|
290
|
+
this.record[cfg.key] = updater(getContextValue(this.record, cfg));
|
|
291
|
+
}
|
|
292
|
+
has<V>(cfg: ImportStateConfig<V>): boolean {
|
|
293
|
+
return Object.prototype.hasOwnProperty.call(this.record, cfg.key);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function getDefaultImportContext(
|
|
298
|
+
editor: LexicalEditor,
|
|
299
|
+
): undefined | ContextRecord<typeof DOMImportContextSymbol> {
|
|
300
|
+
const dep = getPeerDependencyFromEditor<typeof DOMImportExtension>(
|
|
301
|
+
editor,
|
|
302
|
+
DOMImportExtensionName,
|
|
303
|
+
);
|
|
304
|
+
return dep ? dep.output.defaults : undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function getImportContext(
|
|
308
|
+
editor: LexicalEditor,
|
|
309
|
+
): undefined | ContextRecord<typeof DOMImportContextSymbol> {
|
|
310
|
+
return (
|
|
311
|
+
getContextRecord(DOMImportContextSymbol, editor) ||
|
|
312
|
+
getDefaultImportContext(editor)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Read an import context value during an import operation.
|
|
318
|
+
* @experimental
|
|
319
|
+
*/
|
|
320
|
+
export function $getImportContextValue<V>(
|
|
321
|
+
cfg: ImportStateConfig<V>,
|
|
322
|
+
editor: LexicalEditor = $getEditor(),
|
|
323
|
+
): V {
|
|
324
|
+
return getContextValue(getImportContext(editor), cfg);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Run `f` with the given context pairs applied on top of the editor's
|
|
329
|
+
* current import context.
|
|
330
|
+
*
|
|
331
|
+
* @experimental
|
|
332
|
+
*/
|
|
333
|
+
export const $withImportContext: (
|
|
334
|
+
cfg: readonly ImportContextPairOrUpdater[],
|
|
335
|
+
editor?: LexicalEditor,
|
|
336
|
+
) => <T>(f: () => T) => T = $withContext(
|
|
337
|
+
DOMImportContextSymbol,
|
|
338
|
+
getDefaultImportContext,
|
|
339
|
+
);
|