@lexical/html
Advanced tools
| import { type EditorDOMRenderConfig, InitialEditorConfig, Klass, type LexicalNode } from 'lexical'; | ||
| import { AnyDOMRenderMatch, DOMRenderConfig } from './types'; | ||
| interface TypeRecord { | ||
| readonly klass: Klass<LexicalNode>; | ||
| readonly types: { | ||
| [NodeAndSubclasses in string]?: boolean; | ||
| }; | ||
| } | ||
| type TypeTree = { | ||
| [NodeType in string]?: TypeRecord; | ||
| }; | ||
| export declare function buildTypeTree(editorConfig: Pick<InitialEditorConfig, 'nodes'>): TypeTree; | ||
| type TypeRender<T> = { | ||
| [NodeType in string]?: T[]; | ||
| }; | ||
| type AnyRender<T> = readonly [(node: LexicalNode) => boolean, T] | readonly ['types', TypeRender<T>]; | ||
| type PreEditorDOMRenderConfig = { | ||
| [K in keyof EditorDOMRenderConfig]: AnyRender<AnyDOMRenderMatch[K]>[]; | ||
| }; | ||
| export declare function precompileDOMRenderConfigOverrides(editorConfig: Pick<InitialEditorConfig, 'nodes'>, overrides: DOMRenderConfig['overrides']): PreEditorDOMRenderConfig; | ||
| export declare function compileDOMRenderConfigOverrides(editorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>, { overrides }: Pick<DOMRenderConfig, 'overrides'>): EditorDOMRenderConfig; | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| export declare const DOMRenderExtensionName = "@lexical/html/DOM"; | ||
| export declare const DOMRenderContextSymbol: unique symbol; | ||
| export declare const DOMImportExtensionName = "@lexical/html/DOMImport"; | ||
| export declare const DOMImportContextSymbol: unique symbol; | ||
| export declare const ALWAYS_TRUE: () => true; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { AnyContextConfigPairOrUpdater, AnyContextSymbol, ContextConfig, ContextConfigPair, ContextConfigUpdater, ContextRecord } from './types'; | ||
| import { type LexicalEditor } from 'lexical'; | ||
| type WithContext<Ctx extends AnyContextSymbol> = { | ||
| [K in Ctx]?: undefined | ContextRecord<Ctx>; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The LexicalEditor with context | ||
| */ | ||
| export type EditorContext = { | ||
| editor: LexicalEditor; | ||
| } & WithContext<AnyContextSymbol>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value or defaultValue of cfg | ||
| */ | ||
| export declare function getContextValue<Ctx extends AnyContextSymbol, V>(contextRecord: undefined | ContextRecord<Ctx>, cfg: ContextConfig<Ctx, V>): V; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Read and delete cfg from this layer of context | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value of the configuration that was removed | ||
| */ | ||
| export declare function popOwnContextValue<Ctx extends AnyContextSymbol, V>(contextRecord: ContextRecord<Ctx>, cfg: ContextConfig<Ctx, V>): undefined | V; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Get the value without a default | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The current value in this context or `undefined` if not set | ||
| */ | ||
| export declare function getOwnContextValue<Ctx extends AnyContextSymbol, V>(contextRecord: ContextRecord<Ctx>, cfg: ContextConfig<Ctx, V>): undefined | V; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol) | ||
| * @param editor The editor | ||
| * @returns The current context or undefined | ||
| */ | ||
| export declare function getContextRecord<Ctx extends AnyContextSymbol>(sym: Ctx, editor: LexicalEditor): undefined | ContextRecord<Ctx>; | ||
| /** | ||
| * Construct a new context from a parent context and pairs | ||
| * | ||
| * @param pairs The pairs and updaters to build the context from | ||
| * @param parent The parent context | ||
| * @returns The new context | ||
| */ | ||
| export declare function contextFromPairs<Ctx extends AnyContextSymbol>(pairs: readonly AnyContextConfigPairOrUpdater<Ctx>[], parent: undefined | ContextRecord<Ctx>): undefined | ContextRecord<Ctx>; | ||
| /** | ||
| * Create a context config pair that sets a value in the render context. | ||
| * @experimental | ||
| */ | ||
| export declare function contextValue<Ctx extends AnyContextSymbol, V>(cfg: ContextConfig<Ctx, V>, value: V): ContextConfigPair<Ctx, V>; | ||
| /** | ||
| * Create a context config updater that transforms a value in the render context. | ||
| * @experimental | ||
| */ | ||
| export declare function contextUpdater<Ctx extends AnyContextSymbol, V>(cfg: ContextConfig<Ctx, V>, updater: (prev: V) => V): ContextConfigUpdater<Ctx, V>; | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function $withFullContext<Ctx extends AnyContextSymbol, T>(sym: Ctx, contextRecord: ContextRecord<Ctx>, f: () => T, editor?: LexicalEditor): T; | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function $withContext<Ctx extends AnyContextSymbol>(sym: Ctx, $defaults?: (editor: LexicalEditor) => undefined | ContextRecord<Ctx>): (cfg: readonly AnyContextConfigPairOrUpdater<Ctx>[], editor?: LexicalEditor) => (<T>(f: () => T) => T); | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function createContextState<Tag extends symbol, V>(tag: Tag, name: string, getDefaultValue: () => V, isEqual?: (a: V, b: V) => boolean): ContextConfig<Tag, V>; | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMOverrideOptions, DOMRenderMatch, DOMRenderMatchConfig, NodeMatch } from './types'; | ||
| import type { LexicalNode } from 'lexical'; | ||
| /** | ||
| * A convenience function for type inference when constructing DOM overrides for | ||
| * use with {@link DOMRenderExtension}. | ||
| * | ||
| * The optional `options` argument controls *whether* the override is installed | ||
| * based only on render context — `disabledForEditor` gates residency in the | ||
| * editor's render pipeline (reconciliation), `disabledForSession` gates | ||
| * participation in a single export/generate session. See {@link DOMOverrideOptions}. | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function domOverride(nodes: '*', config: DOMRenderMatchConfig<LexicalNode>, options?: DOMOverrideOptions): DOMRenderMatch<LexicalNode>; | ||
| export declare function domOverride<T extends LexicalNode>(nodes: readonly NodeMatch<T>[], config: DOMRenderMatchConfig<T>, options?: DOMOverrideOptions): DOMRenderMatch<T>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMRenderConfig, DOMRenderExtensionOutput } from './types'; | ||
| import type { InitialEditorConfig } from 'lexical'; | ||
| /** @internal The result returned from {@link DOMRenderExtension}'s `init`. */ | ||
| interface DOMRenderInitResult { | ||
| /** | ||
| * The `nodes` and base `dom` captured from the editor config before `dom` | ||
| * is overwritten with the compiled config — the only fields the runtime | ||
| * needs to recompile. | ||
| */ | ||
| initialEditorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * An extension that allows overriding the render and export behavior for an | ||
| * editor. This is highly experimental and subject to change from one version | ||
| * to the next. | ||
| **/ | ||
| export declare const DOMRenderExtension: import("lexical").LexicalExtension<DOMRenderConfig, "@lexical/html/DOM", DOMRenderExtensionOutput, DOMRenderInitResult>; | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { AnyDOMRenderMatch, AnyRenderStateConfigPairOrUpdater, ContextRecord, DOMRenderRuntime, RenderStateConfig } from './types'; | ||
| import type { EditorDOMRenderConfig, InitialEditorConfig, LexicalEditor } from 'lexical'; | ||
| import { DOMRenderContextSymbol } from './constants'; | ||
| type RenderContextRecord = ContextRecord<typeof DOMRenderContextSymbol>; | ||
| /** | ||
| * The mutable, writable editor-level context record. Reads of a render state | ||
| * during reconciliation (and as the base layer of a session) fall through to | ||
| * this record, and it is the layer the `disabledForEditor` predicates read. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function createEditorContextRecord(contextDefaults: readonly AnyRenderStateConfigPairOrUpdater[]): RenderContextRecord; | ||
| /** | ||
| * Filter the configured overrides down to those that are resident in the | ||
| * editor's render config, removing any whose `disabledForEditor` predicate | ||
| * returns `true` for the given editor context. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function filterEditorInstalled(overrides: readonly AnyDOMRenderMatch[], record: RenderContextRecord): AnyDOMRenderMatch[]; | ||
| /** | ||
| * Per-editor runtime backing {@link DOMRenderExtension}'s conditional | ||
| * overrides and imperative editor context. See {@link DOMRenderRuntime}. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare class DOMRenderRuntimeImpl implements DOMRenderRuntime { | ||
| readonly editor: LexicalEditor; | ||
| /** | ||
| * The `nodes` and base `dom` captured at `init` (before `dom` was | ||
| * overwritten with the compiled config) — the clean base for every recompile. | ||
| */ | ||
| readonly initialEditorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>; | ||
| readonly overrides: readonly AnyDOMRenderMatch[]; | ||
| readonly editorContext: RenderContextRecord; | ||
| readonly hasSessionGates: boolean; | ||
| installed: readonly AnyDOMRenderMatch[]; | ||
| /** Memoized session configs keyed by the set of session-disabled overrides. */ | ||
| private readonly sessionCache; | ||
| constructor(editor: LexicalEditor, initialEditorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>, overrides: readonly AnyDOMRenderMatch[], editorContext: RenderContextRecord); | ||
| setContextValue<V>(cfg: RenderStateConfig<V>, value: V): void; | ||
| getSessionConfig(): EditorDOMRenderConfig; | ||
| } | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { AnyDOMImportRule, DOMImportFn } from './types'; | ||
| import { type Predicate } from './sel'; | ||
| /** @internal */ | ||
| export interface CompiledRule { | ||
| readonly name: string; | ||
| readonly predicate: Predicate; | ||
| readonly $import: DOMImportFn<Node, Record<string, RegExpMatchArray>>; | ||
| } | ||
| /** @internal */ | ||
| export interface CompiledDispatch { | ||
| /** All rules in registration order. Index = registration order. */ | ||
| readonly rules: readonly CompiledRule[]; | ||
| /** | ||
| * For each (uppercased) HTML tag name, the ordered list of rule indices | ||
| * considered when dispatching that tag. Includes interleaved wildcard | ||
| * element rules so a single iteration handles both. | ||
| */ | ||
| readonly byTag: ReadonlyMap<string, readonly number[]>; | ||
| /** Indices of rules whose match has no tag restriction. */ | ||
| readonly wildcardIndices: readonly number[]; | ||
| /** Indices of rules whose match is `sel.text()`. */ | ||
| readonly textIndices: readonly number[]; | ||
| /** Indices of rules whose match is `sel.comment()`. */ | ||
| readonly commentIndices: readonly number[]; | ||
| } | ||
| /** | ||
| * Compile an ordered list of {@link DOMImportRule}s into the dispatch tables | ||
| * used by the import runtime. The rule at index 0 is the highest-priority | ||
| * (`mergeConfig` prepends partial.rules so later-merged extensions land | ||
| * first). | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function compileImportRules(rules: readonly AnyDOMImportRule[]): CompiledDispatch; | ||
| /** | ||
| * Look up the (already interleaved) rule indices relevant to `node`. Element | ||
| * nodes hit `byTag` (with wildcards merged in) or fall back to the wildcard | ||
| * bucket if no tag-specific rules exist; text and comment nodes use their | ||
| * own buckets. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function getDispatchIndices(dispatch: CompiledDispatch, node: Node): readonly number[]; |
| /** | ||
| * Bundles {@link CoreImportRules} into a {@link DOMImportExtension}-aware | ||
| * extension. Depend on this from your editor (directly or via richer | ||
| * extensions like `RichTextImportExtension`) to get the equivalent of the | ||
| * legacy core `importDOM` behavior for `<p>`, `<span>`, `<b>`, | ||
| * `<strong>`, `<em>`, `<i>`, `<code>`, `<mark>`, `<s>`, `<sub>`, `<sup>`, | ||
| * `<u>`, `<br>`, and `#text`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const CoreImportExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@lexical/html/CoreImport", unknown, unknown>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import { type ElementFormatType } from 'lexical'; | ||
| /** | ||
| * True if `value` is a non-empty {@link ElementFormatType} (matches one of | ||
| * the supported `text-align` / legacy `align`-attribute values). | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function isAlignmentValue(value: string): value is ElementFormatType; | ||
| /** | ||
| * Rules covering the {@link ParagraphNode}, {@link TextNode}, | ||
| * {@link LineBreakNode}, and {@link TabNode} cases that the legacy | ||
| * `importDOM` machinery in `@lexical/lexical` handled. Intended to be | ||
| * registered as a dependency of every editor that uses | ||
| * {@link DOMImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const CoreImportRules: (import("./types").DOMImportRule<import("./types").ElementSelectorBuilder<HTMLElement | HTMLSpanElement, Record<string, never>>> | import("./types").DOMImportRule<import("./types").CompiledSelector<Text, Record<string, RegExpMatchArray>>>)[]; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { CompiledSelector, DOMImportFn, DOMImportRule } from './types'; | ||
| /** | ||
| * Identity helper that infers a rule's matched node type and capture map | ||
| * from its `match` selector and threads them into the `$import` signature. | ||
| * Usage: | ||
| * | ||
| * ```ts | ||
| * defineImportRule({ | ||
| * name: '@lexical/list/li', | ||
| * match: sel.tag('li'), | ||
| * $import: (ctx, el, $next) => { | ||
| * // el: HTMLLIElement | ||
| * return [$createListItemNode()]; | ||
| * }, | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function defineImportRule<const S extends CompiledSelector>(rule: { | ||
| readonly name?: string; | ||
| readonly match: S; | ||
| readonly $import: DOMImportFn<S extends CompiledSelector<infer N, Record<string, RegExpMatchArray>> ? N : Node, S extends CompiledSelector<Node, infer C> ? C : Record<string, never>>; | ||
| }): DOMImportRule<S>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { AnyDOMImportRule } from './types'; | ||
| import { type CompiledDispatch } from './compileImportRules'; | ||
| /** | ||
| * Opaque handle for a pre-compiled set of overlay rules. Produce one with | ||
| * {@link defineOverlayRules} and pass it to | ||
| * {@link DOMImportContext.$importChildren} via | ||
| * {@link ImportChildrenOpts.rules}. | ||
| * | ||
| * To merge two or more overlays into a single one, pass them (alongside | ||
| * raw {@link DOMImportRule}s if desired) to a fresh | ||
| * {@link defineOverlayRules} — earlier arguments are higher priority. | ||
| * | ||
| * The internal shape is intentionally not part of the public API: it's a | ||
| * compiled dispatch table tagged with `__type` so callers cannot pass a | ||
| * raw rule array where a compiled overlay is expected. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface CompiledOverlayRules { | ||
| readonly __type: 'CompiledOverlayRules'; | ||
| /** @internal */ | ||
| readonly dispatch: CompiledDispatch; | ||
| /** | ||
| * @internal — flattened source rules retained so an overlay can be | ||
| * recompiled when it is passed to another {@link defineOverlayRules} | ||
| * call or as part of {@link DOMImportConfig.rules}. | ||
| */ | ||
| readonly rules: readonly AnyDOMImportRule[]; | ||
| } | ||
| /** | ||
| * An entry accepted everywhere rules are configured (overlay | ||
| * definitions, {@link DOMImportConfig.rules}). Either a single | ||
| * {@link DOMImportRule} or a {@link CompiledOverlayRules} produced by | ||
| * a previous {@link defineOverlayRules} call — passing the latter | ||
| * inlines the overlay's rules at this position in priority order. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type DOMImportRuleEntry = AnyDOMImportRule | CompiledOverlayRules; | ||
| /** @internal */ | ||
| export declare function flattenRuleEntries(entries: readonly DOMImportRuleEntry[]): AnyDOMImportRule[]; | ||
| /** | ||
| * Pre-compile a set of {@link DOMImportRuleEntry}s into a | ||
| * {@link CompiledOverlayRules} handle that can be installed via | ||
| * `ctx.$importChildren(el, {rules: …})`. | ||
| * | ||
| * Entries can be raw {@link DOMImportRule}s or other | ||
| * {@link CompiledOverlayRules} (the latter are inlined at their | ||
| * position in priority order, so the same call composes any number of | ||
| * overlays). Earlier entries are higher priority. | ||
| * | ||
| * Overlay rules installed as a raw array would be re-compiled on every | ||
| * `$importChildren` call. For overlays that are reused (e.g. a GitHub | ||
| * code-table rule that wraps every matching table), call this once at | ||
| * module scope so the dispatch table is built up front. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function defineOverlayRules(entries: readonly DOMImportRuleEntry[]): CompiledOverlayRules; |
| import type { DOMImportRuleEntry } from './defineOverlayRules'; | ||
| import type { DOMImportExtensionOutput, DOMPreprocessFn, GenerateNodesFromDOMOptions, ImportContextPairOrUpdater } from './types'; | ||
| import { type LexicalNode } from 'lexical'; | ||
| /** | ||
| * Configuration for {@link DOMImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportConfig { | ||
| /** | ||
| * The set of rules contributed by this extension and its dependencies. | ||
| * Entries can be raw {@link DOMImportRule}s or a | ||
| * {@link CompiledOverlayRules} produced by {@link defineOverlayRules} | ||
| * (the latter is inlined in priority order — useful for libraries | ||
| * that already publish a compiled overlay). | ||
| * | ||
| * Rules are dispatched in priority order: rules contributed by | ||
| * extensions merged later (i.e. closer to the editor root) run first | ||
| * and may call `$next()` to delegate to lower-priority rules. | ||
| * | ||
| * `mergeConfig` prepends `partial.rules` to existing `rules`, so later | ||
| * configuration carries higher priority. | ||
| */ | ||
| readonly rules: readonly DOMImportRuleEntry[]; | ||
| /** | ||
| * Default context pairs applied to every `$generateNodesFromDOM` call. | ||
| * Per-call overrides can be supplied via | ||
| * {@link GenerateNodesFromDOMOptions.context}. | ||
| */ | ||
| readonly contextDefaults: readonly ImportContextPairOrUpdater[]; | ||
| /** | ||
| * Functions run in order on the DOM before walking begins, mutating in | ||
| * place. The default config registers | ||
| * {@link $inlineStylesFromStyleSheets} (resolves `<style>` rules to | ||
| * inline styles so the rules' style-driven matchers see them); apps | ||
| * append additional preprocessors (e.g. strip unsafe elements, | ||
| * normalize attributes, resolve relative URLs). | ||
| * | ||
| * `mergeConfig` appends, so each contributing extension's preprocessors | ||
| * run in dependency order. Per-call preprocessors registered via | ||
| * {@link GenerateNodesFromDOMOptions.preprocess} run AFTER these. | ||
| */ | ||
| readonly preprocess: readonly DOMPreprocessFn[]; | ||
| } | ||
| /** | ||
| * Lowest-priority catch-all rule used as the default `config.rules` entry | ||
| * for {@link DOMImportExtension}: descends into the element's children | ||
| * and returns whatever they produced. With no other matching rule, an | ||
| * element vanishes and its contents are inserted in its place — the | ||
| * legacy `$createNodesFromDOM` hoisting behavior, but now expressed as a | ||
| * regular rule that apps can override (e.g. with a `sel.any()` rule that | ||
| * captures and discards unknown elements). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const DefaultHoistRule: import("./types").DOMImportRule<import("./types").ElementSelectorBuilder<HTMLElement, Record<string, never>>>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Extension-based replacement for the legacy `importDOM` / `DOMConversion` | ||
| * machinery. Rules are contributed via configuration (see | ||
| * {@link DOMImportConfig.rules}), compiled into a tag-bucketed dispatcher at | ||
| * editor build time, and consumed via the extension's | ||
| * {@link DOMImportExtensionOutput.$generateNodesFromDOM} output. | ||
| * | ||
| * The legacy `$generateNodesFromDOM` continues to work in parallel; the | ||
| * intent is to migrate node packages over to this extension incrementally. | ||
| */ | ||
| export declare const DOMImportExtension: import("lexical").LexicalExtension<DOMImportConfig, "@lexical/html/DOMImport", DOMImportExtensionOutput, void>; | ||
| /** | ||
| * Look up the editor's {@link DOMImportExtension} and run its | ||
| * `$generateNodesFromDOM`. Designed as a drop-in replacement for the | ||
| * legacy `$generateNodesFromDOM(editor, dom)` signature so it can be | ||
| * supplied to `ClipboardImportExtension.$generateNodesFromDOM` (or any | ||
| * other consumer that wants to route through the extension pipeline). | ||
| * | ||
| * Throws if the editor was not built with {@link DOMImportExtension} as a | ||
| * dependency. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function $generateNodesFromDOMViaExtension(dom: Document | ParentNode, options?: GenerateNodesFromDOMOptions): LexicalNode[]; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * Import rules for {@link HorizontalRuleNode}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const HorizontalRuleImportRules: import("./types").DOMImportRule<import("./types").ElementSelectorBuilder<HTMLHRElement, Record<string, never>>>[]; | ||
| /** | ||
| * Bundles {@link HorizontalRuleImportRules} (plus | ||
| * {@link CoreImportExtension}) into a single dependency. The legacy | ||
| * {@link HorizontalRuleExtension.importDOM} continues to work in parallel; | ||
| * depend on this extension to opt into the new pipeline. | ||
| * | ||
| * Lives in `@lexical/html` (not `@lexical/extension`) because | ||
| * {@link DOMImportExtension} itself is in `@lexical/html`, and | ||
| * `@lexical/extension` is upstream of `@lexical/html` in the dependency | ||
| * graph — same arrangement as {@link CoreImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const HorizontalRuleImportExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@lexical/html/HorizontalRuleImport", unknown, unknown>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { ContextRecord } from '../types'; | ||
| import type { CompiledOverlayRules } from './defineOverlayRules'; | ||
| import type { ImportContextPairOrUpdater, ImportSession, ImportStateConfig } from './types'; | ||
| import { type LexicalEditor } from 'lexical'; | ||
| import { DOMImportContextSymbol } from '../constants'; | ||
| type ImportContextRecord = ContextRecord<typeof DOMImportContextSymbol>; | ||
| /** | ||
| * Create an import context state. The phantom symbol prevents accidental | ||
| * use of a render-context state in an import context (and vice versa). | ||
| * | ||
| * Note: to support the value-or-updater pattern, `V` cannot be a function | ||
| * type; wrap it in an array or object if needed. | ||
| * | ||
| * `getDefaultValue` is called **once at state creation** and the result is | ||
| * shared between every session that reads the state without first writing | ||
| * a value. Defaults must therefore be immutable (primitives, frozen | ||
| * objects, or read-only arrays / records). If your state needs mutable | ||
| * per-session storage, lazily initialize it inside your rule (e.g. | ||
| * `if (!ctx.session.has(cfg)) ctx.session.set(cfg, new …())`). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function createImportState<V>(name: string, getDefaultValue: () => V, isEqual?: (a: V, b: V) => boolean): ImportStateConfig<V>; | ||
| /** | ||
| * The kind of operation that produced this import. Lets rules adapt | ||
| * their behavior (e.g. preserve more whitespace on `'paste'`). | ||
| * Defaults to `'unknown'`. Apps that need a different vocabulary can | ||
| * define their own {@link ImportStateConfig} with whatever value type | ||
| * they want. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type ImportSourceKind = 'paste' | 'unknown'; | ||
| /** | ||
| * Built-in import-context state identifying how this import was initiated. | ||
| * Callers of `$generateNodesFromDOM` should set it via the `context` option. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ImportSource: ImportStateConfig<ImportSourceKind>; | ||
| /** | ||
| * Built-in import-context state holding the {@link DataTransfer} the | ||
| * import was sourced from, if any. `null` outside paste/drop flows. | ||
| * | ||
| * The clipboard import pipeline passes the original `DataTransfer` | ||
| * through to its per-MIME-type handler stack (see | ||
| * {@link ImportMimeTypeFunction}); handlers that route HTML through | ||
| * the {@link DOMImportExtension} pipeline should forward it into the | ||
| * walk via `context: [contextValue(ImportSourceDataTransfer, | ||
| * dataTransfer)]` so rules and preprocessors can call | ||
| * `ctx.get(ImportSourceDataTransfer)` to inspect companion MIME types | ||
| * (e.g. an `'application/rtf'` alternative or an attached | ||
| * `'application/x-officedrawing'` payload), the file list, or any | ||
| * custom drag-and-drop slot. | ||
| * | ||
| * Use sparingly: the safer pattern is to decide *which* MIME-type | ||
| * payload to walk in the clipboard handler stack and hand a finalized | ||
| * DOM to the rules; only fall back to peeking at `ImportSourceDataTransfer` | ||
| * when the source-detection signal genuinely lives in a companion | ||
| * slot. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ImportSourceDataTransfer: ImportStateConfig<DataTransfer | null>; | ||
| /** | ||
| * Built-in import-context state holding the bit-packed | ||
| * {@link TextFormatType} formats that should apply to {@link TextNode}s | ||
| * produced during the current subtree. Used by inline-format wrappers | ||
| * (`<b>`, `<i>`, `<u>`, …) to propagate formatting through the context | ||
| * record instead of via the legacy `forChild` chain. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ImportTextFormat: ImportStateConfig<number>; | ||
| /** | ||
| * Built-in import-context state holding a parsed CSS-style record | ||
| * (the {@link getStyleObjectFromCSS} shape) that should apply to | ||
| * {@link TextNode}s produced during the current subtree. Mirrors the | ||
| * format-bit propagation in {@link ImportTextFormat} for properties | ||
| * that don't fit into the format bit mask — `color`, `font-family`, | ||
| * `font-size`, etc. | ||
| * | ||
| * Ancestor rules that contribute a style branch the context with a | ||
| * merged record; the core `#text` rule materializes the non-empty | ||
| * record to a CSS string and calls `setStyle` on the new TextNode. | ||
| * Once TextNode adopts a parsed style record, the materialization | ||
| * step will go away. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ImportTextStyle: ImportStateConfig<Readonly<Record<string, string>>>; | ||
| /** | ||
| * Determines whether a given DOM element should be treated as preserving | ||
| * whitespace (i.e. text content under it is not collapsed and is split on | ||
| * `\n` / `\t` into `LineBreakNode` / `TabNode`). The default matches the | ||
| * legacy behavior: the element itself is `<pre>` or its inline | ||
| * `white-space` style begins with `'pre'`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type IsPreserveWhitespaceDom = (node: Node) => boolean; | ||
| /** | ||
| * Determines whether a given DOM node sits on the same visual line as its | ||
| * adjacent text siblings, governing whether leading/trailing whitespace in | ||
| * a `#text` is collapsed against neighbors. The default consults | ||
| * {@link isInlineDomNode} from `lexical` (style.display or a fixed inline | ||
| * tag-name set) and additionally treats elements with an explicit | ||
| * non-inline `display` style as block. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type IsInlineForWhitespace = (node: Node) => boolean; | ||
| /** | ||
| * Configuration for the core text whitespace-collapse logic. Override via | ||
| * {@link ImportWhitespaceConfig} either as a `contextDefaults` entry on | ||
| * the {@link DOMImportExtension} or per-call on `$generateNodesFromDOM`'s | ||
| * `context` option. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface WhitespaceImportConfig { | ||
| /** See {@link IsPreserveWhitespaceDom}. */ | ||
| readonly preservesWhitespace: IsPreserveWhitespaceDom; | ||
| /** See {@link IsInlineForWhitespace}. */ | ||
| readonly isInline: IsInlineForWhitespace; | ||
| } | ||
| /** | ||
| * Default {@link WhitespaceImportConfig.preservesWhitespace}: matches | ||
| * `<pre>` and any element with `white-space: pre*`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function defaultPreservesWhitespace(node: Node): boolean; | ||
| /** | ||
| * Default {@link WhitespaceImportConfig.isInline}: treats an element as | ||
| * inline iff its inline `display` style is `inline*` OR (no explicit | ||
| * non-inline display) its nodeName is a known inline tag (`isInlineDomNode`). | ||
| * Text nodes are always inline; comments and other non-elements are not. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function defaultIsInline(node: Node): boolean; | ||
| /** | ||
| * Built-in import-context state controlling text-node whitespace handling | ||
| * (collapse vs. preserve, what counts as an inline sibling). Override per | ||
| * editor via {@link DOMImportConfig.contextDefaults} or per call via | ||
| * {@link GenerateNodesFromDOMOptions.context}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ImportWhitespaceConfig: ImportStateConfig<WhitespaceImportConfig>; | ||
| /** | ||
| * Built-in session slot for runtime overlay rules that should be in | ||
| * effect for the entire walk. A preprocessor writes here when it wants | ||
| * to conditionally install handling for a particular paste source | ||
| * (e.g. "if the Microsoft Word generator meta tag is present, push the | ||
| * Word-paste overlay"). Each entry contributes an overlay dispatcher | ||
| * to the runtime's overlay stack; later array entries are higher | ||
| * priority. Use `ctx.session.update(ImportOverlays, prev => […])` to | ||
| * append. | ||
| * | ||
| * This is the walk-wide counterpart to | ||
| * `$importChildren({rules: …})` (which scopes an overlay to one | ||
| * subtree): write to {@link ImportOverlays} when the overlay should | ||
| * apply for the whole document; use `$importChildren`'s `rules` when | ||
| * the overlay should only apply for a deeper region. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const ImportOverlays: ImportStateConfig<readonly CompiledOverlayRules[]>; | ||
| /** | ||
| * The session IS the root-layer {@link ContextRecord} of the walk. Reads | ||
| * fall through the prototype chain to the editor's `contextDefaults`, | ||
| * writes mutate the record's own properties, and any branch pushed by | ||
| * `$importChildren({context})` sits above this layer and can shadow | ||
| * (but does not overwrite) slots. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare class ImportSessionImpl implements ImportSession { | ||
| readonly record: ImportContextRecord; | ||
| constructor(record: ImportContextRecord); | ||
| get<V>(cfg: ImportStateConfig<V>): V; | ||
| set<V>(cfg: ImportStateConfig<V>, value: V): void; | ||
| update<V>(cfg: ImportStateConfig<V>, updater: (prev: V) => V): void; | ||
| has<V>(cfg: ImportStateConfig<V>): boolean; | ||
| } | ||
| /** | ||
| * Read an import context value during an import operation. | ||
| * @experimental | ||
| */ | ||
| export declare function $getImportContextValue<V>(cfg: ImportStateConfig<V>, editor?: LexicalEditor): V; | ||
| /** | ||
| * Run `f` with the given context pairs applied on top of the editor's | ||
| * current import context. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const $withImportContext: (cfg: readonly ImportContextPairOrUpdater[], editor?: LexicalEditor) => <T>(f: () => T) => T; | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import { parseSelector } from './parseCss'; | ||
| /** | ||
| * Combinator-and-parser-based builder for {@link CompiledSelector}s. The | ||
| * runtime shape returned by these factory methods is opaque; consumers | ||
| * should never inspect or construct selector objects directly. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const sel: { | ||
| readonly any: () => import("./types").ElementSelectorBuilder<HTMLElement>; | ||
| readonly comment: () => import("./types").CompiledSelector<Comment>; | ||
| /** | ||
| * Parse a reduced CSS-selector subset and return a builder you can chain | ||
| * combinator methods off of. | ||
| */ | ||
| readonly css: typeof parseSelector; | ||
| readonly tag: <const Tags extends readonly string[]>(...tags: Tags) => import("./types").ElementSelectorBuilder<Tags[number] extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Tags[number]] : HTMLElement>; | ||
| readonly text: () => import("./types").CompiledSelector<Text>; | ||
| }; | ||
| export { CoreImportExtension } from './CoreImportExtension'; | ||
| export { CoreImportRules } from './coreImportRules'; | ||
| export { defineImportRule } from './defineImportRule'; | ||
| export { type CompiledOverlayRules, defineOverlayRules, type DOMImportRuleEntry, } from './defineOverlayRules'; | ||
| export { $generateNodesFromDOMViaExtension, type DOMImportConfig, DOMImportExtension, } from './DOMImportExtension'; | ||
| export { HorizontalRuleImportExtension, HorizontalRuleImportRules, } from './HorizontalRuleImportExtension'; | ||
| export { $getImportContextValue, $withImportContext, createImportState, defaultIsInline, defaultPreservesWhitespace, ImportOverlays, ImportSource, ImportSourceDataTransfer, type ImportSourceKind, ImportTextFormat, ImportTextStyle, ImportWhitespaceConfig, type IsInlineForWhitespace, type IsPreserveWhitespaceDom, type WhitespaceImportConfig, } from './ImportContext'; | ||
| export { $inlineStylesFromStyleSheets } from './inlineStylesFromStyleSheets'; | ||
| export { parseSelector } from './parseCss'; | ||
| export { $distributeInlineWrapper, $isBlockLevel, BlockSchema, InlineSchema, NestedBlockSchema, RootSchema, } from './schemas'; | ||
| export { isElementOfTag } from './sel'; | ||
| export type { AnyDOMImportRule, AttrMatchOptions, CapturesOfSelector, ChildSchema, CompiledSelector, DOMImportContext, DOMImportExtensionOutput, DOMImportFn, DOMImportRule, DOMPreprocessContext, DOMPreprocessFn, ElementSelectorBuilder, GenerateNodesFromDOMOptions, ImportChildrenOpts, ImportContextPairOrUpdater, ImportNodeOpts, ImportSession, ImportStateConfig, NodeOfSelector, StyleMatchOptions, } from './types'; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMPreprocessFn } from './types'; | ||
| /** | ||
| * Inlines CSS rules from `<style>` tags onto matching elements as inline | ||
| * styles. | ||
| * | ||
| * Used by apps like Excel that generate HTML where styles live in | ||
| * class-based `<style>` rules (e.g. `.xl65 { background: #FFFF00; color: | ||
| * blue; }`) rather than inline styles. Since Lexical's import converters | ||
| * read inline styles, we resolve stylesheet rules into inline styles | ||
| * before conversion. | ||
| * | ||
| * Mutates the DOM in-place. Original inline styles always take | ||
| * precedence over stylesheet rules (matching CSS specificity behavior). | ||
| * | ||
| * No-op for {@link ParentNode}s that are not {@link Document}s — only a | ||
| * full document carries `styleSheets` we can iterate. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const $inlineStylesFromStyleSheets: DOMPreprocessFn; | ||
| export declare function $inlineStylesFromStyleSheetsDOM(dom: Document | ParentNode): void; |
| import type { ElementSelectorBuilder } from './types'; | ||
| /** | ||
| * Parse a reduced CSS-selector subset and return a {@link CompiledSelector}. | ||
| * Supported: | ||
| * - Tag (`p`), wildcard (`*`). | ||
| * - Tag list (`h1, h2, h3`). | ||
| * - Class (`.foo`, `.foo.bar`). | ||
| * - ID (`#foo`). | ||
| * - Attribute presence (`[name]`). | ||
| * - Attribute equality (`[name="value"]`, `[name=value]`). | ||
| * | ||
| * Anything outside the subset (regex attribute, inline-style match, | ||
| * combinators, pseudo-classes) is intentionally rejected — chain combinator | ||
| * methods off the returned builder instead. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function parseSelector(source: string): ElementSelectorBuilder<HTMLElement>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { ImportSession } from './types'; | ||
| import { type LexicalEditor, type LexicalNode } from 'lexical'; | ||
| import { type CompiledDispatch } from './compileImportRules'; | ||
| /** | ||
| * Top-level walker for a compiled dispatcher. Iterates the DOM children of | ||
| * `dom` (using the document body if a {@link Document} is passed) and | ||
| * applies `RootSchema` to the produced lexical nodes so runs of inlines are | ||
| * wrapped in paragraphs — same shape as the legacy `$generateNodesFromDOM`. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function $runImport(dispatch: CompiledDispatch, editor: LexicalEditor, dom: Document | ParentNode, session: ImportSession): LexicalNode[]; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { ChildSchema } from './types'; | ||
| import { type ElementNode, type LexicalNode } from 'lexical'; | ||
| /** | ||
| * True if the node fills a block slot at the root or inside another | ||
| * block — covers both ElementNode-style blocks (paragraph, heading, | ||
| * quote) and block-level DecoratorNodes (HorizontalRuleNode, | ||
| * ImageNode-as-block, etc.). Used by {@link BlockSchema}, | ||
| * {@link RootSchema}, and {@link NestedBlockSchema}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function $isBlockLevel(node: LexicalNode): boolean; | ||
| /** | ||
| * Distribute an inline wrapper (`LinkNode`, `MarkNode`, …) across a | ||
| * heterogeneous run of children produced by `$importChildren`, lifting | ||
| * any block children to the top level while keeping the wrapper around | ||
| * the leaf inline content. | ||
| * | ||
| * Use from a rule whose DOM source is an inline element that the | ||
| * browser permitted to enclose block elements — the canonical case is | ||
| * `<a href="…"><h1>title</h1><div>body</div></a>`, which a link rule | ||
| * wants to surface as two block siblings (heading + paragraph), each | ||
| * with its own link wrapping the original inline content. Schemas | ||
| * can't express this because they reason about a parent's children | ||
| * only — they cannot lift the parent out of itself. | ||
| * | ||
| * For each top-level child: | ||
| * - **Inline children** are collected into runs; each run is wrapped | ||
| * in a single fresh wrapper (from `$makeWrapper()`). | ||
| * - **Block children** are descended into: their own children are | ||
| * recursively distributed with `$makeWrapper`, then re-attached so | ||
| * the block keeps its position at the top level. | ||
| * | ||
| * The returned list will contain a mix of blocks and wrapped inline | ||
| * runs. The enclosing schema (typically {@link BlockSchema}) will | ||
| * then package those inline wrappers into paragraphs as usual. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function $distributeInlineWrapper(children: readonly LexicalNode[], $makeWrapper: () => ElementNode): LexicalNode[]; | ||
| /** | ||
| * Apply a {@link ChildSchema} to a flat list of children produced by | ||
| * `$importChildren`. Walks the list once, partitions into accepted vs. | ||
| * rejected runs, packages or drops rejected runs, then runs `$finalize`. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare function $applySchema(schema: ChildSchema, children: LexicalNode[], parent: LexicalNode | null, domParent: Node | null): LexicalNode[]; | ||
| /** | ||
| * Default schema for block-level positions (root of the document, the body | ||
| * of a block element node). Accepts block lexical nodes; packages runs of | ||
| * inline children into fresh paragraph nodes. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const BlockSchema: ChildSchema; | ||
| /** | ||
| * Schema for inline-only positions (the body of an inline lexical node such | ||
| * as a link). Accepts non-block lexical nodes; runs of block children are | ||
| * dropped (`onReject: 'drop'` is the default). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const InlineSchema: ChildSchema; | ||
| /** | ||
| * Schema for nested block positions — the equivalent of the legacy | ||
| * `ArtificialNode__DO_NOT_USE` flow used when a block DOM element appears | ||
| * inside another block lexical ancestor. Accepts block nodes; runs of inline | ||
| * children are emitted with a line break between consecutive runs (instead | ||
| * of being wrapped in a paragraph, which would introduce an extra level of | ||
| * nesting). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const NestedBlockSchema: ChildSchema; | ||
| /** | ||
| * Schema for the topmost level of `$generateNodesFromDOM`. Identical to | ||
| * {@link BlockSchema}; aliased for clarity at the entry point and so it can | ||
| * be overridden separately in the future (e.g. to synthesize a `ListNode` | ||
| * around runs of orphan `ListItemNode`s). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const RootSchema: ChildSchema; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { AttrMatchOptions, CompiledSelector, ElementSelectorBuilder } from './types'; | ||
| /** | ||
| * @internal | ||
| * | ||
| * A predicate that may write into the per-invocation `captures` map. Returns | ||
| * `true` if the rule matches; `false` otherwise. | ||
| */ | ||
| export type Predicate = (node: Node, captures: Record<string, RegExpMatchArray>) => boolean; | ||
| /** @internal */ | ||
| export type SelectorKind = 'element' | 'text' | 'comment'; | ||
| /** @internal The runtime shape of a {@link CompiledSelector}. */ | ||
| export interface SelectorImpl { | ||
| readonly kind: SelectorKind; | ||
| /** | ||
| * Uppercased tag names this selector is restricted to. Empty for wildcard | ||
| * element selectors and for text / comment selectors (dispatched by | ||
| * `kind`). | ||
| */ | ||
| readonly tags: ReadonlySet<string>; | ||
| /** Composed predicate run against a candidate node. */ | ||
| readonly predicate: Predicate; | ||
| } | ||
| /** @internal */ | ||
| export declare function getSelectorImpl(sel: CompiledSelector): SelectorImpl; | ||
| /** | ||
| * @internal | ||
| * | ||
| * Build a selector value from a tag set and a predicate list. Used by the | ||
| * combinator API and the CSS parser. | ||
| */ | ||
| export declare function buildSelector(tags: ReadonlySet<string>, predicates: readonly Predicate[]): ElementSelectorBuilder<HTMLElement>; | ||
| /** @internal */ | ||
| export declare function buildClassAllPredicate(classes: readonly string[]): Predicate; | ||
| /** @internal */ | ||
| export declare function buildClassAnyPredicate(classes: readonly string[]): Predicate; | ||
| /** @internal */ | ||
| export declare function buildAttrPredicate(name: string, value: unknown, options?: AttrMatchOptions): Predicate; | ||
| /** | ||
| * Combinator API for building {@link CompiledSelector}s. The public | ||
| * `sel` is augmented from this in `./index.ts` (where the CSS parser is | ||
| * available without a circular import); consumers outside `@lexical/html` | ||
| * should always import the public `sel` from the package root. | ||
| * | ||
| * @internal | ||
| */ | ||
| export declare const selBase: { | ||
| /** Match any {@link HTMLElement}. */ | ||
| readonly any: () => ElementSelectorBuilder<HTMLElement>; | ||
| /** Match DOM {@link Comment} nodes. */ | ||
| readonly comment: () => CompiledSelector<Comment>; | ||
| /** | ||
| * Match by tag name(s). With one literal tag the element type is narrowed | ||
| * (e.g. `'a' → HTMLAnchorElement`); with multiple, it is the union of | ||
| * their `HTMLElementTagNameMap` entries. | ||
| */ | ||
| readonly tag: <const Tags extends readonly string[]>(...tags: Tags) => ElementSelectorBuilder<Tags[number] extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Tags[number]] : HTMLElement>; | ||
| /** Match DOM {@link Text} nodes. */ | ||
| readonly text: () => CompiledSelector<Text>; | ||
| }; | ||
| /** | ||
| * Cross-frame-safe replacement for `node instanceof HTMLXxxElement`. Returns | ||
| * true when `node` is an HTMLElement whose `nodeName` equals `tag` (compared | ||
| * case-insensitively). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function isElementOfTag<T extends keyof HTMLElementTagNameMap>(node: Node, tag: T): node is HTMLElementTagNameMap[T]; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMImportContextSymbol } from '../constants'; | ||
| import type { AnyContextConfigPairOrUpdater, ContextConfig, ContextRecord } from '../types'; | ||
| import type { CompiledOverlayRules } from './defineOverlayRules'; | ||
| import type { LexicalNode } from 'lexical'; | ||
| /** | ||
| * Phantom-typed branding so consumers cannot construct or mutate a | ||
| * {@link CompiledSelector} directly; the only way to obtain one is via the | ||
| * {@link sel} builder or {@link parseSelector}. The actual runtime shape is | ||
| * an internal implementation detail (see `./sel`). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const NodeBrand: unique symbol; | ||
| /** @experimental */ | ||
| export declare const CaptureBrand: unique symbol; | ||
| /** | ||
| * An opaque, compiled selector used as the `match` field of a | ||
| * {@link DOMImportRule}. The two phantom type parameters carry the matched | ||
| * Node subtype (`N`) and a record of named regex captures (`C`) so the | ||
| * importer body gets correctly-typed `ctx` and `node` arguments without | ||
| * casts. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface CompiledSelector<N extends Node = Node, C extends Record<string, RegExpMatchArray> = Record<string, RegExpMatchArray>> { | ||
| readonly [NodeBrand]?: N; | ||
| readonly [CaptureBrand]?: C; | ||
| } | ||
| /** | ||
| * The Node subtype matched by a selector (e.g. `HTMLAnchorElement` for | ||
| * `sel.tag('a')`, `Text` for `sel.text()`). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type NodeOfSelector<S> = S extends CompiledSelector<infer N, Record<string, RegExpMatchArray>> ? N : Node; | ||
| /** | ||
| * The named-capture map for a selector. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type CapturesOfSelector<S> = S extends CompiledSelector<Node, infer C> ? C : Record<string, never>; | ||
| /** | ||
| * Options bag for {@link ElementSelectorBuilder.attr} when the value is a | ||
| * regex. Future options will be added here without breaking existing | ||
| * call-sites. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface AttrMatchOptions<K extends string = string> { | ||
| /** | ||
| * If provided, the {@link RegExpMatchArray} from the successful match is | ||
| * stored on `ctx.captures[capture]` for the importer to consume — saving | ||
| * a second regex execution. | ||
| */ | ||
| readonly capture?: K; | ||
| } | ||
| /** | ||
| * Options bag for {@link ElementSelectorBuilder.styleAny} when the value is a | ||
| * regex. See {@link AttrMatchOptions} for capture semantics. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface StyleMatchOptions<K extends string = string> { | ||
| readonly capture?: K; | ||
| } | ||
| /** | ||
| * Fluent builder for an element selector. The two type parameters carry the | ||
| * matched element type and the named-capture map; each call refines them. | ||
| * | ||
| * The builder itself implements {@link CompiledSelector} so it can be used | ||
| * directly as the `match` field of a rule — no `.build()` call needed. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ElementSelectorBuilder<E extends HTMLElement, C extends Record<string, RegExpMatchArray> = Record<string, never>> extends CompiledSelector<E, C> { | ||
| /** Require every listed class to be present on the element. */ | ||
| classAll(...classes: readonly string[]): ElementSelectorBuilder<E, C>; | ||
| /** Require at least one of the listed classes to be present. */ | ||
| classAny(...classes: readonly string[]): ElementSelectorBuilder<E, C>; | ||
| /** Require the attribute to be present (any value). */ | ||
| attr(name: string, value: true): ElementSelectorBuilder<E, C>; | ||
| /** Require the attribute to equal the given string. */ | ||
| attr(name: string, value: string): ElementSelectorBuilder<E, C>; | ||
| /** | ||
| * Require the attribute to match the given regex. With | ||
| * `{capture: 'name'}` the match result is exposed on | ||
| * `ctx.captures.name`. | ||
| */ | ||
| attr<const O extends AttrMatchOptions>(name: string, value: RegExp, options?: O): ElementSelectorBuilder<E, O extends { | ||
| capture: infer K; | ||
| } ? C & Record<K & string, RegExpMatchArray> : C>; | ||
| /** Require the inline-style declaration to equal `value`. */ | ||
| styleAny(prop: string, value: string): ElementSelectorBuilder<E, C>; | ||
| /** Require the inline-style declaration to match `value`. */ | ||
| styleAny<const O extends StyleMatchOptions>(prop: string, value: RegExp, options?: O): ElementSelectorBuilder<E, O extends { | ||
| capture: infer K; | ||
| } ? C & Record<K & string, RegExpMatchArray> : C>; | ||
| } | ||
| /** | ||
| * Argument to {@link DOMImportContext.branch} / `$importChildren({context})` | ||
| * — see {@link ContextConfigPair} / {@link ContextConfigUpdater}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type ImportContextPairOrUpdater = AnyContextConfigPairOrUpdater<typeof DOMImportContextSymbol>; | ||
| /** | ||
| * A typed context-state key for the import pipeline. Create with | ||
| * {@link createImportState}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type ImportStateConfig<V> = ContextConfig<typeof DOMImportContextSymbol, V>; | ||
| /** | ||
| * A mutable, document-order-shared store for the import pipeline. Lets a | ||
| * rule visited early in the document write information that rules visited | ||
| * later can read — e.g. parse `<style>` or `<meta>` and influence | ||
| * subsequent matching. | ||
| * | ||
| * Implemented as the root-layer {@link ContextRecord} of the import walk: | ||
| * `ctx.session.set(cfg, v)` mutates the slot on that root record, and | ||
| * every unshadowed `ctx.get(cfg)` read in any branch picks it up. A | ||
| * `$importChildren({context: [...]})` branch that explicitly writes the | ||
| * same slot shadows the session value for the duration of that branch. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ImportSession { | ||
| /** Read the current value, returning the config's default if unset. */ | ||
| get<V>(cfg: ImportStateConfig<V>): V; | ||
| /** Write `value` into the slot. */ | ||
| set<V>(cfg: ImportStateConfig<V>, value: V): void; | ||
| /** Read-modify-write. */ | ||
| update<V>(cfg: ImportStateConfig<V>, updater: (prev: V) => V): void; | ||
| /** Returns `true` if the slot has been written since session creation. */ | ||
| has<V>(cfg: ImportStateConfig<V>): boolean; | ||
| } | ||
| /** | ||
| * Context exposed to a rule's `$import` function. Mirrors the existing render | ||
| * context (see {@link RenderContext}) but is import-scoped. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportContext<C extends Record<string, RegExpMatchArray> = Record<string, never>> { | ||
| /** Captures from this rule's selector. Fresh per rule invocation. */ | ||
| readonly captures: Readonly<C>; | ||
| /** | ||
| * Mutable, document-order-shared store. Use to make information from | ||
| * earlier-visited nodes available to later-visited ones (e.g. a | ||
| * `<style>` or `<meta>` at the top of the document influencing how | ||
| * later elements are interpreted). One {@link ImportSession} instance | ||
| * is created per top-level `$generateNodesFromDOM` call and is shared | ||
| * across all recursive `$importChildren` / `$importOne` invocations. | ||
| */ | ||
| readonly session: ImportSession; | ||
| /** Read a typed context value. */ | ||
| get<V>(cfg: ImportStateConfig<V>): V; | ||
| /** | ||
| * Recursively import every child of `parent` and return the produced | ||
| * lexical nodes, optionally enforcing a {@link ChildSchema} and/or | ||
| * branching the import context for the duration of the call (via | ||
| * `opts.context`). | ||
| */ | ||
| $importChildren(parent: ParentNode, opts?: ImportChildrenOpts): LexicalNode[]; | ||
| /** | ||
| * Recursively import a single DOM node. | ||
| */ | ||
| $importOne(node: Node, opts?: ImportNodeOpts): LexicalNode[]; | ||
| } | ||
| /** | ||
| * Options accepted by {@link DOMImportContext.$importChildren}. The combination | ||
| * of `schema`, `$onChild`, and `$after` is sufficient to express every | ||
| * child-handling pattern in the legacy `forChild` / `after` / wrap-continuous | ||
| * machinery. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ImportChildrenOpts { | ||
| /** | ||
| * How to validate and (re)package produced children. Defaults to whichever | ||
| * schema the parent's importer passed; the top-level entry uses | ||
| * {@link BlockSchema}. | ||
| */ | ||
| readonly schema?: ChildSchema; | ||
| /** | ||
| * Called for each produced lexical child immediately after its rule | ||
| * returned, with the chance to substitute or drop it. Equivalent to the | ||
| * old `forChild` hook but scoped to one `$importChildren` call. | ||
| */ | ||
| readonly $onChild?: (child: LexicalNode) => LexicalNode | null | undefined; | ||
| /** | ||
| * Called once with the full child array after all DOM children have been | ||
| * recursively imported but before {@link ChildSchema.$packageRun} is | ||
| * applied. Equivalent to the old `after` hook. | ||
| */ | ||
| readonly $after?: (children: LexicalNode[]) => LexicalNode[]; | ||
| /** Context overrides scoped to the children traversal. */ | ||
| readonly context?: readonly ImportContextPairOrUpdater[]; | ||
| /** | ||
| * Additional {@link DOMImportRule}s active only for this children | ||
| * traversal (and any nested `$importChildren` calls that don't push | ||
| * their own overlay). The overlay is checked BEFORE the main | ||
| * dispatcher, so its rules take precedence; calling `$next()` from an | ||
| * overlay rule falls through to the next overlay-or-main rule. | ||
| * | ||
| * Use this to scope cost-bearing rules to where they apply. For | ||
| * example, a GitHub code-table rule installs an overlay that | ||
| * unwraps `<tr>` / `<td>` inside the table, without paying that | ||
| * predicate cost on every other `<tr>` / `<td>` paste. | ||
| * | ||
| * The value must be produced by | ||
| * {@link defineOverlayRules}; this forces the dispatcher to be | ||
| * compiled once at module scope and reused across | ||
| * `$importChildren` calls, instead of being recompiled per invocation. | ||
| */ | ||
| readonly rules?: CompiledOverlayRules; | ||
| } | ||
| /** @experimental */ | ||
| export interface ImportNodeOpts { | ||
| readonly context?: readonly ImportContextPairOrUpdater[]; | ||
| } | ||
| /** | ||
| * A {@link ChildSchema} encodes which lexical nodes a parent accepts as | ||
| * children and how to package or reject the rest. The legacy | ||
| * `wrapContinuousInlines` / `ArtificialNode__DO_NOT_USE` logic is the | ||
| * `BlockSchema` and `NestedBlockSchema` cases of this primitive. | ||
| * | ||
| * A schema only controls how the *children* are assembled before being | ||
| * appended to the parent the calling rule already chose. It cannot | ||
| * change the parent itself. Cases where the parent's shape needs to | ||
| * change in response to its children — e.g. an inline `<a>` that | ||
| * encloses a block `<h1>`, which must be lifted so the heading takes | ||
| * the link's place and the link is redistributed onto the heading's | ||
| * inline contents — belong in the rule body, not the schema. See the | ||
| * "Lifting blocks out of an inline parent" section of the docs. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ChildSchema { | ||
| /** Optional name for debug output. */ | ||
| readonly name?: string; | ||
| /** | ||
| * Returns `true` if `child` is a valid child of `parent` in this position. | ||
| */ | ||
| $accepts(child: LexicalNode, parent: LexicalNode | null): boolean; | ||
| /** | ||
| * Package a maximal run of non-accepted siblings into zero or more | ||
| * accepted nodes. The returned nodes replace the rejected run at the | ||
| * same position in the child list and are not re-checked against | ||
| * `$accepts` — the caller is trusted to return valid children. If | ||
| * omitted, or if it returns an empty array, {@link ChildSchema.onReject} | ||
| * is consulted instead. | ||
| */ | ||
| $packageRun?(rejected: LexicalNode[], parent: LexicalNode | null, domParent: Node | null): LexicalNode[]; | ||
| /** | ||
| * Fallback strategy for a run of non-accepted children when | ||
| * `$packageRun` is missing or returns an empty array: | ||
| * | ||
| * - `'drop'` (default) — silently discards the run. Use when the | ||
| * schema is strict and rejected content is meaningless in this | ||
| * position (e.g. text between table rows). | ||
| * - `'hoist'` — emits the rejected nodes unchanged at the same | ||
| * position in the assembled child list. The caller's parent then | ||
| * receives them as-is, which is only useful if the calling rule | ||
| * intends to surface mixed content to *its* parent (a less common | ||
| * pattern; usually `$packageRun` should re-shape the run first). | ||
| * | ||
| * `'hoist'` does NOT lift the run all the way up out of the calling | ||
| * rule's parent — that requires the rule itself to detect the | ||
| * situation and emit a different parent structure (see the | ||
| * "Lifting blocks out of an inline parent" section). | ||
| */ | ||
| readonly onReject?: 'hoist' | 'drop'; | ||
| /** | ||
| * Final pass over the assembled child list (after `$packageRun`). Returns | ||
| * the children to actually attach. Use to enforce structural invariants | ||
| * (e.g. drop empty runs, pad short table rows). | ||
| */ | ||
| $finalize?(children: LexicalNode[], parent: LexicalNode | null): LexicalNode[]; | ||
| } | ||
| /** | ||
| * The middleware signature of an import rule. Call `$next()` to delegate to | ||
| * the next-matching rule for this node (returning its result, which may then | ||
| * be inspected or wrapped); return `[]` to drop the node. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type DOMImportFn<E extends Node, C extends Record<string, RegExpMatchArray> = Record<string, never>> = (ctx: DOMImportContext<C>, node: E, $next: () => readonly LexicalNode[]) => readonly LexicalNode[]; | ||
| /** | ||
| * An importer for a DOM node, dispatched by `match` and implemented by | ||
| * `$import`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportRule<S extends CompiledSelector = CompiledSelector> { | ||
| /** | ||
| * Optional identifier surfaced in dev-mode logs, error messages, and | ||
| * introspection devtools. Convention: `'@scope/package/rule-id'` for | ||
| * library rules. | ||
| */ | ||
| readonly name?: string; | ||
| /** A {@link CompiledSelector} produced by the {@link sel} builder. */ | ||
| readonly match: S; | ||
| /** Middleware that converts the matched DOM node into lexical nodes. */ | ||
| readonly $import: DOMImportFn<NodeOfSelector<S>, CapturesOfSelector<S>>; | ||
| } | ||
| /** @experimental */ | ||
| export type AnyDOMImportRule = DOMImportRule<any>; | ||
| /** | ||
| * Context exposed to a {@link DOMPreprocessFn}. Lets the preprocessor: | ||
| * | ||
| * - Write to the per-import {@link ImportSession} (the same | ||
| * `ctx.session` rules see during the walk). Writes mutate the | ||
| * root-layer context record, so they are visible to every scoped | ||
| * `ctx.get(cfg)` read that hasn't been shadowed by a branch. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMPreprocessContext { | ||
| /** | ||
| * Document-order-shared store, same instance as `ctx.session` in | ||
| * rules. Use to pass info from the DOM preprocess phase (e.g. | ||
| * inspected `<meta>` tags, collected `<style>` text) to the | ||
| * importer rules. | ||
| */ | ||
| readonly session: ImportSession; | ||
| } | ||
| /** | ||
| * A middleware step in the DOM-preprocess chain. Runs before walking | ||
| * begins and may: | ||
| * | ||
| * - Mutate the input DOM in place (e.g. inline stylesheets, strip | ||
| * unsafe elements, normalize attributes). | ||
| * - Write to {@link DOMPreprocessContext.session} for rules to read | ||
| * (and for unshadowed scoped reads to pick up). | ||
| * - Call `$next()` to defer to the next-lower preprocessor in the | ||
| * stack; omit the call to short-circuit and skip the rest. | ||
| * | ||
| * The preprocess phase runs inside the same editor read / update | ||
| * context as the walk that follows, so a preprocess function may call | ||
| * `$`-prefixed Lexical APIs (e.g. `$getState`, `$getRoot`) as needed. | ||
| * The `$next` parameter is named with a `$` prefix to make that | ||
| * editor-context expectation visible to readers and lints. | ||
| * | ||
| * Append-style merge applies: an extension's preprocessors are appended | ||
| * to the existing stack, so later-registered preprocessors run first | ||
| * and may delegate to earlier (lower-priority) ones via `$next()`. Same | ||
| * convention as {@link ExportMimeTypeFunction} on the export side. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type DOMPreprocessFn = (dom: Document | ParentNode, ctx: DOMPreprocessContext, $next: () => void) => void; | ||
| /** | ||
| * Per-call options to the extension's `$generateNodesFromDOM`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface GenerateNodesFromDOMOptions { | ||
| /** | ||
| * Context pairs/updaters applied for the duration of this import only — | ||
| * use to communicate per-call info such as the {@link ImportSource}. | ||
| */ | ||
| readonly context?: readonly ImportContextPairOrUpdater[]; | ||
| /** | ||
| * Additional preprocessors to run on this call only, on top of the | ||
| * extension's configured {@link DOMImportConfig.preprocess}. Per-call | ||
| * preprocessors run AFTER the configured ones. | ||
| */ | ||
| readonly preprocess?: readonly DOMPreprocessFn[]; | ||
| } | ||
| /** | ||
| * Output of {@link DOMImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportExtensionOutput { | ||
| /** | ||
| * Convert a {@link Document} or {@link ParentNode} into lexical nodes, | ||
| * using the dispatcher compiled from this extension's configured | ||
| * {@link DOMImportRule}s. | ||
| * | ||
| * Must be called within an `editor.update()` or `editor.read()` because | ||
| * the importers may invoke `$create...` helpers. | ||
| */ | ||
| $generateNodesFromDOM(dom: Document | ParentNode, options?: GenerateNodesFromDOMOptions): LexicalNode[]; | ||
| /** @internal */ | ||
| readonly defaults: undefined | ContextRecord<typeof DOMImportContextSymbol>; | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { BaseSelection, LexicalEditor, LexicalNode } from 'lexical'; | ||
| export { contextUpdater, contextValue } from './ContextRecord'; | ||
| export { domOverride } from './domOverride'; | ||
| export { DOMRenderExtension } from './DOMRenderExtension'; | ||
| export type { AnyDOMImportRule, AttrMatchOptions, CapturesOfSelector, ChildSchema, CompiledOverlayRules, CompiledSelector, DOMImportContext, DOMImportExtensionOutput, DOMImportFn, DOMImportRule, DOMImportRuleEntry, DOMPreprocessContext, DOMPreprocessFn, ElementSelectorBuilder, GenerateNodesFromDOMOptions, ImportChildrenOpts, ImportContextPairOrUpdater, ImportNodeOpts, ImportSession, ImportStateConfig, NodeOfSelector, StyleMatchOptions, } from './import'; | ||
| export { $distributeInlineWrapper, $generateNodesFromDOMViaExtension, $getImportContextValue, $inlineStylesFromStyleSheets, $isBlockLevel, $withImportContext, BlockSchema, CoreImportExtension, CoreImportRules, createImportState, defaultIsInline, defaultPreservesWhitespace, defineImportRule, defineOverlayRules, type DOMImportConfig, DOMImportExtension, HorizontalRuleImportExtension, HorizontalRuleImportRules, ImportOverlays, ImportSource, ImportSourceDataTransfer, type ImportSourceKind, ImportTextFormat, ImportTextStyle, ImportWhitespaceConfig, InlineSchema, isElementOfTag, type IsInlineForWhitespace, type IsPreserveWhitespaceDom, NestedBlockSchema, parseSelector, RootSchema, sel, type WhitespaceImportConfig, } from './import'; | ||
| export { $getRenderContextValue, $getSessionDOMRenderConfig, $setRenderContextValue, $updateRenderContextValue, $withRenderContext, createRenderState, RenderContextExport, RenderContextRoot, } from './RenderContext'; | ||
| export type { AnyDOMRenderMatch, AnyRenderStateConfig, AnyRenderStateConfigPairOrUpdater, ContextPairOrUpdater, DOMOverrideOptions, DOMRenderConfig, DOMRenderExtensionOutput, DOMRenderMatch, DOMRenderMatchConfig, NodeMatch, RenderContextReader, } from './types'; | ||
| /** | ||
| * How you parse your html string to get a document is left up to you. In the browser you can use the native | ||
| * DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom | ||
| * or an equivalent library and pass in the document here. | ||
| */ | ||
| export declare function $generateNodesFromDOM(editor: LexicalEditor, dom: Document | ParentNode): Array<LexicalNode>; | ||
| /** | ||
| * Generate DOM nodes from the editor state into the given container element, | ||
| * using the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| export declare function $generateDOMFromNodes<T extends HTMLElement | DocumentFragment>(container: T, selection?: null | BaseSelection, editor?: LexicalEditor): T; | ||
| /** | ||
| * Generate DOM nodes from a root node into the given container element, | ||
| * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| export declare function $generateDOMFromRoot<T extends HTMLElement | DocumentFragment>(container: T, root?: LexicalNode): T; | ||
| /** | ||
| * Generate an HTML string from the editor's current state (or `selection` | ||
| * if provided). | ||
| * | ||
| * Must be called inside an active editor scope — i.e. `editor.update(...)`, | ||
| * `editor.read(...)`, or `editor.getEditorState().read(callback, {editor})`. | ||
| * The legacy `editor.getEditorState().read(callback)` call (without the | ||
| * `{editor}` option) does not set an active editor and is not supported; | ||
| * `editor.read(...)` is the drop-in replacement. | ||
| */ | ||
| export declare function $generateHtmlFromNodes(editor: LexicalEditor, selection?: BaseSelection | null): string; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| 'use strict' | ||
| const LexicalHtml = process.env.NODE_ENV !== 'production' ? require('./LexicalHtml.dev.js') : require('./LexicalHtml.prod.js'); | ||
| module.exports = LexicalHtml; |
Sorry, the diff of this file is not supported yet
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import * as modDev from './LexicalHtml.dev.mjs'; | ||
| import * as modProd from './LexicalHtml.prod.mjs'; | ||
| const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd; | ||
| export const $distributeInlineWrapper = mod.$distributeInlineWrapper; | ||
| export const $generateDOMFromNodes = mod.$generateDOMFromNodes; | ||
| export const $generateDOMFromRoot = mod.$generateDOMFromRoot; | ||
| export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes; | ||
| export const $generateNodesFromDOM = mod.$generateNodesFromDOM; | ||
| export const $generateNodesFromDOMViaExtension = mod.$generateNodesFromDOMViaExtension; | ||
| export const $getImportContextValue = mod.$getImportContextValue; | ||
| export const $getRenderContextValue = mod.$getRenderContextValue; | ||
| export const $getSessionDOMRenderConfig = mod.$getSessionDOMRenderConfig; | ||
| export const $inlineStylesFromStyleSheets = mod.$inlineStylesFromStyleSheets; | ||
| export const $isBlockLevel = mod.$isBlockLevel; | ||
| export const $setRenderContextValue = mod.$setRenderContextValue; | ||
| export const $updateRenderContextValue = mod.$updateRenderContextValue; | ||
| export const $withImportContext = mod.$withImportContext; | ||
| export const $withRenderContext = mod.$withRenderContext; | ||
| export const BlockSchema = mod.BlockSchema; | ||
| export const CoreImportExtension = mod.CoreImportExtension; | ||
| export const CoreImportRules = mod.CoreImportRules; | ||
| export const DOMImportExtension = mod.DOMImportExtension; | ||
| export const DOMRenderExtension = mod.DOMRenderExtension; | ||
| export const HorizontalRuleImportExtension = mod.HorizontalRuleImportExtension; | ||
| export const HorizontalRuleImportRules = mod.HorizontalRuleImportRules; | ||
| export const ImportOverlays = mod.ImportOverlays; | ||
| export const ImportSource = mod.ImportSource; | ||
| export const ImportSourceDataTransfer = mod.ImportSourceDataTransfer; | ||
| export const ImportTextFormat = mod.ImportTextFormat; | ||
| export const ImportTextStyle = mod.ImportTextStyle; | ||
| export const ImportWhitespaceConfig = mod.ImportWhitespaceConfig; | ||
| export const InlineSchema = mod.InlineSchema; | ||
| export const NestedBlockSchema = mod.NestedBlockSchema; | ||
| export const RenderContextExport = mod.RenderContextExport; | ||
| export const RenderContextRoot = mod.RenderContextRoot; | ||
| export const RootSchema = mod.RootSchema; | ||
| export const contextUpdater = mod.contextUpdater; | ||
| export const contextValue = mod.contextValue; | ||
| export const createImportState = mod.createImportState; | ||
| export const createRenderState = mod.createRenderState; | ||
| export const defaultIsInline = mod.defaultIsInline; | ||
| export const defaultPreservesWhitespace = mod.defaultPreservesWhitespace; | ||
| export const defineImportRule = mod.defineImportRule; | ||
| export const defineOverlayRules = mod.defineOverlayRules; | ||
| export const domOverride = mod.domOverride; | ||
| export const isElementOfTag = mod.isElementOfTag; | ||
| export const parseSelector = mod.parseSelector; | ||
| export const sel = mod.sel; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalHtml.dev.mjs') : import('./LexicalHtml.prod.mjs')); | ||
| export const $distributeInlineWrapper = mod.$distributeInlineWrapper; | ||
| export const $generateDOMFromNodes = mod.$generateDOMFromNodes; | ||
| export const $generateDOMFromRoot = mod.$generateDOMFromRoot; | ||
| export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes; | ||
| export const $generateNodesFromDOM = mod.$generateNodesFromDOM; | ||
| export const $generateNodesFromDOMViaExtension = mod.$generateNodesFromDOMViaExtension; | ||
| export const $getImportContextValue = mod.$getImportContextValue; | ||
| export const $getRenderContextValue = mod.$getRenderContextValue; | ||
| export const $getSessionDOMRenderConfig = mod.$getSessionDOMRenderConfig; | ||
| export const $inlineStylesFromStyleSheets = mod.$inlineStylesFromStyleSheets; | ||
| export const $isBlockLevel = mod.$isBlockLevel; | ||
| export const $setRenderContextValue = mod.$setRenderContextValue; | ||
| export const $updateRenderContextValue = mod.$updateRenderContextValue; | ||
| export const $withImportContext = mod.$withImportContext; | ||
| export const $withRenderContext = mod.$withRenderContext; | ||
| export const BlockSchema = mod.BlockSchema; | ||
| export const CoreImportExtension = mod.CoreImportExtension; | ||
| export const CoreImportRules = mod.CoreImportRules; | ||
| export const DOMImportExtension = mod.DOMImportExtension; | ||
| export const DOMRenderExtension = mod.DOMRenderExtension; | ||
| export const HorizontalRuleImportExtension = mod.HorizontalRuleImportExtension; | ||
| export const HorizontalRuleImportRules = mod.HorizontalRuleImportRules; | ||
| export const ImportOverlays = mod.ImportOverlays; | ||
| export const ImportSource = mod.ImportSource; | ||
| export const ImportSourceDataTransfer = mod.ImportSourceDataTransfer; | ||
| export const ImportTextFormat = mod.ImportTextFormat; | ||
| export const ImportTextStyle = mod.ImportTextStyle; | ||
| export const ImportWhitespaceConfig = mod.ImportWhitespaceConfig; | ||
| export const InlineSchema = mod.InlineSchema; | ||
| export const NestedBlockSchema = mod.NestedBlockSchema; | ||
| export const RenderContextExport = mod.RenderContextExport; | ||
| export const RenderContextRoot = mod.RenderContextRoot; | ||
| export const RootSchema = mod.RootSchema; | ||
| export const contextUpdater = mod.contextUpdater; | ||
| export const contextValue = mod.contextValue; | ||
| export const createImportState = mod.createImportState; | ||
| export const createRenderState = mod.createRenderState; | ||
| export const defaultIsInline = mod.defaultIsInline; | ||
| export const defaultPreservesWhitespace = mod.defaultPreservesWhitespace; | ||
| export const defineImportRule = mod.defineImportRule; | ||
| export const defineOverlayRules = mod.defineOverlayRules; | ||
| export const domOverride = mod.domOverride; | ||
| export const isElementOfTag = mod.isElementOfTag; | ||
| export const parseSelector = mod.parseSelector; | ||
| export const sel = mod.sel; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| "use strict";var e=require("@lexical/selection"),t=require("lexical"),n=require("@lexical/utils"),o=require("@lexical/extension");function r(e,...t){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",e);for(const e of t)o.append("v",e);throw n.search=o.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}let s;function i(e,t){const{key:n}=t;return e&&n in e?e[n]:t.defaultValue}function c(e){return s&&s.editor===e?s:void 0}function l(e,t){const n=c(t);return n&&n[e]}function u(e,t){if("cfg"in t){const{cfg:n,updater:o}=t;return[n,o(i(e,n))]}return t}function a(e,t){let n=t;for(const o of e){const[e,r]=u(n,o),s=e.key;if(n===t&&i(n,e)===r)continue;const c=n===t||void 0===n?f(t):n;c[s]=r,n=c}return n}function f(e){return Object.create(e||null)}function d(e,t){return[e,t]}function p(e,n,o,r=t.$getEditor()){const i=s,l=c(r);try{return s={...l,editor:r,[e]:n},o()}finally{s=i}}function h(e,n=()=>{}){return(o,r=t.$getEditor())=>t=>{const s=c(r),i=s&&s[e],l=a(o,i||n(r));return l&&l!==i?p(e,l,t,r):t()}}function m(e,n,o,r){return Object.assign(t.createState(Symbol(n),{isEqual:r,parse:o}),{[e]:!0})}const g=(e,t,n)=>{x(e),n()};function x(e){if(!t.isDOMDocumentNode(e))return;const o=e;if(null===o.querySelector("style"))return;const r=new Map;function s(e){let t=r.get(e);if(void 0===t){t=new Set;for(let n=0;n<e.style.length;n++)t.add(e.style[n]);r.set(e,t)}return t}try{for(const e of Array.from(o.styleSheets)){let r;try{r=e.cssRules}catch(e){continue}for(const e of Array.from(r)){if(!n.objectKlassEquals(e,CSSStyleRule))continue;let r;try{r=o.querySelectorAll(e.selectorText)}catch(e){continue}for(const n of Array.from(r)){if(!t.isHTMLElement(n))continue;const o=s(n);for(let t=0;t<e.style.length;t++){const r=e.style[t];o.has(r)||n.style.setProperty(r,e.style.getPropertyValue(r),e.style.getPropertyPriority(r))}}}}}catch(e){}}const y="@lexical/html/DOM",$=Symbol.for("@lexical/html/DOMExportContext"),S="@lexical/html/DOMImport",E=Symbol.for("@lexical/html/DOMImportContext"),D=()=>!0;function C(e,t,n){return m($,e,t,n)}const I=C("root",Boolean),N=C("isExport",Boolean);function O(e){const t=o.getPeerDependencyFromEditor(e,y);return t?t.output.defaults:void 0}function v(e){const t=o.getPeerDependencyFromEditor(e,y);return t?t.output.runtime:void 0}function M(e=t.$getEditor()){const n=v(e);return n?n.getSessionConfig():t.$getEditorDOMRenderConfig(e)}const R=h($,O);function T(e){return t=>t instanceof e}function b(e,{nodes:t}){if("*"===t)return D;let n={};const o=[];for(const s of t)if("getType"in s){const t=s.getType();if(n){const o=e[t];void 0===o&&r(339,s.name,t),n=Object.assign(n,o.types)}o.push(T(s))}else n=void 0,o.push(s);return n||(1===o.length?o[0]:e=>{for(const t of o)if(t(e))return!0;return!1})}function k(e){return(t,n,o)=>e(t,o)}function w(e){return(t,n,o,r)=>e(t,n,r)}function L(e){return(t,n,o,r,s)=>e(t,n,o,s)}function A(e){return(t,n,o,r,s,i)=>e(t,n,o,r,i)}function _(e,t){return(n,o)=>{const r=()=>e(n,o),s=t(n);return s?s(n,r,o):r()}}function F(e,t){return(n,o,r)=>{const s=()=>e(n,o,r),i=t(n);return i?i(n,o,s,r):s()}}const P=F,H=w;function W(e,t){return(n,o,r,s)=>{const i=()=>e(n,o,r,s),c=t(n);return c?c(n,o,r,i,s):i()}}function B(e,t){return(n,o,r,s,i)=>{const c=()=>e(n,o,r,s,i),l=t(n);return l?l(n,o,r,s,c,i):c()}}function U(e,t){return(n,o,r,s)=>{e(n,o,r,s);const i=t(n);i&&i(n,o,r,s)}}function j(e,t,n,o,r){let s=n[t];for(const n of e[t])if("function"==typeof n[0]){const[e,t]=n;s=o(s,n=>e(n)&&t||void 0)}else{const e=n[1],t={};for(const n in e){const r=e[n];r&&(t[n]=r.reduce((e,t)=>o(e,()=>t),s))}s=o(s,e=>{const n=t[e.getType()];return n&&r(n)})}n[t]=s}function z(e,t,n,o){if(!o)return;const r=e[t];if("function"==typeof n)r.push([n,o]);else{const e=r[r.length-1];let t;e&&"types"===e[0]?t=e[1]:(t={},r.push(["types",t]));for(const e in n){const n=t[e]||[];t[e]=n,n.push(o)}}}function V(e){return"*"===e.nodes}function G(e,n){const r=function(e){const n={},{nodes:r}=o.getKnownTypesAndNodes(e);for(const e of r)n[e.getType()]={klass:e,types:{}};for(const e of Object.values(n))if(e){const o=e.klass.getType();for(let{klass:r}=e;t.$isLexicalNode(r.prototype);r=Object.getPrototypeOf(r)){const{ownNodeType:e}=t.getStaticNodeConfig(r),s=e&&n[e];s&&(s.types[o]=!0)}}return n}(e),s={$createDOM:[],$decorateDOM:[],$exportDOM:[],$extractWithChild:[],$getDOMSlot:[],$shouldExclude:[],$shouldInclude:[],$updateDOM:[]};for(const e of function(e){const n=[],o=[],r=[];for(const s of e)if(V(s))n.push(s);else if(Array.isArray(s.nodes))for(const e of s.nodes)t.$isLexicalNode(e.prototype)?r.push(1===s.nodes.length?s:{...s,nodes:[e]}):o.push(1===s.nodes.length?s:{...s,nodes:[e]});const s=new Map,i=e=>{let n=s.get(e);if(void 0===n){n=0;for(let o=e;t.$isLexicalNode(o.prototype);o=Object.getPrototypeOf(o))n++;s.set(e,n)}return n};return r.sort((e,t)=>i(e.nodes[0])-i(t.nodes[0])),[...r,...o,...n]}(n)){const t=b(r,e);for(const n in s){z(s,n,t,e[n])}}return s}function q(e){return e}function K(e,{overrides:n}){const o=G(e,n),r={...t.DEFAULT_EDITOR_DOM_CONFIG,...e.dom};return j(o,"$createDOM",r,_,k),j(o,"$exportDOM",r,_,k),j(o,"$extractWithChild",r,B,A),j(o,"$getDOMSlot",r,P,H),j(o,"$shouldExclude",r,F,w),j(o,"$shouldInclude",r,F,w),j(o,"$updateDOM",r,W,L),j(o,"$decorateDOM",r,U,q),r}function J(e){return{get:t=>i(e,t)}}function Q(e){const t=Object.create(null);return a(e,t)||t}function Y(e,t){const n=J(t);return e.filter(e=>!(e.disabledForEditor&&e.disabledForEditor(n)))}function Z(e){if("*"===e.nodes)return()=>!0;const n=e.nodes.map(e=>{const n=e;return t.$isLexicalNode(n.prototype)?e=>e instanceof n:e});return e=>n.some(t=>t(e))}class X{editor;initialEditorConfig;overrides;editorContext;hasSessionGates;installed;sessionCache=new Map;constructor(e,t,n,o){this.editor=e,this.initialEditorConfig=t,this.overrides=n,this.editorContext=o,this.installed=Y(n,o),this.hasSessionGates=n.some(e=>e.disabledForSession)}setContextValue(e,n){const o=this.installed;this.editorContext[e.key]=n;const r=Y(this.overrides,this.editorContext);if(function(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0}(o,r))return;const s=function(e,t){const n=new Set(e),o=new Set(t),r=[];for(const t of e)o.has(t)||r.push(t);for(const e of t)n.has(e)||r.push(e);return r}(o,r);this.installed=r,this.sessionCache.clear();const i=K(this.initialEditorConfig,{overrides:r});this.editor._config.dom=i;const c=function(e){const t=[];for(const n of e)(n.$createDOM||n.$getDOMSlot||n.$decorateDOM)&&t.push(Z(n));return 0===t.length?null:e=>t.some(t=>t(e))}(s);if(!c)return;const l=i.$updateDOM;i.$updateDOM=(e,t,n,o)=>!!c(e)||l(e,t,n,o),this.editor.update(t.$fullReconcile,{discrete:!0}),i.$updateDOM=l}getSessionConfig(){const e=this.editor._config.dom||t.DEFAULT_EDITOR_DOM_CONFIG;if(!this.hasSessionGates)return e;const n=J(l($,this.editor)||this.editorContext),o=[],r=[];if(this.installed.forEach((e,t)=>{e.disabledForSession&&e.disabledForSession(n)?o.push(String(t)):r.push(e)}),0===o.length)return e;const s=o.join(",");let i=this.sessionCache.get(s);return i||(i=K(this.initialEditorConfig,{overrides:r}),this.sessionCache.set(s,i)),i}}const ee=t.defineExtension({build(e,t,n){const{initialEditorConfig:o}=n.getInitResult(),r=Q(t.contextDefaults);return{defaults:r,runtime:new X(e,o,t.overrides,r)}},config:{contextDefaults:[],overrides:[]},html:{export:new Map([[t.RootNode,()=>{const e=document.createElement("div");return e.role="textbox",{element:e}}]])},init(e,t){const n={dom:e.dom,nodes:e.nodes},o=Q(t.contextDefaults),r=Y(t.overrides,o);return e.dom=K(e,{overrides:r}),{initialEditorConfig:n}},mergeConfig(e,n){const o=t.shallowMergeConfig(e,n);for(const t of["overrides","contextDefaults"])n[t]&&(o[t]=[...e[t],...n[t]]);return o},name:y}),te=Symbol.for("@lexical/html/SelectorImpl");function ne(e,n){const o={kind:"element",predicate:(s=n,0===s.length?t.isHTMLElement:1===s.length?s[0]:(e,t)=>{for(const n of s)if(!n(e,t))return!1;return!0}),tags:e};var s;const i=t=>ne(e,[...n,t]);return{[te]:o,attr:(e,t,n)=>i(se(e,t,n)),classAll:(...e)=>i(re(e)),classAny:(...e)=>i(function(e){const n=oe(e);if(0===n.length)return()=>!1;return e=>{if(!t.isHTMLElement(e))return!1;const o=e.classList;for(const e of n)if(o.contains(e))return!0;return!1}}(e)),styleAny:(e,n,o)=>i(function(e,n,o){if("string"==typeof n)return o=>t.isHTMLElement(o)&&o.style.getPropertyValue(e)===n;if(n instanceof RegExp){const r=o&&o.capture,s=n;return(n,o)=>{if(!t.isHTMLElement(n))return!1;const i=n.style.getPropertyValue(e);if(!i)return!1;const c=i.match(s);return null!==c&&(void 0!==r&&(o[r]=c),!0)}}r(362,JSON.stringify(e))}(e,n,o))}}function oe(e){const t=[];for(const n of e)n&&t.push(n);return t}function re(e){const n=oe(e);return 0===n.length?()=>!0:e=>{if(!t.isHTMLElement(e))return!1;const o=e.classList;for(const e of n)if(!o.contains(e))return!1;return!0}}function se(e,n,o){if(!0===n)return n=>t.isHTMLElement(n)&&n.hasAttribute(e);if("string"==typeof n)return o=>t.isHTMLElement(o)&&o.getAttribute(e)===n;if(n instanceof RegExp){const r=o&&o.capture,s=n;return(n,o)=>{if(!t.isHTMLElement(n))return!1;const i=n.getAttribute(e);if(null==i)return!1;const c=i.match(s);return null!==c&&(void 0!==r&&(o[r]=c),!0)}}r(361,JSON.stringify(e))}const ie={kind:"text",predicate:t.isDOMTextNode,tags:new Set},ce={[te]:ie},le={kind:"comment",predicate:e=>8===e.nodeType,tags:new Set},ue={[te]:le},ae={any:()=>ne(new Set,[]),comment:()=>ue,tag(...e){e.length>0||r(363);const t=new Set;for(const n of e)t.add(n.toUpperCase());return ne(t,[])},text:()=>ce};const fe=/[A-Za-z0-9_-]/;class de{constructor(e,t){this.source=e,this.pos=t}peek(e=0){return this.source[this.pos+e]||""}consume(){return this.source[this.pos++]||""}eof(){return this.pos>=this.source.length}skipWhitespace(){for(;!this.eof()&&/\s/.test(this.peek());)this.pos++}readIdent(){const e=this.pos;for(;!this.eof()&&fe.test(this.peek());)this.pos++;return this.source.slice(e,this.pos)}readQuoted(){const e=this.consume();this.assert('"'===e||"'"===e,"expected quote");const t=this.pos;for(;!this.eof()&&this.peek()!==e;)"\\"===this.peek()?this.pos+=2:this.pos++;this.assert(!this.eof(),"unterminated string");const n=this.source.slice(t,this.pos);return this.pos++,n.replace(/\\(.)/g,"$1")}assert(e,t){e||r(364,String(this.pos+1),t,this.source)}}function pe(e){const t=new Set,n=[],o=[];if(e.skipWhitespace(),"*"===e.peek())e.consume();else if(fe.test(e.peek())){const n=e.readIdent();n&&t.add(n.toUpperCase())}for(;!e.eof();){const t=e.peek();if("."===t){e.consume();const t=e.readIdent();e.assert(""!==t,'expected class name after "."'),o.push(t)}else if("#"===t){e.consume();const t=e.readIdent();e.assert(""!==t,'expected id after "#"'),n.push(se("id",t))}else{if("["!==t)break;{e.consume(),e.skipWhitespace();const t=e.readIdent();e.assert(""!==t,'expected attribute name after "["'),e.skipWhitespace();let o=!0;if("="===e.peek()){e.consume(),e.skipWhitespace();const t=e.peek();'"'===t||"'"===t?o=e.readQuoted():(o=e.readIdent(),e.assert(""!==o,"expected attribute value")),e.skipWhitespace()}e.assert("]"===e.peek(),'expected "]"'),e.consume(),n.push(se(t,o))}}}return o.length>0&&n.push(re(o)),{predicates:n,tags:t}}function he(e){const t=new de(e,0),n=[];for(;;){const e=pe(t);if(n.push(e),t.skipWhitespace(),t.eof())break;t.assert(","===t.peek(),'expected "," (selector lists are the only supported combinator)'),t.consume(),t.skipWhitespace()}if(1===n.length)return ne(n[0].tags,n[0].predicates);const o=new Set;for(const e of n)for(const t of e.tags)o.add(t);return ne(o,[(e,t)=>{for(const o of n){const n=e.nodeName;if(o.tags.size>0&&!o.tags.has(n))continue;let r=!0;for(const n of o.predicates)if(!n(e,t)){r=!1;break}if(r)return!0}return!1}])}function me(e){return e}function ge(e,t,n){return m(E,e,t,n)}const xe=ge("importSource",()=>"unknown"),ye=ge("importSourceDataTransfer",()=>null),$e=ge("textFormat",()=>0),Se=ge("textStyle",()=>({}));function Ee(e){if(!t.isHTMLElement(e))return!1;if("PRE"===e.nodeName)return!0;const n=e.style.whiteSpace;return"string"==typeof n&&n.startsWith("pre")}function De(e){if(t.isDOMTextNode(e))return!0;if(!t.isHTMLElement(e))return!1;const n=e.style.display;return n?n.startsWith("inline"):!t.isBlockDomNode(e)&&t.isInlineDomNode(e)}const Ce=ge("whitespaceConfig",()=>({isInline:De,preservesWhitespace:Ee})),Ie=ge("importOverlays",()=>[]);class Ne{constructor(e){this.record=e}get(e){return i(this.record,e)}set(e,t){this.record[e.key]=t}update(e,t){this.record[e.key]=t(i(this.record,e))}has(e){return Object.prototype.hasOwnProperty.call(this.record,e.key)}}function Oe(e){const t=o.getPeerDependencyFromEditor(e,S);return t?t.output.defaults:void 0}function ve(e,n=t.$getEditor()){return i(function(e){return l(E,e)||Oe(e)}(n),e)}const Me=h(E,Oe),Re=ae,Te=new Set(["center","end","justify","left","right","start"]);function be(e){return Te.has(e)}const ke={B:{fontWeight:"bold"},EM:{fontStyle:"italic"},I:{fontStyle:"italic"},S:{textDecoration:"line-through"},STRONG:{fontWeight:"bold"},SUB:{verticalAlign:"sub"},SUP:{verticalAlign:"super"},U:{textDecoration:"underline"}},we={CODE:t.IS_CODE,MARK:t.IS_HIGHLIGHT};const Le=new Set(["font-weight","font-style","text-decoration","vertical-align"]);const Ae={$import:(e,n)=>{const o=e.get($e),r=ke[n.nodeName],s=function(e){return{fontStyle:e.style.fontStyle,fontWeight:e.style.fontWeight,textDecoration:e.style.textDecoration,verticalAlign:e.style.verticalAlign}}(n),i=r?(c=r,{fontStyle:(l=s).fontStyle||c.fontStyle,fontWeight:l.fontWeight||c.fontWeight,textDecoration:l.textDecoration||c.textDecoration,verticalAlign:l.verticalAlign||c.verticalAlign}):s;var c,l;let u=(a=o,f=function(e){let n=0,o=0;const{fontWeight:r,fontStyle:s,textDecoration:i,verticalAlign:c}=e;if("700"===r||"bold"===r?n|=t.IS_BOLD:"normal"!==r&&"400"!==r||(o|=t.IS_BOLD),"italic"===s?n|=t.IS_ITALIC:"normal"===s&&(o|=t.IS_ITALIC),i){const e=i.split(" ");e.includes("underline")&&(n|=t.IS_UNDERLINE),e.includes("line-through")&&(n|=t.IS_STRIKETHROUGH),e.includes("none")&&(o|=t.IS_UNDERLINE|t.IS_STRIKETHROUGH)}return"sub"===c?(n|=t.IS_SUBSCRIPT,o|=t.IS_SUPERSCRIPT):"super"===c?(n|=t.IS_SUPERSCRIPT,o|=t.IS_SUBSCRIPT):"baseline"===c&&(o|=t.IS_SUBSCRIPT|t.IS_SUPERSCRIPT),{clear:o,set:n}}(i),a&~f.clear|f.set);var a,f;const p=we[n.nodeName];return p&&(u|=p),u===o?e.$importChildren(n):e.$importChildren(n,{context:[d($e,u)]})},match:Re.tag("b","strong","em","i","code","mark","s","sub","sup","u","span"),name:"@lexical/html/inline-format"};function _e(e,n,o){let r=e;for(;;){let e=null;for(;null===(e=n?r.nextSibling:r.previousSibling);){const e=r.parentNode;if(null===e)return null;r=e}if(r=e,!o.isInline(r))return null;let s=r;for(;null!==(s=n?r.firstChild:r.lastChild);)r=s;if(t.isDOMTextNode(r))return r;if("BR"===r.nodeName)return null}}function Fe(e,n){return 0!==n&&t.$isTextNode(e)?e.setFormat(n):e}function Pe(e,n){if(t.$isTextNode(e)){const t=function(e){let t="";for(const n in e)Le.has(n)||(t+=`${n}: ${e[n]}; `);return t.trimEnd()}(n);""!==t&&e.setStyle(t)}return e}const He={$import:(e,n)=>{const o=e.get($e),r=e.get(Se),s=e.get(Ce);if(function(e,t){let n=e.parentNode;for(;null!==n;){if(t.preservesWhitespace(n))return!0;n=n.parentNode}return!1}(n,s)){const e=t.$generateNodesFromRawText(n.textContent||"");for(const t of e)Fe(t,o),Pe(t,r);return e}const i=function(e,t){let n=(e.textContent||"").replace(/\r/g,"").replace(/[ \t\n]+/g," ");if(0===n.length)return"";if(" "===n[0]){let o=e,r=!0;for(;null!==o&&null!==(o=_e(o,!1,t));){const e=o.textContent||"";if(e.length>0){/[ \t\n]$/.test(e)&&(n=n.slice(1)),r=!1;break}}r&&(n=n.slice(1))}if(n.length>0&&" "===n[n.length-1]){let o=e,r=!0;for(;null!==o&&null!==(o=_e(o,!0,t));)if((o.textContent||"").replace(/^( |\t|\r?\n)+/,"").length>0){r=!1;break}r&&(n=n.slice(0,-1))}return n}(n,s);if(""===i)return[];const c=t.$createTextNode(i);return Fe(c,o),Pe(c,r),[c]},match:Re.text(),name:"@lexical/html/#text"},We={$import:()=>[],match:Re.tag("script","style"),name:"@lexical/html/script-style-ignore"},Be={$import:()=>[t.$createLineBreakNode()],match:Re.tag("br"),name:"@lexical/html/br"},Ue=[We,{$import:(e,n)=>{const o=t.$createParagraphNode();if(t.$setFormatFromDOM(o,n),t.setNodeIndentFromDOM(n,o),""===o.getFormatType()){const e=n.getAttribute("align");e&&be(e)&&o.setFormat(e)}return t.$setDirectionFromDOM(o,n),[o.splice(0,0,e.$importChildren(n))]},match:Re.tag("p"),name:"@lexical/html/p"},He,Be,Ae];function je(e,t){const n=[];let o=0,r=0;for(;o<e.length&&r<t.length;)e[o]<=t[r]?n.push(e[o++]):n.push(t[r++]);for(;o<e.length;)n.push(e[o++]);for(;r<t.length;)n.push(t[r++]);return n}function ze(e){const t=[],n=new Map,o=[],s=[],i=[],c=new Set;e.forEach((e,l)=>{const u=function(e){const t=e[te];return void 0===t&&r(360),t}(e.match),a=e.name||function(e,t){if("text"===e.kind)return`#text@${t}`;if("comment"===e.kind)return`#comment@${t}`;if(0===e.tags.size)return`*@${t}`;const n=Array.from(e.tags).join(",").toLowerCase();return`${n}@${t}`}(u,l);if(e.name&&c.add(e.name),t.push({$import:e.$import,name:a,predicate:u.predicate}),"text"===u.kind)s.push(l);else if("comment"===u.kind)i.push(l);else if(0===u.tags.size)o.push(l);else for(const e of u.tags){let t=n.get(e);t||(t=[],n.set(e,t)),t.push(l)}});const l=new Map;if(0===o.length)for(const[e,t]of n)l.set(e,t);else for(const[e,t]of n)l.set(e,je(t,o));return{byTag:l,commentIndices:i,rules:t,textIndices:s,wildcardIndices:o}}function Ve(e,n){return t.isDOMTextNode(n)?e.textIndices:8===n.nodeType?e.commentIndices:t.isHTMLElement(n)?e.byTag.get(n.nodeName)||e.wildcardIndices:Ge}const Ge=Object.freeze([]);function qe(e){const t=[];for(const n of e)if(Ke(n))for(const e of n.rules)t.push(e);else t.push(n);return t}function Ke(e){return"object"==typeof e&&null!==e&&"__type"in e&&"CompiledOverlayRules"===e.__type}function Je(e){return t.$isBlockElementNode(e)||t.$isDecoratorNode(e)&&!e.isInline()}function Qe(e,n,o){const r=t.$createParagraphNode();if(t.isHTMLElement(o)){const e=o.style.textAlign;be(e)&&r.setFormat(e)}return[r.splice(0,0,e)]}const Ye={$accepts:Je,$packageRun:Qe,name:"BlockSchema"},Ze={$accepts:e=>!Je(e),name:"InlineSchema"},Xe={$accepts:Je,$packageRun:e=>e,name:"NestedBlockSchema"},et={$accepts:Je,$packageRun:Qe,name:"RootSchema"},tt=Object.freeze({});function nt(e,t){const n={$importChildren:(t,n)=>function(e,t,n){const o=n&&n.rules?n.rules.dispatch:void 0;o&&e.overlays.push(o);try{const o=()=>ot(e,t,n);return n&&n.context?Me(n.context,e.editor)(o):o()}finally{o&&e.overlays.pop()}}(e,t,n),$importOne:(t,n)=>rt(e,t,n),captures:t,get:t=>ve(t,e.editor),session:e.session};return n}function ot(e,t,n){const o=n&&n.$onChild,r=[];for(const n of Array.from(t.childNodes)){const t=rt(e,n,void 0);for(const e of t){const t=o?o(e):e;null!=t&&r.push(t)}}const s=n&&n.$after?n.$after(r):r,i=n&&n.schema;return i?function(e,t,n,o){const r=[];let s=null;const i=()=>{if(null===s)return;const t=s;if(s=null,e.$packageRun){const s=e.$packageRun(t,n,o);if(s.length>0){for(const e of s)r.push(e);return}}if("hoist"===e.onReject)for(const e of t)r.push(e)};for(const o of t)e.$accepts(o,n)?(i(),r.push(o)):(null===s&&(s=[]),s.push(o));return i(),e.$finalize?e.$finalize(r,n):r}(i,s,null,t):s}function rt(e,t,n){const o=()=>function(e,t){const n=function(e,t){const n=[];for(let o=e.overlays.length-1;o>=0;o--){const r=e.overlays[o],s=Ve(r,t);s.length>0&&n.push({dispatch:r,indices:s})}const o=Ve(e.dispatch,t);o.length>0&&n.push({dispatch:e.dispatch,indices:o});return n}(e,t);if(0===n.length)return st(e,t);let o=0,r=0;const s=()=>{for(;o<n.length;){const{dispatch:i,indices:c}=n[o];for(;r<c.length;){const n=c[r++],o=i.rules[n],l={};if(o.predicate(t,l)){const n=nt(e,0===Object.keys(l).length?tt:l);try{return o.$import(n,t,s)}catch(e){throw e}}}o++,r=0}return st(e,t)};return s()}(e,t);return n&&n.context?Me(n.context,e.editor)(o):o()}function st(e,t){if(0===t.childNodes.length)return[];const n=[];for(const o of Array.from(t.childNodes)){const t=rt(e,o,void 0);for(const e of t)n.push(e)}return n}const it={$import:(e,t)=>e.$importChildren(t),match:ae.any(),name:"@lexical/html/default-hoist"},ct=t.defineExtension({build(e,n){const o=ze(qe(n.rules)),r=a(n.contextDefaults,void 0),s=n.preprocess;return{$generateNodesFromDOM:(n,i)=>{const c=i&&i.context?a(i.context,r):r,l=void 0!==c&&c!==r?c:Object.create(r||null),u=new Ne(l),f={session:u};return function(e,t,n){let o=e.length-1;const r=()=>{for(;o>=0;)return void(0,e[o--])(t,n,r)};r()}(i&&i.preprocess?[...s,...i.preprocess]:s,n,f),p(E,l,()=>function(e,n,o,r){return ot({dispatch:e,editor:n,overlays:r.get(Ie).map(e=>e.dispatch),session:r},t.isDOMDocumentNode(o)?o.body:o,{schema:et})}(o,e,n,u),e)},defaults:r}},config:{contextDefaults:[],preprocess:[g],rules:[it]},mergeConfig:(e,n)=>t.shallowMergeConfig(e,{...n,...n.contextDefaults&&{contextDefaults:[...e.contextDefaults,...n.contextDefaults]},...n.preprocess&&{preprocess:[...e.preprocess,...n.preprocess]},...n.rules&&{rules:[...n.rules,...e.rules]}}),name:S});const lt=t.defineExtension({dependencies:[t.configExtension(ct,{rules:Ue})],name:"@lexical/html/CoreImport"}),ut=[{$import:()=>[o.$createHorizontalRuleNode()],match:ae.tag("hr"),name:"@lexical/html/hr"}],at=t.defineExtension({dependencies:[lt,o.HorizontalRuleExtension,t.configExtension(ct,{rules:ut})],name:"@lexical/html/HorizontalRuleImport"}),ft={any:ae.any,comment:ae.comment,css:he,tag:ae.tag,text:ae.text},dt=new Set(["STYLE","SCRIPT"]);function pt(e,n=null,o=t.$getEditor()){return R([d(N,!0)],o)(()=>{const r=t.$getRoot(),s=M(o),i=e.append.bind(e);for(const e of r.getChildren())ht(o,e,i,n,s);return e})}function ht(n,o,r,s=null,i=t.$getEditorDOMRenderConfig(n)){let c=i.$shouldInclude(o,s,n);const l=i.$shouldExclude(o,s,n);let u=o;null!==s&&t.$isTextNode(o)&&(u=e.$sliceSelectedTextNodeContent(s,o,"clone"));const a=i.$exportDOM(u,n),{element:f,after:d,append:p,$getChildNodes:h}=a;if(!f)return!1;const m=document.createDocumentFragment(),g=h?h():t.$isElementNode(u)?u.getChildren():[],x=m.append.bind(m);for(const e of g){const t=ht(n,e,x,s,i);!c&&t&&i.$extractWithChild(o,e,s,"html",n)&&(c=!0)}if(c&&!l){if((t.isHTMLElement(f)||t.isDocumentFragment(f))&&(p?p(m):f.append(m)),r(f),d){const e=d.call(u,f);e&&(t.isDocumentFragment(f)?f.replaceChildren(e):f.replaceWith(e))}}else r(m);return c}function mt(e,n,o,r,s=new Map,i){const c=[];if(dt.has(e.nodeName))return c;let l=null;const u=function(e,t){const{nodeName:n}=e,o=t._htmlConversions.get(n.toLowerCase());let r=null;if(void 0!==o)for(const t of o){const n=t(e);null!==n&&(null===r||(r.priority||0)<=(n.priority||0))&&(r=n)}return null!==r?r.conversion:null}(e,n),a=u?u(e):null;let f=null;if(null!==a){f=a.after;const t=a.node;if(l=Array.isArray(t)?t[t.length-1]:t,null!==l){for(const[,e]of s)if(l=e(l,i),!l)break;l&&c.push(...Array.isArray(t)?t:[l])}null!=a.forChild&&s.set(e.nodeName,a.forChild)}const d=e.childNodes;let p=[];const h=(null==l||!t.$isRootOrShadowRoot(l))&&(null!=l&&t.$isBlockElementNode(l)||r);for(let e=0;e<d.length;e++)p.push(...mt(d[e],n,o,h,new Map(s),l));if(null!=f&&(p=f(p)),t.isBlockDomNode(e)&&(p=gt(e,p,h?()=>{const e=new t.ArtificialNode__DO_NOT_USE;return o.push(e),e}:t.$createParagraphNode)),null==l)if(p.length>0)for(const e of p)c.push(e);else t.isBlockDomNode(e)&&function(e){if(null==e.nextSibling||null==e.previousSibling)return!1;return t.isInlineDomNode(e.nextSibling)&&t.isInlineDomNode(e.previousSibling)}(e)&&c.push(t.$createLineBreakNode());else t.$isElementNode(l)&&l.append(...p);return c}function gt(e,n,o){const r=e.style.textAlign,s=[];let i=[];for(let e=0;e<n.length;e++){const c=n[e];if(t.$isBlockElementNode(c))r&&!c.getFormat()&&c.setFormat(r),s.push(c);else if(i.push(c),e===n.length-1||e<n.length-1&&t.$isBlockElementNode(n[e+1])){const e=o();e.setFormat(r),e.append(...i),s.push(e),i=[]}}return s}exports.$distributeInlineWrapper=function e(n,o){const r=[];let s=[];const i=()=>{0!==s.length&&(r.push(o().splice(0,0,s)),s=[])};for(const c of n)if(Je(c)){if(i(),t.$isElementNode(c)){const t=e(c.getChildren(),o);c.splice(0,c.getChildrenSize(),t)}r.push(c)}else s.push(c);return i(),r},exports.$generateDOMFromNodes=pt,exports.$generateDOMFromRoot=function(e,n=t.$getRoot()){const o=t.$getEditor();return R([d(N,!0),d(I,!0)],o)(()=>{const t=M(o),r=e.append.bind(e);return ht(o,n,r,null,t),e})},exports.$generateHtmlFromNodes=function(e,t=null){return("undefined"==typeof document||"undefined"==typeof window&&void 0===global.window)&&r(338),pt(document.createElement("div"),t,e).innerHTML},exports.$generateNodesFromDOM=function(e,n){x(n);const o=t.isDOMDocumentNode(n)?n.body.childNodes:n.childNodes,r=[],s=[];for(const t of o)if(!dt.has(t.nodeName)){const n=mt(t,e,s,!1);if(null!==n)for(const e of n)r.push(e)}return function(e){for(const n of e)n.getParent()&&n.getNextSibling()instanceof t.ArtificialNode__DO_NOT_USE&&n.insertAfter(t.$createLineBreakNode());for(const t of e){const e=t.getParent();e&&e.splice(t.getIndexWithinParent(),1,t.getChildren())}}(s),r},exports.$generateNodesFromDOMViaExtension=function(e,t){return o.$getExtensionOutput(ct).$generateNodesFromDOM(e,t)},exports.$getImportContextValue=ve,exports.$getRenderContextValue=function(e,n=t.$getEditor()){return i(function(e){return l($,e)||O(e)}(n),e)},exports.$getSessionDOMRenderConfig=M,exports.$inlineStylesFromStyleSheets=g,exports.$isBlockLevel=Je,exports.$setRenderContextValue=function(e,n,o=t.$getEditor()){const r=v(o);r&&r.setContextValue(e,n)},exports.$updateRenderContextValue=function(e,n,o=t.$getEditor()){const r=v(o);r&&r.setContextValue(e,n(i(r.editorContext,e)))},exports.$withImportContext=Me,exports.$withRenderContext=R,exports.BlockSchema=Ye,exports.CoreImportExtension=lt,exports.CoreImportRules=Ue,exports.DOMImportExtension=ct,exports.DOMRenderExtension=ee,exports.HorizontalRuleImportExtension=at,exports.HorizontalRuleImportRules=ut,exports.ImportOverlays=Ie,exports.ImportSource=xe,exports.ImportSourceDataTransfer=ye,exports.ImportTextFormat=$e,exports.ImportTextStyle=Se,exports.ImportWhitespaceConfig=Ce,exports.InlineSchema=Ze,exports.NestedBlockSchema=Xe,exports.RenderContextExport=N,exports.RenderContextRoot=I,exports.RootSchema=et,exports.contextUpdater=function(e,t){return{cfg:e,updater:t}},exports.contextValue=d,exports.createImportState=ge,exports.createRenderState=C,exports.defaultIsInline=De,exports.defaultPreservesWhitespace=Ee,exports.defineImportRule=me,exports.defineOverlayRules=function(e){const t=qe(e);return{__type:"CompiledOverlayRules",dispatch:ze(t),rules:t}},exports.domOverride=function(e,t,n){return{...t,...n,nodes:e}},exports.isElementOfTag=function(e,n){return t.isHTMLElement(e)&&e.nodeName===n.toUpperCase()},exports.parseSelector=he,exports.sel=ft; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import{$sliceSelectedTextNodeContent as t}from"@lexical/selection";import{$getEditor as e,createState as n,isDOMDocumentNode as o,isHTMLElement as r,$getEditorDOMRenderConfig as s,DEFAULT_EDITOR_DOM_CONFIG as i,$isLexicalNode as c,getStaticNodeConfig as l,$fullReconcile as u,defineExtension as f,shallowMergeConfig as a,RootNode as p,isDOMTextNode as d,isBlockDomNode as h,isInlineDomNode as g,IS_HIGHLIGHT as m,IS_CODE as y,$generateNodesFromRawText as x,$createTextNode as S,$createLineBreakNode as $,$createParagraphNode as v,$setFormatFromDOM as b,setNodeIndentFromDOM as C,$setDirectionFromDOM as k,$isTextNode as O,IS_BOLD as w,IS_ITALIC as D,IS_UNDERLINE as M,IS_STRIKETHROUGH as A,IS_SUBSCRIPT as N,IS_SUPERSCRIPT as I,$isElementNode as E,$isBlockElementNode as W,$isDecoratorNode as R,configExtension as j,$getRoot as T,isDocumentFragment as F,$isRootOrShadowRoot as P,ArtificialNode__DO_NOT_USE as z}from"lexical";import{objectKlassEquals as _}from"@lexical/utils";import{getPeerDependencyFromEditor as L,getKnownTypesAndNodes as U,$getExtensionOutput as B,HorizontalRuleExtension as V,$createHorizontalRuleNode as q}from"@lexical/extension";function G(t,...e){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const t of e)o.append("v",t);throw n.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}let H;function J(t,e){const{key:n}=e;return t&&n in t?t[n]:e.defaultValue}function Q(t){return H&&H.editor===t?H:void 0}function K(t,e){const n=Q(e);return n&&n[t]}function Y(t,e){if("cfg"in e){const{cfg:n,updater:o}=e;return[n,o(J(t,n))]}return e}function Z(t,e){let n=e;for(const o of t){const[t,r]=Y(n,o),s=t.key;if(n===e&&J(n,t)===r)continue;const i=n===e||void 0===n?X(e):n;i[s]=r,n=i}return n}function X(t){return Object.create(t||null)}function tt(t,e){return[t,e]}function et(t,e){return{cfg:t,updater:e}}function nt(t,n,o,r=e()){const s=H,i=Q(r);try{return H={...i,editor:r,[t]:n},o()}finally{H=s}}function ot(t,n=()=>{}){return(o,r=e())=>e=>{const s=Q(r),i=s&&s[t],c=Z(o,i||n(r));return c&&c!==i?nt(t,c,e,r):e()}}function rt(t,e,o,r){return Object.assign(n(Symbol(e),{isEqual:r,parse:o}),{[t]:!0})}const st=(t,e,n)=>{it(t),n()};function it(t){if(!o(t))return;const e=t;if(null===e.querySelector("style"))return;const n=new Map;function s(t){let e=n.get(t);if(void 0===e){e=new Set;for(let n=0;n<t.style.length;n++)e.add(t.style[n]);n.set(t,e)}return e}try{for(const t of Array.from(e.styleSheets)){let n;try{n=t.cssRules}catch(t){continue}for(const t of Array.from(n)){if(!_(t,CSSStyleRule))continue;let n;try{n=e.querySelectorAll(t.selectorText)}catch(t){continue}for(const e of Array.from(n)){if(!r(e))continue;const n=s(e);for(let o=0;o<t.style.length;o++){const r=t.style[o];n.has(r)||e.style.setProperty(r,t.style.getPropertyValue(r),t.style.getPropertyPriority(r))}}}}}catch(t){}}const ct="@lexical/html/DOM",lt=Symbol.for("@lexical/html/DOMExportContext"),ut="@lexical/html/DOMImport",ft=Symbol.for("@lexical/html/DOMImportContext"),at=()=>!0;function pt(t,e,n){return rt(lt,t,e,n)}const dt=pt("root",Boolean),ht=pt("isExport",Boolean);function gt(t){const e=L(t,ct);return e?e.output.defaults:void 0}function mt(t,n=e()){return J(function(t){return K(lt,t)||gt(t)}(n),t)}function yt(t){const e=L(t,ct);return e?e.output.runtime:void 0}function xt(t,n,o=e()){const r=yt(o);r&&r.setContextValue(t,n)}function St(t,n,o=e()){const r=yt(o);r&&r.setContextValue(t,n(J(r.editorContext,t)))}function $t(t=e()){const n=yt(t);return n?n.getSessionConfig():s(t)}const vt=ot(lt,gt);function bt(t,e,n){return{...e,...n,nodes:t}}function Ct(t){return e=>e instanceof t}function kt(t,{nodes:e}){if("*"===e)return at;let n={};const o=[];for(const r of e)if("getType"in r){const e=r.getType();if(n){const o=t[e];void 0===o&&G(339,r.name,e),n=Object.assign(n,o.types)}o.push(Ct(r))}else n=void 0,o.push(r);return n||(1===o.length?o[0]:t=>{for(const e of o)if(e(t))return!0;return!1})}function Ot(t){return(e,n,o)=>t(e,o)}function wt(t){return(e,n,o,r)=>t(e,n,r)}function Dt(t){return(e,n,o,r,s)=>t(e,n,o,s)}function Mt(t){return(e,n,o,r,s,i)=>t(e,n,o,r,i)}function At(t,e){return(n,o)=>{const r=()=>t(n,o),s=e(n);return s?s(n,r,o):r()}}function Nt(t,e){return(n,o,r)=>{const s=()=>t(n,o,r),i=e(n);return i?i(n,o,s,r):s()}}const It=Nt,Et=wt;function Wt(t,e){return(n,o,r,s)=>{const i=()=>t(n,o,r,s),c=e(n);return c?c(n,o,r,i,s):i()}}function Rt(t,e){return(n,o,r,s,i)=>{const c=()=>t(n,o,r,s,i),l=e(n);return l?l(n,o,r,s,c,i):c()}}function jt(t,e){return(n,o,r,s)=>{t(n,o,r,s);const i=e(n);i&&i(n,o,r,s)}}function Tt(t,e,n,o,r){let s=n[e];for(const n of t[e])if("function"==typeof n[0]){const[t,e]=n;s=o(s,n=>t(n)&&e||void 0)}else{const t=n[1],e={};for(const n in t){const r=t[n];r&&(e[n]=r.reduce((t,e)=>o(t,()=>e),s))}s=o(s,t=>{const n=e[t.getType()];return n&&r(n)})}n[e]=s}function Ft(t,e,n,o){if(!o)return;const r=t[e];if("function"==typeof n)r.push([n,o]);else{const t=r[r.length-1];let e;t&&"types"===t[0]?e=t[1]:(e={},r.push(["types",e]));for(const t in n){const n=e[t]||[];e[t]=n,n.push(o)}}}function Pt(t){return"*"===t.nodes}function zt(t,e){const n=function(t){const e={},{nodes:n}=U(t);for(const t of n)e[t.getType()]={klass:t,types:{}};for(const t of Object.values(e))if(t){const n=t.klass.getType();for(let{klass:o}=t;c(o.prototype);o=Object.getPrototypeOf(o)){const{ownNodeType:t}=l(o),r=t&&e[t];r&&(r.types[n]=!0)}}return e}(t),o={$createDOM:[],$decorateDOM:[],$exportDOM:[],$extractWithChild:[],$getDOMSlot:[],$shouldExclude:[],$shouldInclude:[],$updateDOM:[]};for(const t of function(t){const e=[],n=[],o=[];for(const r of t)if(Pt(r))e.push(r);else if(Array.isArray(r.nodes))for(const t of r.nodes)c(t.prototype)?o.push(1===r.nodes.length?r:{...r,nodes:[t]}):n.push(1===r.nodes.length?r:{...r,nodes:[t]});const r=new Map,s=t=>{let e=r.get(t);if(void 0===e){e=0;for(let n=t;c(n.prototype);n=Object.getPrototypeOf(n))e++;r.set(t,e)}return e};return o.sort((t,e)=>s(t.nodes[0])-s(e.nodes[0])),[...o,...n,...e]}(e)){const e=kt(n,t);for(const n in o){Ft(o,n,e,t[n])}}return o}function _t(t){return t}function Lt(t,{overrides:e}){const n=zt(t,e),o={...i,...t.dom};return Tt(n,"$createDOM",o,At,Ot),Tt(n,"$exportDOM",o,At,Ot),Tt(n,"$extractWithChild",o,Rt,Mt),Tt(n,"$getDOMSlot",o,It,Et),Tt(n,"$shouldExclude",o,Nt,wt),Tt(n,"$shouldInclude",o,Nt,wt),Tt(n,"$updateDOM",o,Wt,Dt),Tt(n,"$decorateDOM",o,jt,_t),o}function Ut(t){return{get:e=>J(t,e)}}function Bt(t){const e=Object.create(null);return Z(t,e)||e}function Vt(t,e){const n=Ut(e);return t.filter(t=>!(t.disabledForEditor&&t.disabledForEditor(n)))}function qt(t){if("*"===t.nodes)return()=>!0;const e=t.nodes.map(t=>{const e=t;return c(e.prototype)?t=>t instanceof e:t});return t=>e.some(e=>e(t))}class Gt{editor;initialEditorConfig;overrides;editorContext;hasSessionGates;installed;sessionCache=new Map;constructor(t,e,n,o){this.editor=t,this.initialEditorConfig=e,this.overrides=n,this.editorContext=o,this.installed=Vt(n,o),this.hasSessionGates=n.some(t=>t.disabledForSession)}setContextValue(t,e){const n=this.installed;this.editorContext[t.key]=e;const o=Vt(this.overrides,this.editorContext);if(function(t,e){if(t.length!==e.length)return!1;for(let n=0;n<t.length;n++)if(t[n]!==e[n])return!1;return!0}(n,o))return;const r=function(t,e){const n=new Set(t),o=new Set(e),r=[];for(const e of t)o.has(e)||r.push(e);for(const t of e)n.has(t)||r.push(t);return r}(n,o);this.installed=o,this.sessionCache.clear();const s=Lt(this.initialEditorConfig,{overrides:o});this.editor._config.dom=s;const i=function(t){const e=[];for(const n of t)(n.$createDOM||n.$getDOMSlot||n.$decorateDOM)&&e.push(qt(n));return 0===e.length?null:t=>e.some(e=>e(t))}(r);if(!i)return;const c=s.$updateDOM;s.$updateDOM=(t,e,n,o)=>!!i(t)||c(t,e,n,o),this.editor.update(u,{discrete:!0}),s.$updateDOM=c}getSessionConfig(){const t=this.editor._config.dom||i;if(!this.hasSessionGates)return t;const e=Ut(K(lt,this.editor)||this.editorContext),n=[],o=[];if(this.installed.forEach((t,r)=>{t.disabledForSession&&t.disabledForSession(e)?n.push(String(r)):o.push(t)}),0===n.length)return t;const r=n.join(",");let s=this.sessionCache.get(r);return s||(s=Lt(this.initialEditorConfig,{overrides:o}),this.sessionCache.set(r,s)),s}}const Ht=f({build(t,e,n){const{initialEditorConfig:o}=n.getInitResult(),r=Bt(e.contextDefaults);return{defaults:r,runtime:new Gt(t,o,e.overrides,r)}},config:{contextDefaults:[],overrides:[]},html:{export:new Map([[p,()=>{const t=document.createElement("div");return t.role="textbox",{element:t}}]])},init(t,e){const n={dom:t.dom,nodes:t.nodes},o=Bt(e.contextDefaults),r=Vt(e.overrides,o);return t.dom=Lt(t,{overrides:r}),{initialEditorConfig:n}},mergeConfig(t,e){const n=a(t,e);for(const o of["overrides","contextDefaults"])e[o]&&(n[o]=[...t[o],...e[o]]);return n},name:ct}),Jt=Symbol.for("@lexical/html/SelectorImpl");function Qt(t,e){const n={kind:"element",predicate:(o=e,0===o.length?r:1===o.length?o[0]:(t,e)=>{for(const n of o)if(!n(t,e))return!1;return!0}),tags:t};var o;const s=n=>Qt(t,[...e,n]);return{[Jt]:n,attr:(t,e,n)=>s(Zt(t,e,n)),classAll:(...t)=>s(Yt(t)),classAny:(...t)=>s(function(t){const e=Kt(t);if(0===e.length)return()=>!1;return t=>{if(!r(t))return!1;const n=t.classList;for(const t of e)if(n.contains(t))return!0;return!1}}(t)),styleAny:(t,e,n)=>s(function(t,e,n){if("string"==typeof e)return n=>r(n)&&n.style.getPropertyValue(t)===e;if(e instanceof RegExp){const o=n&&n.capture,s=e;return(e,n)=>{if(!r(e))return!1;const i=e.style.getPropertyValue(t);if(!i)return!1;const c=i.match(s);return null!==c&&(void 0!==o&&(n[o]=c),!0)}}G(362,JSON.stringify(t))}(t,e,n))}}function Kt(t){const e=[];for(const n of t)n&&e.push(n);return e}function Yt(t){const e=Kt(t);return 0===e.length?()=>!0:t=>{if(!r(t))return!1;const n=t.classList;for(const t of e)if(!n.contains(t))return!1;return!0}}function Zt(t,e,n){if(!0===e)return e=>r(e)&&e.hasAttribute(t);if("string"==typeof e)return n=>r(n)&&n.getAttribute(t)===e;if(e instanceof RegExp){const o=n&&n.capture,s=e;return(e,n)=>{if(!r(e))return!1;const i=e.getAttribute(t);if(null==i)return!1;const c=i.match(s);return null!==c&&(void 0!==o&&(n[o]=c),!0)}}G(361,JSON.stringify(t))}const Xt={kind:"text",predicate:d,tags:new Set},te={[Jt]:Xt},ee={kind:"comment",predicate:t=>8===t.nodeType,tags:new Set},ne={[Jt]:ee},oe={any:()=>Qt(new Set,[]),comment:()=>ne,tag(...t){t.length>0||G(363);const e=new Set;for(const n of t)e.add(n.toUpperCase());return Qt(e,[])},text:()=>te};function re(t,e){return r(t)&&t.nodeName===e.toUpperCase()}const se=/[A-Za-z0-9_-]/;class ie{constructor(t,e){this.source=t,this.pos=e}peek(t=0){return this.source[this.pos+t]||""}consume(){return this.source[this.pos++]||""}eof(){return this.pos>=this.source.length}skipWhitespace(){for(;!this.eof()&&/\s/.test(this.peek());)this.pos++}readIdent(){const t=this.pos;for(;!this.eof()&&se.test(this.peek());)this.pos++;return this.source.slice(t,this.pos)}readQuoted(){const t=this.consume();this.assert('"'===t||"'"===t,"expected quote");const e=this.pos;for(;!this.eof()&&this.peek()!==t;)"\\"===this.peek()?this.pos+=2:this.pos++;this.assert(!this.eof(),"unterminated string");const n=this.source.slice(e,this.pos);return this.pos++,n.replace(/\\(.)/g,"$1")}assert(t,e){t||G(364,String(this.pos+1),e,this.source)}}function ce(t){const e=new Set,n=[],o=[];if(t.skipWhitespace(),"*"===t.peek())t.consume();else if(se.test(t.peek())){const n=t.readIdent();n&&e.add(n.toUpperCase())}for(;!t.eof();){const e=t.peek();if("."===e){t.consume();const e=t.readIdent();t.assert(""!==e,'expected class name after "."'),o.push(e)}else if("#"===e){t.consume();const e=t.readIdent();t.assert(""!==e,'expected id after "#"'),n.push(Zt("id",e))}else{if("["!==e)break;{t.consume(),t.skipWhitespace();const e=t.readIdent();t.assert(""!==e,'expected attribute name after "["'),t.skipWhitespace();let o=!0;if("="===t.peek()){t.consume(),t.skipWhitespace();const e=t.peek();'"'===e||"'"===e?o=t.readQuoted():(o=t.readIdent(),t.assert(""!==o,"expected attribute value")),t.skipWhitespace()}t.assert("]"===t.peek(),'expected "]"'),t.consume(),n.push(Zt(e,o))}}}return o.length>0&&n.push(Yt(o)),{predicates:n,tags:e}}function le(t){const e=new ie(t,0),n=[];for(;;){const t=ce(e);if(n.push(t),e.skipWhitespace(),e.eof())break;e.assert(","===e.peek(),'expected "," (selector lists are the only supported combinator)'),e.consume(),e.skipWhitespace()}if(1===n.length)return Qt(n[0].tags,n[0].predicates);const o=new Set;for(const t of n)for(const e of t.tags)o.add(e);return Qt(o,[(t,e)=>{for(const o of n){const n=t.nodeName;if(o.tags.size>0&&!o.tags.has(n))continue;let r=!0;for(const n of o.predicates)if(!n(t,e)){r=!1;break}if(r)return!0}return!1}])}function ue(t){return t}function fe(t,e,n){return rt(ft,t,e,n)}const ae=fe("importSource",()=>"unknown"),pe=fe("importSourceDataTransfer",()=>null),de=fe("textFormat",()=>0),he=fe("textStyle",()=>({}));function ge(t){if(!r(t))return!1;if("PRE"===t.nodeName)return!0;const e=t.style.whiteSpace;return"string"==typeof e&&e.startsWith("pre")}function me(t){if(d(t))return!0;if(!r(t))return!1;const e=t.style.display;return e?e.startsWith("inline"):!h(t)&&g(t)}const ye=fe("whitespaceConfig",()=>({isInline:me,preservesWhitespace:ge})),xe=fe("importOverlays",()=>[]);class Se{constructor(t){this.record=t}get(t){return J(this.record,t)}set(t,e){this.record[t.key]=e}update(t,e){this.record[t.key]=e(J(this.record,t))}has(t){return Object.prototype.hasOwnProperty.call(this.record,t.key)}}function $e(t){const e=L(t,ut);return e?e.output.defaults:void 0}function ve(t,n=e()){return J(function(t){return K(ft,t)||$e(t)}(n),t)}const be=ot(ft,$e),Ce=oe,ke=new Set(["center","end","justify","left","right","start"]);function Oe(t){return ke.has(t)}const we={B:{fontWeight:"bold"},EM:{fontStyle:"italic"},I:{fontStyle:"italic"},S:{textDecoration:"line-through"},STRONG:{fontWeight:"bold"},SUB:{verticalAlign:"sub"},SUP:{verticalAlign:"super"},U:{textDecoration:"underline"}},De={CODE:y,MARK:m};const Me=new Set(["font-weight","font-style","text-decoration","vertical-align"]);const Ae={$import:(t,e)=>{const n=t.get(de),o=we[e.nodeName],r=function(t){return{fontStyle:t.style.fontStyle,fontWeight:t.style.fontWeight,textDecoration:t.style.textDecoration,verticalAlign:t.style.verticalAlign}}(e),s=o?(i=o,{fontStyle:(c=r).fontStyle||i.fontStyle,fontWeight:c.fontWeight||i.fontWeight,textDecoration:c.textDecoration||i.textDecoration,verticalAlign:c.verticalAlign||i.verticalAlign}):r;var i,c;let l=(u=n,f=function(t){let e=0,n=0;const{fontWeight:o,fontStyle:r,textDecoration:s,verticalAlign:i}=t;if("700"===o||"bold"===o?e|=w:"normal"!==o&&"400"!==o||(n|=w),"italic"===r?e|=D:"normal"===r&&(n|=D),s){const t=s.split(" ");t.includes("underline")&&(e|=M),t.includes("line-through")&&(e|=A),t.includes("none")&&(n|=M|A)}return"sub"===i?(e|=N,n|=I):"super"===i?(e|=I,n|=N):"baseline"===i&&(n|=N|I),{clear:n,set:e}}(s),u&~f.clear|f.set);var u,f;const a=De[e.nodeName];return a&&(l|=a),l===n?t.$importChildren(e):t.$importChildren(e,{context:[tt(de,l)]})},match:Ce.tag("b","strong","em","i","code","mark","s","sub","sup","u","span"),name:"@lexical/html/inline-format"};function Ne(t,e,n){let o=t;for(;;){let t=null;for(;null===(t=e?o.nextSibling:o.previousSibling);){const t=o.parentNode;if(null===t)return null;o=t}if(o=t,!n.isInline(o))return null;let r=o;for(;null!==(r=e?o.firstChild:o.lastChild);)o=r;if(d(o))return o;if("BR"===o.nodeName)return null}}function Ie(t,e){return 0!==e&&O(t)?t.setFormat(e):t}function Ee(t,e){if(O(t)){const n=function(t){let e="";for(const n in t)Me.has(n)||(e+=`${n}: ${t[n]}; `);return e.trimEnd()}(e);""!==n&&t.setStyle(n)}return t}const We={$import:(t,e)=>{const n=t.get(de),o=t.get(he),r=t.get(ye);if(function(t,e){let n=t.parentNode;for(;null!==n;){if(e.preservesWhitespace(n))return!0;n=n.parentNode}return!1}(e,r)){const t=x(e.textContent||"");for(const e of t)Ie(e,n),Ee(e,o);return t}const s=function(t,e){let n=(t.textContent||"").replace(/\r/g,"").replace(/[ \t\n]+/g," ");if(0===n.length)return"";if(" "===n[0]){let o=t,r=!0;for(;null!==o&&null!==(o=Ne(o,!1,e));){const t=o.textContent||"";if(t.length>0){/[ \t\n]$/.test(t)&&(n=n.slice(1)),r=!1;break}}r&&(n=n.slice(1))}if(n.length>0&&" "===n[n.length-1]){let o=t,r=!0;for(;null!==o&&null!==(o=Ne(o,!0,e));)if((o.textContent||"").replace(/^( |\t|\r?\n)+/,"").length>0){r=!1;break}r&&(n=n.slice(0,-1))}return n}(e,r);if(""===s)return[];const i=S(s);return Ie(i,n),Ee(i,o),[i]},match:Ce.text(),name:"@lexical/html/#text"},Re={$import:()=>[],match:Ce.tag("script","style"),name:"@lexical/html/script-style-ignore"},je={$import:()=>[$()],match:Ce.tag("br"),name:"@lexical/html/br"},Te=[Re,{$import:(t,e)=>{const n=v();if(b(n,e),C(e,n),""===n.getFormatType()){const t=e.getAttribute("align");t&&Oe(t)&&n.setFormat(t)}return k(n,e),[n.splice(0,0,t.$importChildren(e))]},match:Ce.tag("p"),name:"@lexical/html/p"},We,je,Ae];function Fe(t,e){const n=[];let o=0,r=0;for(;o<t.length&&r<e.length;)t[o]<=e[r]?n.push(t[o++]):n.push(e[r++]);for(;o<t.length;)n.push(t[o++]);for(;r<e.length;)n.push(e[r++]);return n}function Pe(t){const e=[],n=new Map,o=[],r=[],s=[],i=new Set;t.forEach((t,c)=>{const l=function(t){const e=t[Jt];return void 0===e&&G(360),e}(t.match),u=t.name||function(t,e){if("text"===t.kind)return`#text@${e}`;if("comment"===t.kind)return`#comment@${e}`;if(0===t.tags.size)return`*@${e}`;const n=Array.from(t.tags).join(",").toLowerCase();return`${n}@${e}`}(l,c);if(t.name&&i.add(t.name),e.push({$import:t.$import,name:u,predicate:l.predicate}),"text"===l.kind)r.push(c);else if("comment"===l.kind)s.push(c);else if(0===l.tags.size)o.push(c);else for(const t of l.tags){let e=n.get(t);e||(e=[],n.set(t,e)),e.push(c)}});const c=new Map;if(0===o.length)for(const[t,e]of n)c.set(t,e);else for(const[t,e]of n)c.set(t,Fe(e,o));return{byTag:c,commentIndices:s,rules:e,textIndices:r,wildcardIndices:o}}function ze(t,e){return d(e)?t.textIndices:8===e.nodeType?t.commentIndices:r(e)?t.byTag.get(e.nodeName)||t.wildcardIndices:_e}const _e=Object.freeze([]);function Le(t){const e=[];for(const n of t)if(Ue(n))for(const t of n.rules)e.push(t);else e.push(n);return e}function Ue(t){return"object"==typeof t&&null!==t&&"__type"in t&&"CompiledOverlayRules"===t.__type}function Be(t){const e=Le(t);return{__type:"CompiledOverlayRules",dispatch:Pe(e),rules:e}}function Ve(t){return W(t)||R(t)&&!t.isInline()}function qe(t,e){const n=[];let o=[];const r=()=>{0!==o.length&&(n.push(e().splice(0,0,o)),o=[])};for(const s of t)if(Ve(s)){if(r(),E(s)){const t=qe(s.getChildren(),e);s.splice(0,s.getChildrenSize(),t)}n.push(s)}else o.push(s);return r(),n}function Ge(t,e,n){const o=v();if(r(n)){const t=n.style.textAlign;Oe(t)&&o.setFormat(t)}return[o.splice(0,0,t)]}const He={$accepts:Ve,$packageRun:Ge,name:"BlockSchema"},Je={$accepts:t=>!Ve(t),name:"InlineSchema"},Qe={$accepts:Ve,$packageRun:t=>t,name:"NestedBlockSchema"},Ke={$accepts:Ve,$packageRun:Ge,name:"RootSchema"},Ye=Object.freeze({});function Ze(t,e){const n={$importChildren:(e,n)=>function(t,e,n){const o=n&&n.rules?n.rules.dispatch:void 0;o&&t.overlays.push(o);try{const o=()=>Xe(t,e,n);return n&&n.context?be(n.context,t.editor)(o):o()}finally{o&&t.overlays.pop()}}(t,e,n),$importOne:(e,n)=>tn(t,e,n),captures:e,get:e=>ve(e,t.editor),session:t.session};return n}function Xe(t,e,n){const o=n&&n.$onChild,r=[];for(const n of Array.from(e.childNodes)){const e=tn(t,n,void 0);for(const t of e){const e=o?o(t):t;null!=e&&r.push(e)}}const s=n&&n.$after?n.$after(r):r,i=n&&n.schema;return i?function(t,e,n,o){const r=[];let s=null;const i=()=>{if(null===s)return;const e=s;if(s=null,t.$packageRun){const s=t.$packageRun(e,n,o);if(s.length>0){for(const t of s)r.push(t);return}}if("hoist"===t.onReject)for(const t of e)r.push(t)};for(const o of e)t.$accepts(o,n)?(i(),r.push(o)):(null===s&&(s=[]),s.push(o));return i(),t.$finalize?t.$finalize(r,n):r}(i,s,null,e):s}function tn(t,e,n){const o=()=>function(t,e){const n=function(t,e){const n=[];for(let o=t.overlays.length-1;o>=0;o--){const r=t.overlays[o],s=ze(r,e);s.length>0&&n.push({dispatch:r,indices:s})}const o=ze(t.dispatch,e);o.length>0&&n.push({dispatch:t.dispatch,indices:o});return n}(t,e);if(0===n.length)return en(t,e);let o=0,r=0;const s=()=>{for(;o<n.length;){const{dispatch:i,indices:c}=n[o];for(;r<c.length;){const n=c[r++],o=i.rules[n],l={};if(o.predicate(e,l)){const n=Ze(t,0===Object.keys(l).length?Ye:l);try{return o.$import(n,e,s)}catch(t){throw t}}}o++,r=0}return en(t,e)};return s()}(t,e);return n&&n.context?be(n.context,t.editor)(o):o()}function en(t,e){if(0===e.childNodes.length)return[];const n=[];for(const o of Array.from(e.childNodes)){const e=tn(t,o,void 0);for(const t of e)n.push(t)}return n}const nn={$import:(t,e)=>t.$importChildren(e),match:oe.any(),name:"@lexical/html/default-hoist"},on=f({build(t,e){const n=Pe(Le(e.rules)),r=Z(e.contextDefaults,void 0),s=e.preprocess;return{$generateNodesFromDOM:(e,i)=>{const c=i&&i.context?Z(i.context,r):r,l=void 0!==c&&c!==r?c:Object.create(r||null),u=new Se(l),f={session:u};return function(t,e,n){let o=t.length-1;const r=()=>{for(;o>=0;)return void(0,t[o--])(e,n,r)};r()}(i&&i.preprocess?[...s,...i.preprocess]:s,e,f),nt(ft,l,()=>function(t,e,n,r){return Xe({dispatch:t,editor:e,overlays:r.get(xe).map(t=>t.dispatch),session:r},o(n)?n.body:n,{schema:Ke})}(n,t,e,u),t)},defaults:r}},config:{contextDefaults:[],preprocess:[st],rules:[nn]},mergeConfig:(t,e)=>a(t,{...e,...e.contextDefaults&&{contextDefaults:[...t.contextDefaults,...e.contextDefaults]},...e.preprocess&&{preprocess:[...t.preprocess,...e.preprocess]},...e.rules&&{rules:[...e.rules,...t.rules]}}),name:ut});function rn(t,e){return B(on).$generateNodesFromDOM(t,e)}const sn=f({dependencies:[j(on,{rules:Te})],name:"@lexical/html/CoreImport"}),cn=[{$import:()=>[q()],match:oe.tag("hr"),name:"@lexical/html/hr"}],ln=f({dependencies:[sn,V,j(on,{rules:cn})],name:"@lexical/html/HorizontalRuleImport"}),un={any:oe.any,comment:oe.comment,css:le,tag:oe.tag,text:oe.text},fn=new Set(["STYLE","SCRIPT"]);function an(t,e){it(e);const n=o(e)?e.body.childNodes:e.childNodes,r=[],s=[];for(const e of n)if(!fn.has(e.nodeName)){const n=mn(e,t,s,!1);if(null!==n)for(const t of n)r.push(t)}return function(t){for(const e of t)e.getParent()&&e.getNextSibling()instanceof z&&e.insertAfter($());for(const e of t){const t=e.getParent();t&&t.splice(e.getIndexWithinParent(),1,e.getChildren())}}(s),r}function pn(t,n=null,o=e()){return vt([tt(ht,!0)],o)(()=>{const e=T(),r=$t(o),s=t.append.bind(t);for(const t of e.getChildren())gn(o,t,s,n,r);return t})}function dn(t,n=T()){const o=e();return vt([tt(ht,!0),tt(dt,!0)],o)(()=>{const e=$t(o),r=t.append.bind(t);return gn(o,n,r,null,e),t})}function hn(t,e=null){return("undefined"==typeof document||"undefined"==typeof window&&void 0===global.window)&&G(338),pn(document.createElement("div"),e,t).innerHTML}function gn(e,n,o,i=null,c=s(e)){let l=c.$shouldInclude(n,i,e);const u=c.$shouldExclude(n,i,e);let f=n;null!==i&&O(n)&&(f=t(i,n,"clone"));const a=c.$exportDOM(f,e),{element:p,after:d,append:h,$getChildNodes:g}=a;if(!p)return!1;const m=document.createDocumentFragment(),y=g?g():E(f)?f.getChildren():[],x=m.append.bind(m);for(const t of y){const o=gn(e,t,x,i,c);!l&&o&&c.$extractWithChild(n,t,i,"html",e)&&(l=!0)}if(l&&!u){if((r(p)||F(p))&&(h?h(m):p.append(m)),o(p),d){const t=d.call(f,p);t&&(F(p)?p.replaceChildren(t):p.replaceWith(t))}}else o(m);return l}function mn(t,e,n,o,r=new Map,s){const i=[];if(fn.has(t.nodeName))return i;let c=null;const l=function(t,e){const{nodeName:n}=t,o=e._htmlConversions.get(n.toLowerCase());let r=null;if(void 0!==o)for(const e of o){const n=e(t);null!==n&&(null===r||(r.priority||0)<=(n.priority||0))&&(r=n)}return null!==r?r.conversion:null}(t,e),u=l?l(t):null;let f=null;if(null!==u){f=u.after;const e=u.node;if(c=Array.isArray(e)?e[e.length-1]:e,null!==c){for(const[,t]of r)if(c=t(c,s),!c)break;c&&i.push(...Array.isArray(e)?e:[c])}null!=u.forChild&&r.set(t.nodeName,u.forChild)}const a=t.childNodes;let p=[];const d=(null==c||!P(c))&&(null!=c&&W(c)||o);for(let t=0;t<a.length;t++)p.push(...mn(a[t],e,n,d,new Map(r),c));if(null!=f&&(p=f(p)),h(t)&&(p=yn(t,p,d?()=>{const t=new z;return n.push(t),t}:v)),null==c)if(p.length>0)for(const t of p)i.push(t);else h(t)&&function(t){if(null==t.nextSibling||null==t.previousSibling)return!1;return g(t.nextSibling)&&g(t.previousSibling)}(t)&&i.push($());else E(c)&&c.append(...p);return i}function yn(t,e,n){const o=t.style.textAlign,r=[];let s=[];for(let t=0;t<e.length;t++){const i=e[t];if(W(i))o&&!i.getFormat()&&i.setFormat(o),r.push(i);else if(s.push(i),t===e.length-1||t<e.length-1&&W(e[t+1])){const t=n();t.setFormat(o),t.append(...s),r.push(t),s=[]}}return r}export{qe as $distributeInlineWrapper,pn as $generateDOMFromNodes,dn as $generateDOMFromRoot,hn as $generateHtmlFromNodes,an as $generateNodesFromDOM,rn as $generateNodesFromDOMViaExtension,ve as $getImportContextValue,mt as $getRenderContextValue,$t as $getSessionDOMRenderConfig,st as $inlineStylesFromStyleSheets,Ve as $isBlockLevel,xt as $setRenderContextValue,St as $updateRenderContextValue,be as $withImportContext,vt as $withRenderContext,He as BlockSchema,sn as CoreImportExtension,Te as CoreImportRules,on as DOMImportExtension,Ht as DOMRenderExtension,ln as HorizontalRuleImportExtension,cn as HorizontalRuleImportRules,xe as ImportOverlays,ae as ImportSource,pe as ImportSourceDataTransfer,de as ImportTextFormat,he as ImportTextStyle,ye as ImportWhitespaceConfig,Je as InlineSchema,Qe as NestedBlockSchema,ht as RenderContextExport,dt as RenderContextRoot,Ke as RootSchema,et as contextUpdater,tt as contextValue,fe as createImportState,pt as createRenderState,me as defaultIsInline,ge as defaultPreservesWhitespace,ue as defineImportRule,Be as defineOverlayRules,bt as domOverride,re as isElementOfTag,le as parseSelector,un as sel}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { EditorDOMRenderConfig } from 'lexical'; | ||
| import { LexicalEditor } from 'lexical'; | ||
| import { AnyRenderStateConfigPairOrUpdater, RenderStateConfig } from './types'; | ||
| /** | ||
| * Create a context state to be used during render. | ||
| * | ||
| * Note that to support the ValueOrUpdater pattern you can not use a | ||
| * function for V (but you may wrap it in an array or object). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function createRenderState<V>(name: string, getDefaultValue: () => V, isEqual?: (a: V, b: V) => boolean): RenderStateConfig<V>; | ||
| /** | ||
| * Render context state that is true if the export was initiated from the root of the document. | ||
| * @experimental | ||
| */ | ||
| export declare const RenderContextRoot: RenderStateConfig<boolean>; | ||
| /** | ||
| * Render context state that is true if this is an export operation ($generateHtmlFromNodes). | ||
| * @experimental | ||
| */ | ||
| export declare const RenderContextExport: RenderStateConfig<boolean>; | ||
| /** | ||
| * Get a render context value during a DOM render or export operation. | ||
| * @experimental | ||
| */ | ||
| export declare function $getRenderContextValue<V>(cfg: RenderStateConfig<V>, editor?: LexicalEditor): V; | ||
| /** | ||
| * Imperatively set a value in the persistent editor render context. | ||
| * | ||
| * Unlike {@link $withRenderContext} (which scopes values to a callback), this | ||
| * persists on the editor. If the change flips any override's | ||
| * `disabledForEditor` result, the resident render config is recompiled and the | ||
| * affected nodes are re-rendered. No-op if {@link DOMRenderExtension} is not | ||
| * installed. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function $setRenderContextValue<V>(cfg: RenderStateConfig<V>, value: V, editor?: LexicalEditor): void; | ||
| /** | ||
| * Imperatively update a value in the persistent editor render context with an | ||
| * updater function. See {@link $setRenderContextValue}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function $updateRenderContextValue<V>(cfg: RenderStateConfig<V>, updater: (prev: V) => V, editor?: LexicalEditor): void; | ||
| /** | ||
| * Resolve the {@link EditorDOMRenderConfig} to use for the current | ||
| * export/generate session, applying any `disabledForSession` overrides against | ||
| * the active session context. Falls back to the editor's resident config when | ||
| * {@link DOMRenderExtension} is not installed. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare function $getSessionDOMRenderConfig(editor?: LexicalEditor): EditorDOMRenderConfig; | ||
| /** | ||
| * Execute a callback within a render context with the given config pairs. | ||
| * @experimental | ||
| */ | ||
| export declare const $withRenderContext: (cfg: readonly AnyRenderStateConfigPairOrUpdater[], editor?: LexicalEditor) => <T>(f: () => T) => T; |
+381
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMImportContextSymbol, DOMRenderContextSymbol } from './constants'; | ||
| import type { BaseSelection, DOMExportOutput, DOMSlotForNode, EditorDOMRenderConfig, Klass, LexicalEditor, LexicalNode, StateConfig } from 'lexical'; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any ContextSymbol for {@link ContextConfig} (DOM render or DOM import). | ||
| */ | ||
| export type AnyContextSymbol = typeof DOMRenderContextSymbol | typeof DOMImportContextSymbol; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Context with a phantom type for its purpose (such as {@link DOMRenderContextSymbol}). | ||
| * | ||
| * A ContextRecord is a data structure used in the export and import pipelines | ||
| * to allow for information to be passed throughout the chain without explicit | ||
| * argument passing, e.g. to specify whether the intended use case for HTML | ||
| * export is for serialization or for clipboard copy. | ||
| */ | ||
| export type ContextRecord<_K extends symbol> = Record<string | symbol, unknown>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * A data structure much like StateConfig (they share implementation details) | ||
| * but for managing context during an export or import pipeline rather than | ||
| * individual node state. | ||
| */ | ||
| export type ContextConfig<Sym extends symbol, V> = StateConfig<symbol, V> & { | ||
| readonly [K in Sym]?: true; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Update the context at `cfg` with updater, constructed with {@link contextUpdater} | ||
| */ | ||
| export type ContextConfigUpdater<Ctx extends AnyContextSymbol, V> = { | ||
| readonly cfg: ContextConfig<Ctx, V>; | ||
| /** | ||
| * @param prev The current or default value | ||
| * @returns The new value | ||
| */ | ||
| readonly updater: (prev: V) => V; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Set the the context at `cfg` to a specific value, constructed with {@link contextValue} | ||
| */ | ||
| export type ContextConfigPair<Ctx extends AnyContextSymbol, V> = readonly [ | ||
| ContextConfig<Ctx, V>, | ||
| V | ||
| ]; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Set or update a context value, constructed with {@link contextValue} or {@link contextUpdater} | ||
| */ | ||
| export type ContextPairOrUpdater<Ctx extends AnyContextSymbol, V> = ContextConfigPair<Ctx, V> | ContextConfigUpdater<Ctx, V>; | ||
| /** @experimental */ | ||
| export type AnyContextConfigPairOrUpdater<Ctx extends AnyContextSymbol> = ContextPairOrUpdater<Ctx, any>; | ||
| /** @experimental */ | ||
| export interface DOMRenderExtensionOutput { | ||
| /** @internal */ | ||
| defaults: undefined | ContextRecord<typeof DOMRenderContextSymbol>; | ||
| /** @internal */ | ||
| runtime: DOMRenderRuntime; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * A read-only view of a render context layer, passed to the | ||
| * {@link DOMOverrideOptions} predicates so they can decide whether an | ||
| * override should be installed based only on context values. | ||
| */ | ||
| export interface RenderContextReader { | ||
| get<V>(cfg: RenderStateConfig<V>): V; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * | ||
| * Per-editor runtime state for {@link DOMRenderExtension} that backs the | ||
| * imperative editor context ({@link createRenderState} writes via | ||
| * `$setRenderContextValue`) and the conditional install of overrides. | ||
| */ | ||
| export interface DOMRenderRuntime { | ||
| /** | ||
| * The mutable, persistent editor-level context record. Reads of a | ||
| * {@link RenderStateConfig} during reconciliation (and as the base layer | ||
| * during a session) fall through to this record. It is also the layer | ||
| * that {@link DOMOverrideOptions.disabledForEditor} predicates read from. | ||
| */ | ||
| readonly editorContext: ContextRecord<typeof DOMRenderContextSymbol>; | ||
| /** | ||
| * Imperatively set a value in the editor context. If the change flips any | ||
| * override's `disabledForEditor` result, the resident render config is | ||
| * recompiled and the affected nodes are re-rendered (recreating DOM for | ||
| * structural overrides). | ||
| */ | ||
| setContextValue<V>(cfg: RenderStateConfig<V>, value: V): void; | ||
| /** | ||
| * Resolve the {@link EditorDOMRenderConfig} for the current export/generate | ||
| * session, applying any {@link DOMOverrideOptions.disabledForSession} | ||
| * predicates against the active session context. Returns the resident | ||
| * config when no session gating applies. | ||
| */ | ||
| getSessionConfig(): EditorDOMRenderConfig; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Options for {@link domOverride} controlling *whether* an override is | ||
| * installed, based only on render context. Both predicates default to | ||
| * "not disabled". | ||
| */ | ||
| export interface DOMOverrideOptions { | ||
| /** | ||
| * Gate residency in the editor's render config (used by reconciliation and | ||
| * as the base for export/generate). Evaluated against the persistent editor | ||
| * context at compile time, and re-evaluated when that context changes via | ||
| * `$setRenderContextValue`; a change recompiles the config and re-renders | ||
| * affected nodes. Return `true` to remove the override. Default: not disabled. | ||
| */ | ||
| disabledForEditor?: (ctx: RenderContextReader) => boolean; | ||
| /** | ||
| * Gate participation in a single export/generate session. Evaluated once at | ||
| * the start of each session against that session's context. Has no effect on | ||
| * live reconciliation (which is not a session). Return `true` to remove the | ||
| * override for that session. Default: not disabled. | ||
| */ | ||
| disabledForSession?: (ctx: RenderContextReader) => boolean; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Context configuration for render context, created with {@link createRenderState} | ||
| */ | ||
| export type RenderStateConfig<V> = ContextConfig<typeof DOMRenderContextSymbol, V>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any setter or updater for {@link RenderStateConfig} | ||
| */ | ||
| export type AnyRenderStateConfigPairOrUpdater = AnyContextConfigPairOrUpdater<typeof DOMRenderContextSymbol>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any {@link RenderStateConfig} | ||
| */ | ||
| export type AnyRenderStateConfig = RenderStateConfig<any>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Configuration for {@link DOMRenderExtension} | ||
| */ | ||
| export interface DOMRenderConfig { | ||
| /** | ||
| * {@link DOMRenderMatch} overrides to customize node behavior, | ||
| * the final priority of these will be based on the following criteria: | ||
| * | ||
| * - Wildcards (`'*'`) have highest priority | ||
| * - Predicates (`$isParagraphNode`) have next priority | ||
| * - Subclasses have higher priority (e.g. `ParagraphNode` before `ElementNode`) | ||
| * - Extensions closer to the root have higher priority | ||
| * - Extensions depended on later have higher priority | ||
| * - Overrides defined later have higher priority | ||
| */ | ||
| overrides: AnyDOMRenderMatch[]; | ||
| /** | ||
| * Default context to provide in all exports, the configurations are created | ||
| * with {@link createRenderState} and should be created at the module-level. | ||
| * | ||
| * Only specify these if overriding the default value globally, since each | ||
| * configuration has a built-in default value that will be used if not | ||
| * already present in the context. | ||
| */ | ||
| contextDefaults: AnyRenderStateConfigPairOrUpdater[]; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * Any {@link DOMRenderMatch} | ||
| */ | ||
| export type AnyDOMRenderMatch = DOMRenderMatch<any>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Match a node (and any subclass of that node) by its LexicalNode class, | ||
| * or with a guard (e.g. `ElementNode` or `$isElementNode`). | ||
| * | ||
| * Note that using the class compiles to significantly more efficient code | ||
| * than using a guard. | ||
| */ | ||
| export type NodeMatch<T extends LexicalNode> = Klass<T> | ((node: LexicalNode) => node is T); | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Used to define overrides for the render and export | ||
| * behavior for nodes matching the `nodes` predicate. | ||
| * | ||
| * All of these overrides are in a middleware style where you may use the | ||
| * result of `$next()` to enhance the result of the default implementation | ||
| * (or a lower priority override) by calling it and manipulating the result, | ||
| * or you may choose not to call `$next()` to entirely replace the behavior. | ||
| * | ||
| * It is not permitted to update the lexical editor state during any of | ||
| * these calls, you should only be doing read-only operations. | ||
| */ | ||
| export interface DOMRenderMatch<T extends LexicalNode> { | ||
| /** | ||
| * '*' for all nodes, or an array of `NodeClass | $isNodeGuard` to match | ||
| * nodes more specifically. Using classes is more efficient, but will | ||
| * also target subclasses. | ||
| */ | ||
| readonly nodes: '*' | readonly NodeMatch<T>[]; | ||
| /** | ||
| * Control where an ElementNode's children are inserted into the DOM, | ||
| * this is useful to add a wrapping node or accessory nodes before or | ||
| * after the children. The root of the node returned by createDOM must | ||
| * still be exactly one HTMLElement. | ||
| * | ||
| * Generally you will call `$next()` to get a slot and then use its methods | ||
| * to create a new one. The slot type is narrowed via {@link DOMSlotForNode}: | ||
| * for `ElementNode` it resolves to {@link ElementDOMSlot} with | ||
| * children-management semantics; for non-Element nodes the base | ||
| * {@link DOMSlot} pointing at the keyed DOM. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param dom The rendered HTMLElement | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns The slot for this node | ||
| */ | ||
| $getDOMSlot?: (node: T, dom: HTMLElement, $next: () => DOMSlotForNode<T>, editor: LexicalEditor) => DOMSlotForNode<T>; | ||
| /** | ||
| * Called during the reconciliation process to determine which nodes | ||
| * to insert into the DOM for this Lexical Node. This is also the default | ||
| * implementation of `$exportDOM` for most nodes. | ||
| * | ||
| * This method must return exactly one `HTMLElement`. | ||
| * | ||
| * Nested elements are not supported except with `DecoratorNode` | ||
| * (which have unmanaged contents) or `ElementNode` using an appropriate | ||
| * `$getDOMSlot` return value. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns The HTMLElement for this node to be rendered in the editor | ||
| */ | ||
| $createDOM?: (node: T, $next: () => HTMLElement, editor: LexicalEditor) => HTMLElement; | ||
| /** | ||
| * Called when a node changes and should update the DOM | ||
| * in whatever way is necessary to make it align with any changes that might | ||
| * have happened during the update. | ||
| * | ||
| * Returning `true` here will cause lexical to unmount and recreate the DOM | ||
| * node (by calling `$createDOM`). You would need to do this if the element | ||
| * tag changes, for instance. | ||
| * | ||
| * @param nextNode The current version of this node | ||
| * @param prevNode The previous version of this node | ||
| * @param dom The previously rendered HTMLElement for this node | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns `false` if no update needed or was performed in-place, `true` if `$createDOM` should be called to re-create the node | ||
| */ | ||
| $updateDOM?: (nextNode: T, prevNode: T, dom: HTMLElement, $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Called after a node is created or updated and should make any in-place | ||
| * updates to the DOM in whatever way is necessary to make it align with | ||
| * any changes that might have happened during the `$createDOM` or | ||
| * `$updateDOM`. This also runs after any children have been reconciled. | ||
| * | ||
| * Use this when you have code that you would need to duplicate in both | ||
| * methods, or if there is a need to ensure that the children are also | ||
| * reconciled before performing this in-place update. | ||
| * | ||
| * Unlike other overrides, all applicable `$decorateDOM` functions are | ||
| * called unconditionally. There is no `$next` argument, because there | ||
| * are no known use cases for avoiding the next implementation and due | ||
| * to the void return value it would be error-prone and add boilerplate | ||
| * to require calling it. | ||
| * | ||
| * The ordering here is equivalent to an implicit `$next` call *first*. | ||
| * | ||
| * @param nextNode The current version of this node | ||
| * @param prevNode The previous version of this node if `$updateDOM` returned `false`, or `null` if `$createDOM` was just called | ||
| * @param dom The previously rendered `HTMLElement` for this node | ||
| * @param editor The editor | ||
| */ | ||
| $decorateDOM?: (nextNode: T, prevNode: null | T, dom: HTMLElement, editor: LexicalEditor) => void; | ||
| /** | ||
| * Controls how the this node is serialized to HTML. This is important for | ||
| * copy and paste between Lexical and non-Lexical editors, or Lexical | ||
| * editors with different namespaces, in which case the primary transfer | ||
| * format is HTML. It's also important if you're serializing to HTML for | ||
| * any other reason via {@link @lexical/html!$generateHtmlFromNodes}. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns A {@link DOMExportOutput} structure that defines how the node should be exported to HTML | ||
| */ | ||
| $exportDOM?: (node: T, $next: () => DOMExportOutput, editor: LexicalEditor) => DOMExportOutput; | ||
| /** | ||
| * Equivalent to `ElementNode.excludeFromCopy`, if it returns `true` this | ||
| * lexical node will not be exported to DOM (but if it's an `ElementNode` | ||
| * its children may still be inserted in its place). | ||
| * | ||
| * Has higher precedence than `$shouldInclude` and `$extractWithChild`. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param selection The current selection | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns true to exclude this node, false otherwise | ||
| */ | ||
| $shouldExclude?: (node: T, selection: null | BaseSelection, $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Return `true` if this node should be included in the export, typically based | ||
| * on the current selection (all nodes by default are included when there | ||
| * is no selection). | ||
| * | ||
| * The default implementation is equivalent to | ||
| * `selection ? node.isSelected(selection) : true`. | ||
| * | ||
| * This has lower precedence than `$extractWithChild` and `$shouldExclude`. | ||
| * | ||
| * @param node The current node | ||
| * @param selection The current selection | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns `true` if this node should be included in the export, `false` otherwise | ||
| */ | ||
| $shouldInclude?: (node: T, selection: null | BaseSelection, $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Return `true` if this node should be included in the export based on | ||
| * `childNode`, even if it would not otherwise be included based on its | ||
| * `$shouldInclude` result. | ||
| * | ||
| * Typically used to ensure that required wrapping nodes are always | ||
| * present with its children, e.g. a ListNode when some of its ListItemNode | ||
| * children are selected. | ||
| * | ||
| * This has higher precedence than `$extractWithChild` and lower precedence | ||
| * than `$shouldExclude`. | ||
| * | ||
| * @param node The lexical node | ||
| * @param childNode A child of this lexical node | ||
| * @param selection The current selection | ||
| * @param destination Currently always `'html'` | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns true if this | ||
| */ | ||
| $extractWithChild?: (node: T, childNode: LexicalNode, selection: null | BaseSelection, destination: 'clone' | 'html', $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Set via {@link domOverride}'s options argument, not directly. See | ||
| * {@link DOMOverrideOptions.disabledForEditor}. | ||
| */ | ||
| disabledForEditor?: (ctx: RenderContextReader) => boolean; | ||
| /** | ||
| * Set via {@link domOverride}'s options argument, not directly. See | ||
| * {@link DOMOverrideOptions.disabledForSession}. | ||
| */ | ||
| disabledForSession?: (ctx: RenderContextReader) => boolean; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The hook fields of a {@link DOMRenderMatch} — i.e. without `nodes` or the | ||
| * {@link DOMOverrideOptions} predicates, which are passed separately to | ||
| * {@link domOverride}. | ||
| */ | ||
| export type DOMRenderMatchConfig<T extends LexicalNode> = Omit<DOMRenderMatch<T>, 'nodes' | keyof DOMOverrideOptions>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import {getKnownTypesAndNodes} from '@lexical/extension'; | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import { | ||
| $isLexicalNode, | ||
| DEFAULT_EDITOR_DOM_CONFIG, | ||
| type EditorDOMRenderConfig, | ||
| getStaticNodeConfig, | ||
| InitialEditorConfig, | ||
| Klass, | ||
| LexicalEditor, | ||
| type LexicalNode, | ||
| } from 'lexical'; | ||
| import {ALWAYS_TRUE} from './constants'; | ||
| import {AnyDOMRenderMatch, DOMRenderConfig, DOMRenderMatch} from './types'; | ||
| interface TypeRecord { | ||
| readonly klass: Klass<LexicalNode>; | ||
| readonly types: {[NodeAndSubclasses in string]?: boolean}; | ||
| } | ||
| type TypeTree = { | ||
| [NodeType in string]?: TypeRecord; | ||
| }; | ||
| export function buildTypeTree( | ||
| editorConfig: Pick<InitialEditorConfig, 'nodes'>, | ||
| ): TypeTree { | ||
| const t: TypeTree = {}; | ||
| const {nodes} = getKnownTypesAndNodes(editorConfig); | ||
| for (const klass of nodes) { | ||
| const type = klass.getType(); | ||
| t[type] = {klass, types: {}}; | ||
| } | ||
| for (const baseRec of Object.values(t)) { | ||
| if (baseRec) { | ||
| const baseType = baseRec.klass.getType(); | ||
| for ( | ||
| let {klass} = baseRec; | ||
| $isLexicalNode(klass.prototype); | ||
| klass = Object.getPrototypeOf(klass) | ||
| ) { | ||
| const {ownNodeType} = getStaticNodeConfig(klass); | ||
| const superRec = ownNodeType && t[ownNodeType]; | ||
| if (superRec) { | ||
| superRec.types[baseType] = true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return t; | ||
| } | ||
| type PredicateOrTypes = | ||
| | ((node: LexicalNode) => boolean) | ||
| | {[NodeType in string]?: true}; | ||
| type TypeRender<T> = {[NodeType in string]?: T[]}; | ||
| type AnyRender<T> = | ||
| | readonly [(node: LexicalNode) => boolean, T] | ||
| | readonly ['types', TypeRender<T>]; | ||
| type PreEditorDOMRenderConfig = { | ||
| [K in keyof EditorDOMRenderConfig]: AnyRender<AnyDOMRenderMatch[K]>[]; | ||
| }; | ||
| function buildNodePredicate<T extends LexicalNode>(klass: Klass<T>) { | ||
| return (node: LexicalNode): node is T => node instanceof klass; | ||
| } | ||
| function getPredicate( | ||
| typeTree: TypeTree, | ||
| {nodes}: DOMRenderMatch<LexicalNode>, | ||
| ): {[NodeType in string]?: true} | ((node: LexicalNode) => boolean) { | ||
| if (nodes === '*') { | ||
| return ALWAYS_TRUE; | ||
| } | ||
| let types: undefined | {[NodeType in string]?: true} = {}; | ||
| const predicates: ((node: LexicalNode) => boolean)[] = []; | ||
| for (const klassOrPredicate of nodes) { | ||
| if ('getType' in klassOrPredicate) { | ||
| const type = klassOrPredicate.getType(); | ||
| if (types) { | ||
| const tree = typeTree[type]; | ||
| invariant( | ||
| tree !== undefined, | ||
| 'Node class %s with type %s not registered in editor', | ||
| klassOrPredicate.name, | ||
| type, | ||
| ); | ||
| types = Object.assign(types, tree.types); | ||
| } | ||
| predicates.push(buildNodePredicate(klassOrPredicate)); | ||
| } else { | ||
| types = undefined; | ||
| predicates.push(klassOrPredicate); | ||
| } | ||
| } | ||
| if (types) { | ||
| return types; | ||
| } else if (predicates.length === 1) { | ||
| return predicates[0]; | ||
| } | ||
| return (node: LexicalNode): boolean => { | ||
| for (const predicate of predicates) { | ||
| if (predicate(node)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| } | ||
| function makePrerender(): PreEditorDOMRenderConfig { | ||
| return { | ||
| $createDOM: [], | ||
| $decorateDOM: [], | ||
| $exportDOM: [], | ||
| $extractWithChild: [], | ||
| $getDOMSlot: [], | ||
| $shouldExclude: [], | ||
| $shouldInclude: [], | ||
| $updateDOM: [], | ||
| }; | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type AccFn<T, N extends LexicalNode, Args extends any[]> = ( | ||
| node: N, | ||
| ...rest: [...Args, editor: LexicalEditor] | ||
| ) => T; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type GetOverrideFn<T, N extends LexicalNode, Args extends any[]> = ( | ||
| n: N, | ||
| ) => undefined | OverrideFn<T, N, Args>; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type OverrideFn<T, N extends LexicalNode, Args extends any[]> = ( | ||
| node: N, | ||
| ...rest: [...Args, $next: () => T, editor: LexicalEditor] | ||
| ) => T; | ||
| function ignoreNext2<T, N extends LexicalNode>( | ||
| acc: AccFn<T, N, []>, | ||
| ): OverrideFn<T, N, []> { | ||
| return (node: N, _$next: () => T, editor: LexicalEditor) => acc(node, editor); | ||
| } | ||
| function ignoreNext3<T, N extends LexicalNode, A>( | ||
| acc: AccFn<T, N, [A]>, | ||
| ): OverrideFn<T, N, [A]> { | ||
| return (node: N, a: A, _$next: () => T, editor: LexicalEditor) => | ||
| acc(node, a, editor); | ||
| } | ||
| function ignoreNext4<T, N extends LexicalNode, A, B>( | ||
| acc: AccFn<T, N, [A, B]>, | ||
| ): OverrideFn<T, N, [A, B]> { | ||
| return (node: N, a: A, b: B, _$next: () => T, editor: LexicalEditor) => | ||
| acc(node, a, b, editor); | ||
| } | ||
| function ignoreNext5<T, N extends LexicalNode, A, B, C>( | ||
| acc: AccFn<T, N, [A, B, C]>, | ||
| ): OverrideFn<T, N, [A, B, C]> { | ||
| return (node: N, a: A, b: B, c: C, _$next: () => T, editor: LexicalEditor) => | ||
| acc(node, a, b, c, editor); | ||
| } | ||
| function merge2<T, N extends LexicalNode>( | ||
| $acc: AccFn<T, N, []>, | ||
| $getOverride: GetOverrideFn<T, N, []>, | ||
| ): typeof $acc { | ||
| return (node, editor) => { | ||
| const $next = () => $acc(node, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge3<T, N extends LexicalNode, A>( | ||
| acc: AccFn<T, N, [A]>, | ||
| $getOverride: GetOverrideFn<T, N, [A]>, | ||
| ): typeof acc { | ||
| return (node, a, editor) => { | ||
| const $next = () => acc(node, a, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| const merge3GetDOMSlot = merge3 as ( | ||
| acc: EditorDOMRenderConfig['$getDOMSlot'], | ||
| $getOverride: (n: LexicalNode) => DOMRenderMatch<LexicalNode>['$getDOMSlot'], | ||
| ) => EditorDOMRenderConfig['$getDOMSlot']; | ||
| const ignoreNext3GetDOMSlot = ignoreNext3 as ( | ||
| fn: EditorDOMRenderConfig['$getDOMSlot'], | ||
| ) => DOMRenderMatch<LexicalNode>['$getDOMSlot']; | ||
| function merge4<T, N extends LexicalNode, A, B>( | ||
| $acc: AccFn<T, N, [A, B]>, | ||
| $getOverride: GetOverrideFn<T, N, [A, B]>, | ||
| ): typeof $acc { | ||
| return (node, a, b, editor) => { | ||
| const $next = () => $acc(node, a, b, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, b, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge5<T, N extends LexicalNode, A, B, C>( | ||
| acc: AccFn<T, N, [A, B, C]>, | ||
| $getOverride: GetOverrideFn<T, N, [A, B, C]>, | ||
| ): typeof acc { | ||
| return (node, a, b, c, editor) => { | ||
| const $next = () => acc(node, a, b, c, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, b, c, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function sequence4<N extends LexicalNode, A, B>( | ||
| $acc: AccFn<void, N, [A, B]>, | ||
| $getOverride: (n: N) => undefined | AccFn<void, N, [A, B]>, | ||
| ): typeof $acc { | ||
| return (node, a, b, editor) => { | ||
| $acc(node, a, b, editor); | ||
| const $override = $getOverride(node); | ||
| if ($override) { | ||
| $override(node, a, b, editor); | ||
| } | ||
| }; | ||
| } | ||
| function compilePrerenderKey<K extends keyof PreEditorDOMRenderConfig>( | ||
| prerender: PreEditorDOMRenderConfig, | ||
| k: K, | ||
| defaults: EditorDOMRenderConfig, | ||
| mergeFunction: ( | ||
| $acc: EditorDOMRenderConfig[K], | ||
| $getOverride: (node: LexicalNode) => AnyDOMRenderMatch[K], | ||
| ) => typeof $acc, | ||
| ignoreNextFunction: (fn: EditorDOMRenderConfig[K]) => AnyDOMRenderMatch[K], | ||
| ): void { | ||
| let acc = defaults[k]; | ||
| for (const pair of prerender[k]) { | ||
| if (typeof pair[0] === 'function') { | ||
| const [$predicate, $override] = pair; | ||
| acc = mergeFunction( | ||
| acc, | ||
| node => ($predicate(node) && $override) || undefined, | ||
| ); | ||
| } else { | ||
| const typeOverrides = pair[1]; | ||
| const compiled: Record<string, undefined | EditorDOMRenderConfig[K]> = {}; | ||
| for (const type in typeOverrides) { | ||
| const arr = typeOverrides[type]; | ||
| if (arr) { | ||
| compiled[type] = arr.reduce( | ||
| ($acc, $override) => mergeFunction($acc, () => $override), | ||
| acc, | ||
| ); | ||
| } | ||
| } | ||
| acc = mergeFunction(acc, node => { | ||
| const f = compiled[node.getType()]; | ||
| return f && ignoreNextFunction(f); | ||
| }); | ||
| } | ||
| } | ||
| defaults[k] = acc; | ||
| } | ||
| function addOverride<K extends keyof PreEditorDOMRenderConfig>( | ||
| prerender: PreEditorDOMRenderConfig, | ||
| k: K, | ||
| predicateOrTypes: PredicateOrTypes, | ||
| override: AnyDOMRenderMatch[K], | ||
| ): void { | ||
| if (!override) { | ||
| return; | ||
| } | ||
| const arr = prerender[k]; | ||
| if (typeof predicateOrTypes === 'function') { | ||
| arr.push([predicateOrTypes, override]); | ||
| } else { | ||
| const last = arr[arr.length - 1]; | ||
| let types: TypeRender<AnyDOMRenderMatch[K]>; | ||
| if (last && last[0] === 'types') { | ||
| types = last[1]; | ||
| } else { | ||
| types = {}; | ||
| arr.push(['types', types]); | ||
| } | ||
| for (const type in predicateOrTypes) { | ||
| const typeArr = types[type] || []; | ||
| types[type] = typeArr; | ||
| typeArr.push(override); | ||
| } | ||
| } | ||
| } | ||
| type NormalizedDOMRenderMatch<T> = Omit< | ||
| DOMRenderMatch<LexicalNode>, | ||
| 'nodes' | ||
| > & { | ||
| nodes: T; | ||
| }; | ||
| function isWildcard( | ||
| override: DOMRenderMatch<LexicalNode>, | ||
| ): override is NormalizedDOMRenderMatch<'*'> { | ||
| return override.nodes === '*'; | ||
| } | ||
| function sortedOverrides( | ||
| overrides: DOMRenderMatch<LexicalNode>[], | ||
| ): DOMRenderMatch<LexicalNode>[] { | ||
| const byWildcard: NormalizedDOMRenderMatch<'*'>[] = []; | ||
| const byPredicate: NormalizedDOMRenderMatch< | ||
| [(node: LexicalNode) => node is LexicalNode] | ||
| >[] = []; | ||
| const byNode: NormalizedDOMRenderMatch<[Klass<LexicalNode>]>[] = []; | ||
| for (const override of overrides) { | ||
| if (isWildcard(override)) { | ||
| byWildcard.push(override); | ||
| } else if (Array.isArray(override.nodes)) { | ||
| for (const klassOrPredicate of override.nodes) { | ||
| if ($isLexicalNode(klassOrPredicate.prototype)) { | ||
| byNode.push( | ||
| override.nodes.length === 1 | ||
| ? (override as NormalizedDOMRenderMatch<[Klass<LexicalNode>]>) | ||
| : {...override, nodes: [klassOrPredicate]}, | ||
| ); | ||
| } else { | ||
| byPredicate.push( | ||
| override.nodes.length === 1 | ||
| ? (override as NormalizedDOMRenderMatch< | ||
| [(node: LexicalNode) => node is LexicalNode] | ||
| >) | ||
| : {...override, nodes: [klassOrPredicate]}, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const depths = new Map<Klass<LexicalNode>, number>(); | ||
| const depthOf = (klass: Klass<LexicalNode>): number => { | ||
| let depth = depths.get(klass); | ||
| if (depth === undefined) { | ||
| depth = 0; | ||
| for ( | ||
| let k: Klass<LexicalNode> = klass; | ||
| $isLexicalNode(k.prototype); | ||
| k = Object.getPrototypeOf(k) | ||
| ) { | ||
| depth++; | ||
| } | ||
| depths.set(klass, depth); | ||
| } | ||
| return depth; | ||
| }; | ||
| byNode.sort((a, b) => depthOf(a.nodes[0]) - depthOf(b.nodes[0])); | ||
| return [...byNode, ...byPredicate, ...byWildcard]; | ||
| } | ||
| export function precompileDOMRenderConfigOverrides( | ||
| editorConfig: Pick<InitialEditorConfig, 'nodes'>, | ||
| overrides: DOMRenderConfig['overrides'], | ||
| ): PreEditorDOMRenderConfig { | ||
| const typeTree = buildTypeTree(editorConfig); | ||
| const prerender = makePrerender(); | ||
| for (const override of sortedOverrides(overrides)) { | ||
| const predicateOrTypes = getPredicate(typeTree, override); | ||
| for (const k_ in prerender) { | ||
| const k = k_ as keyof typeof prerender; | ||
| addOverride(prerender, k, predicateOrTypes, override[k]); | ||
| } | ||
| } | ||
| return prerender; | ||
| } | ||
| function identity<T>(v: T) { | ||
| return v; | ||
| } | ||
| export function compileDOMRenderConfigOverrides( | ||
| editorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>, | ||
| {overrides}: Pick<DOMRenderConfig, 'overrides'>, | ||
| ): EditorDOMRenderConfig { | ||
| const prerender = precompileDOMRenderConfigOverrides(editorConfig, overrides); | ||
| const dom = { | ||
| ...DEFAULT_EDITOR_DOM_CONFIG, | ||
| ...editorConfig.dom, | ||
| }; | ||
| compilePrerenderKey(prerender, '$createDOM', dom, merge2, ignoreNext2); | ||
| compilePrerenderKey(prerender, '$exportDOM', dom, merge2, ignoreNext2); | ||
| compilePrerenderKey(prerender, '$extractWithChild', dom, merge5, ignoreNext5); | ||
| compilePrerenderKey( | ||
| prerender, | ||
| '$getDOMSlot', | ||
| dom, | ||
| merge3GetDOMSlot, | ||
| ignoreNext3GetDOMSlot, | ||
| ); | ||
| compilePrerenderKey(prerender, '$shouldExclude', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$shouldInclude', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$updateDOM', dom, merge4, ignoreNext4); | ||
| compilePrerenderKey(prerender, '$decorateDOM', dom, sequence4, identity); | ||
| return dom; | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| export const DOMRenderExtensionName = '@lexical/html/DOM'; | ||
| export const DOMRenderContextSymbol = Symbol.for( | ||
| '@lexical/html/DOMExportContext', | ||
| ); | ||
| export const DOMImportExtensionName = '@lexical/html/DOMImport'; | ||
| export const DOMImportContextSymbol = Symbol.for( | ||
| '@lexical/html/DOMImportContext', | ||
| ); | ||
| export const ALWAYS_TRUE = () => true as const; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { | ||
| AnyContextConfigPairOrUpdater, | ||
| AnyContextSymbol, | ||
| ContextConfig, | ||
| ContextConfigPair, | ||
| ContextConfigUpdater, | ||
| ContextRecord, | ||
| } from './types'; | ||
| import {$getEditor, createState, type LexicalEditor} from 'lexical'; | ||
| let activeContext: undefined | EditorContext; | ||
| type WithContext<Ctx extends AnyContextSymbol> = { | ||
| [K in Ctx]?: undefined | ContextRecord<Ctx>; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The LexicalEditor with context | ||
| */ | ||
| export type EditorContext = { | ||
| editor: LexicalEditor; | ||
| } & WithContext<AnyContextSymbol>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value or defaultValue of cfg | ||
| */ | ||
| export function getContextValue<Ctx extends AnyContextSymbol, V>( | ||
| contextRecord: undefined | ContextRecord<Ctx>, | ||
| cfg: ContextConfig<Ctx, V>, | ||
| ): V { | ||
| const {key} = cfg; | ||
| return contextRecord && key in contextRecord | ||
| ? (contextRecord[key] as V) | ||
| : cfg.defaultValue; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Read and delete cfg from this layer of context | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value of the configuration that was removed | ||
| */ | ||
| export function popOwnContextValue<Ctx extends AnyContextSymbol, V>( | ||
| contextRecord: ContextRecord<Ctx>, | ||
| cfg: ContextConfig<Ctx, V>, | ||
| ): undefined | V { | ||
| const rval = getOwnContextValue(contextRecord, cfg); | ||
| delete contextRecord[cfg.key]; | ||
| return rval; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Get the value without a default | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The current value in this context or `undefined` if not set | ||
| */ | ||
| export function getOwnContextValue<Ctx extends AnyContextSymbol, V>( | ||
| contextRecord: ContextRecord<Ctx>, | ||
| cfg: ContextConfig<Ctx, V>, | ||
| ): undefined | V { | ||
| const {key} = cfg; | ||
| return key in contextRecord ? (contextRecord[key] as V) : undefined; | ||
| } | ||
| function getEditorContext(editor: LexicalEditor): undefined | EditorContext { | ||
| return activeContext && activeContext.editor === editor | ||
| ? activeContext | ||
| : undefined; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol) | ||
| * @param editor The editor | ||
| * @returns The current context or undefined | ||
| */ | ||
| export function getContextRecord<Ctx extends AnyContextSymbol>( | ||
| sym: Ctx, | ||
| editor: LexicalEditor, | ||
| ): undefined | ContextRecord<Ctx> { | ||
| const editorContext = getEditorContext(editor); | ||
| return editorContext && editorContext[sym]; | ||
| } | ||
| function toPair<Ctx extends AnyContextSymbol, V>( | ||
| contextRecord: undefined | ContextRecord<Ctx>, | ||
| pairOrUpdater: ContextConfigPair<Ctx, V> | ContextConfigUpdater<Ctx, V>, | ||
| ): ContextConfigPair<Ctx, V> { | ||
| if ('cfg' in pairOrUpdater) { | ||
| const {cfg, updater} = pairOrUpdater; | ||
| return [cfg, updater(getContextValue(contextRecord, cfg))]; | ||
| } | ||
| return pairOrUpdater; | ||
| } | ||
| /** | ||
| * Construct a new context from a parent context and pairs | ||
| * | ||
| * @param pairs The pairs and updaters to build the context from | ||
| * @param parent The parent context | ||
| * @returns The new context | ||
| */ | ||
| export function contextFromPairs<Ctx extends AnyContextSymbol>( | ||
| pairs: readonly AnyContextConfigPairOrUpdater<Ctx>[], | ||
| parent: undefined | ContextRecord<Ctx>, | ||
| ): undefined | ContextRecord<Ctx> { | ||
| let rval = parent; | ||
| for (const pairOrUpdater of pairs) { | ||
| const [k, v] = toPair(rval, pairOrUpdater); | ||
| const key = k.key; | ||
| if (rval === parent && getContextValue(rval, k) === v) { | ||
| continue; | ||
| } | ||
| // If we haven't branched away from `parent` yet, create a fresh child | ||
| // context so we never mutate the caller's parent record. Subsequent | ||
| // pairs in this loop accumulate into the same child. Inside the loop | ||
| // `rval` is non-null after the first iteration, since createChildContext | ||
| // never returns null/undefined. | ||
| const ctx: ContextRecord<Ctx> = | ||
| rval === parent || rval === undefined ? createChildContext(parent) : rval; | ||
| ctx[key] = v; | ||
| rval = ctx; | ||
| } | ||
| return rval; | ||
| } | ||
| function createChildContext<Ctx extends AnyContextSymbol>( | ||
| parent: undefined | ContextRecord<Ctx>, | ||
| ): ContextRecord<Ctx> { | ||
| return Object.create(parent || null); | ||
| } | ||
| /** | ||
| * Create a context config pair that sets a value in the render context. | ||
| * @experimental | ||
| */ | ||
| export function contextValue<Ctx extends AnyContextSymbol, V>( | ||
| cfg: ContextConfig<Ctx, V>, | ||
| value: V, | ||
| ): ContextConfigPair<Ctx, V> { | ||
| return [cfg, value]; | ||
| } | ||
| /** | ||
| * Create a context config updater that transforms a value in the render context. | ||
| * @experimental | ||
| */ | ||
| export function contextUpdater<Ctx extends AnyContextSymbol, V>( | ||
| cfg: ContextConfig<Ctx, V>, | ||
| updater: (prev: V) => V, | ||
| ): ContextConfigUpdater<Ctx, V> { | ||
| return {cfg, updater}; | ||
| } | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function $withFullContext<Ctx extends AnyContextSymbol, T>( | ||
| sym: Ctx, | ||
| contextRecord: ContextRecord<Ctx>, | ||
| f: () => T, | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): T { | ||
| const prevDOMContext = activeContext; | ||
| const parentEditorContext = getEditorContext(editor); | ||
| try { | ||
| activeContext = {...parentEditorContext, editor, [sym]: contextRecord}; | ||
| return f(); | ||
| } finally { | ||
| activeContext = prevDOMContext; | ||
| } | ||
| } | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function $withContext<Ctx extends AnyContextSymbol>( | ||
| sym: Ctx, | ||
| $defaults: (editor: LexicalEditor) => undefined | ContextRecord<Ctx> = () => | ||
| undefined, | ||
| ) { | ||
| return ( | ||
| cfg: readonly AnyContextConfigPairOrUpdater<Ctx>[], | ||
| editor = $getEditor(), | ||
| ): (<T>(f: () => T) => T) => { | ||
| return f => { | ||
| const parentEditorContext = getEditorContext(editor); | ||
| const parentContextRecord = | ||
| parentEditorContext && parentEditorContext[sym]; | ||
| const contextRecord = contextFromPairs( | ||
| cfg, | ||
| parentContextRecord || $defaults(editor), | ||
| ); | ||
| if (!contextRecord || contextRecord === parentContextRecord) { | ||
| return f(); | ||
| } | ||
| return $withFullContext(sym, contextRecord, f, editor); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function createContextState<Tag extends symbol, V>( | ||
| tag: Tag, | ||
| name: string, | ||
| getDefaultValue: () => V, | ||
| isEqual?: (a: V, b: V) => boolean, | ||
| ): ContextConfig<Tag, V> { | ||
| return Object.assign( | ||
| createState(Symbol(name), {isEqual, parse: getDefaultValue}), | ||
| {[tag]: true} as const, | ||
| ); | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { | ||
| AnyDOMRenderMatch, | ||
| DOMOverrideOptions, | ||
| DOMRenderMatch, | ||
| DOMRenderMatchConfig, | ||
| NodeMatch, | ||
| } from './types'; | ||
| import type {LexicalNode} from 'lexical'; | ||
| /** | ||
| * A convenience function for type inference when constructing DOM overrides for | ||
| * use with {@link DOMRenderExtension}. | ||
| * | ||
| * The optional `options` argument controls *whether* the override is installed | ||
| * based only on render context — `disabledForEditor` gates residency in the | ||
| * editor's render pipeline (reconciliation), `disabledForSession` gates | ||
| * participation in a single export/generate session. See {@link DOMOverrideOptions}. | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function domOverride( | ||
| nodes: '*', | ||
| config: DOMRenderMatchConfig<LexicalNode>, | ||
| options?: DOMOverrideOptions, | ||
| ): DOMRenderMatch<LexicalNode>; | ||
| export function domOverride<T extends LexicalNode>( | ||
| nodes: readonly NodeMatch<T>[], | ||
| config: DOMRenderMatchConfig<T>, | ||
| options?: DOMOverrideOptions, | ||
| ): DOMRenderMatch<T>; | ||
| export function domOverride( | ||
| nodes: AnyDOMRenderMatch['nodes'], | ||
| config: Omit<AnyDOMRenderMatch, 'nodes' | keyof DOMOverrideOptions>, | ||
| options?: DOMOverrideOptions, | ||
| ): AnyDOMRenderMatch { | ||
| return {...config, ...options, nodes}; | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {DOMRenderConfig, DOMRenderExtensionOutput} from './types'; | ||
| import type {InitialEditorConfig} from 'lexical'; | ||
| import {defineExtension, RootNode, shallowMergeConfig} from 'lexical'; | ||
| import {compileDOMRenderConfigOverrides} from './compileDOMRenderConfigOverrides'; | ||
| import {DOMRenderExtensionName} from './constants'; | ||
| import { | ||
| createEditorContextRecord, | ||
| DOMRenderRuntimeImpl, | ||
| filterEditorInstalled, | ||
| } from './DOMRenderRuntime'; | ||
| /** @internal The result returned from {@link DOMRenderExtension}'s `init`. */ | ||
| interface DOMRenderInitResult { | ||
| /** | ||
| * The `nodes` and base `dom` captured from the editor config before `dom` | ||
| * is overwritten with the compiled config — the only fields the runtime | ||
| * needs to recompile. | ||
| */ | ||
| initialEditorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * An extension that allows overriding the render and export behavior for an | ||
| * editor. This is highly experimental and subject to change from one version | ||
| * to the next. | ||
| **/ | ||
| export const DOMRenderExtension = defineExtension< | ||
| DOMRenderConfig, | ||
| typeof DOMRenderExtensionName, | ||
| DOMRenderExtensionOutput, | ||
| DOMRenderInitResult | ||
| >({ | ||
| build(editor, config, state) { | ||
| const {initialEditorConfig} = state.getInitResult(); | ||
| const editorContext = createEditorContextRecord(config.contextDefaults); | ||
| const runtime = new DOMRenderRuntimeImpl( | ||
| editor, | ||
| initialEditorConfig, | ||
| config.overrides, | ||
| editorContext, | ||
| ); | ||
| return {defaults: editorContext, runtime}; | ||
| }, | ||
| config: { | ||
| contextDefaults: [], | ||
| overrides: [], | ||
| }, | ||
| html: { | ||
| // Define a RootNode export for $generateDOMFromRoot | ||
| export: new Map([ | ||
| [ | ||
| RootNode, | ||
| () => { | ||
| const element = document.createElement('div'); | ||
| element.role = 'textbox'; | ||
| return {element}; | ||
| }, | ||
| ], | ||
| ]), | ||
| }, | ||
| init(editorConfig, config) { | ||
| // Capture the user's base `dom` (before we overwrite it) and `nodes` so the | ||
| // runtime can recompile from scratch when overrides toggle. | ||
| const initialEditorConfig: DOMRenderInitResult['initialEditorConfig'] = { | ||
| dom: editorConfig.dom, | ||
| nodes: editorConfig.nodes, | ||
| }; | ||
| const editorContext = createEditorContextRecord(config.contextDefaults); | ||
| const installed = filterEditorInstalled(config.overrides, editorContext); | ||
| editorConfig.dom = compileDOMRenderConfigOverrides(editorConfig, { | ||
| overrides: installed, | ||
| }); | ||
| return {initialEditorConfig}; | ||
| }, | ||
| mergeConfig(config, partial) { | ||
| const merged = shallowMergeConfig(config, partial); | ||
| for (const k of ['overrides', 'contextDefaults'] as const) { | ||
| if (partial[k]) { | ||
| (merged[k] as unknown[]) = [...config[k], ...partial[k]]; | ||
| } | ||
| } | ||
| return merged; | ||
| }, | ||
| name: DOMRenderExtensionName, | ||
| }); |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { | ||
| AnyDOMRenderMatch, | ||
| AnyRenderStateConfigPairOrUpdater, | ||
| ContextRecord, | ||
| DOMRenderRuntime, | ||
| RenderContextReader, | ||
| RenderStateConfig, | ||
| } from './types'; | ||
| import type { | ||
| EditorDOMRenderConfig, | ||
| InitialEditorConfig, | ||
| Klass, | ||
| LexicalEditor, | ||
| LexicalNode, | ||
| } from 'lexical'; | ||
| import { | ||
| $fullReconcile, | ||
| $isLexicalNode, | ||
| DEFAULT_EDITOR_DOM_CONFIG, | ||
| } from 'lexical'; | ||
| import {compileDOMRenderConfigOverrides} from './compileDOMRenderConfigOverrides'; | ||
| import {DOMRenderContextSymbol} from './constants'; | ||
| import { | ||
| contextFromPairs, | ||
| getContextRecord, | ||
| getContextValue, | ||
| } from './ContextRecord'; | ||
| type RenderContextRecord = ContextRecord<typeof DOMRenderContextSymbol>; | ||
| function makeReader(record: RenderContextRecord): RenderContextReader { | ||
| return { | ||
| get<V>(cfg: RenderStateConfig<V>): V { | ||
| return getContextValue(record, cfg); | ||
| }, | ||
| }; | ||
| } | ||
| /** | ||
| * The mutable, writable editor-level context record. Reads of a render state | ||
| * during reconciliation (and as the base layer of a session) fall through to | ||
| * this record, and it is the layer the `disabledForEditor` predicates read. | ||
| * | ||
| * @internal | ||
| */ | ||
| export function createEditorContextRecord( | ||
| contextDefaults: readonly AnyRenderStateConfigPairOrUpdater[], | ||
| ): RenderContextRecord { | ||
| const parent = Object.create(null) as RenderContextRecord; | ||
| return contextFromPairs(contextDefaults, parent) || parent; | ||
| } | ||
| /** | ||
| * Filter the configured overrides down to those that are resident in the | ||
| * editor's render config, removing any whose `disabledForEditor` predicate | ||
| * returns `true` for the given editor context. | ||
| * | ||
| * @internal | ||
| */ | ||
| export function filterEditorInstalled( | ||
| overrides: readonly AnyDOMRenderMatch[], | ||
| record: RenderContextRecord, | ||
| ): AnyDOMRenderMatch[] { | ||
| const reader = makeReader(record); | ||
| return overrides.filter( | ||
| o => !(o.disabledForEditor && o.disabledForEditor(reader)), | ||
| ); | ||
| } | ||
| function sameOverrides( | ||
| a: readonly AnyDOMRenderMatch[], | ||
| b: readonly AnyDOMRenderMatch[], | ||
| ): boolean { | ||
| if (a.length !== b.length) { | ||
| return false; | ||
| } | ||
| for (let i = 0; i < a.length; i++) { | ||
| if (a[i] !== b[i]) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| function symmetricDiff( | ||
| prev: readonly AnyDOMRenderMatch[], | ||
| next: readonly AnyDOMRenderMatch[], | ||
| ): AnyDOMRenderMatch[] { | ||
| const prevSet = new Set(prev); | ||
| const nextSet = new Set(next); | ||
| const changed: AnyDOMRenderMatch[] = []; | ||
| for (const o of prev) { | ||
| if (!nextSet.has(o)) { | ||
| changed.push(o); | ||
| } | ||
| } | ||
| for (const o of next) { | ||
| if (!prevSet.has(o)) { | ||
| changed.push(o); | ||
| } | ||
| } | ||
| return changed; | ||
| } | ||
| /** | ||
| * Build a predicate matching the nodes an override targets — `'*'` matches | ||
| * everything, a node class matches by `instanceof`, and a guard is used as-is. | ||
| */ | ||
| function nodeMatcher(o: AnyDOMRenderMatch): (node: LexicalNode) => boolean { | ||
| if (o.nodes === '*') { | ||
| return () => true; | ||
| } | ||
| const matchers = o.nodes.map(match => { | ||
| const klass = match as Klass<LexicalNode>; | ||
| return $isLexicalNode(klass.prototype) | ||
| ? (node: LexicalNode) => node instanceof klass | ||
| : (match as (node: LexicalNode) => boolean); | ||
| }); | ||
| return node => matchers.some(f => f(node)); | ||
| } | ||
| /** | ||
| * Build a predicate matching the nodes whose DOM must be recreated for the | ||
| * given override change, or `null` when no live re-render is needed. | ||
| * | ||
| * `$createDOM`/`$getDOMSlot` produce the element and slot, and `$decorateDOM` | ||
| * may add DOM that only a fresh `$createDOM` can revert — so toggling any of | ||
| * them recreates the affected nodes. `$updateDOM` is diff-driven and applies on | ||
| * the next node update, and export-only hooks ($exportDOM/$shouldInclude/…) | ||
| * don't touch the live DOM, so neither needs a re-render. Recreating every | ||
| * affected node is the simple, always-correct choice; toggles are rare, so the | ||
| * cost is acceptable and can be optimized later if needed. | ||
| */ | ||
| function recreatePredicate( | ||
| changed: readonly AnyDOMRenderMatch[], | ||
| ): ((node: LexicalNode) => boolean) | null { | ||
| const matchers: ((node: LexicalNode) => boolean)[] = []; | ||
| for (const o of changed) { | ||
| if (o.$createDOM || o.$getDOMSlot || o.$decorateDOM) { | ||
| matchers.push(nodeMatcher(o)); | ||
| } | ||
| } | ||
| return matchers.length === 0 ? null : node => matchers.some(f => f(node)); | ||
| } | ||
| /** | ||
| * Per-editor runtime backing {@link DOMRenderExtension}'s conditional | ||
| * overrides and imperative editor context. See {@link DOMRenderRuntime}. | ||
| * | ||
| * @internal | ||
| */ | ||
| export class DOMRenderRuntimeImpl implements DOMRenderRuntime { | ||
| readonly editor: LexicalEditor; | ||
| /** | ||
| * The `nodes` and base `dom` captured at `init` (before `dom` was | ||
| * overwritten with the compiled config) — the clean base for every recompile. | ||
| */ | ||
| readonly initialEditorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>; | ||
| readonly overrides: readonly AnyDOMRenderMatch[]; | ||
| readonly editorContext: RenderContextRecord; | ||
| readonly hasSessionGates: boolean; | ||
| installed: readonly AnyDOMRenderMatch[]; | ||
| /** Memoized session configs keyed by the set of session-disabled overrides. */ | ||
| private readonly sessionCache = new Map<string, EditorDOMRenderConfig>(); | ||
| constructor( | ||
| editor: LexicalEditor, | ||
| initialEditorConfig: Pick<InitialEditorConfig, 'nodes' | 'dom'>, | ||
| overrides: readonly AnyDOMRenderMatch[], | ||
| editorContext: RenderContextRecord, | ||
| ) { | ||
| this.editor = editor; | ||
| this.initialEditorConfig = initialEditorConfig; | ||
| this.overrides = overrides; | ||
| this.editorContext = editorContext; | ||
| this.installed = filterEditorInstalled(overrides, editorContext); | ||
| this.hasSessionGates = overrides.some(o => o.disabledForSession); | ||
| } | ||
| setContextValue<V>(cfg: RenderStateConfig<V>, value: V): void { | ||
| const prev = this.installed; | ||
| this.editorContext[cfg.key] = value; | ||
| const next = filterEditorInstalled(this.overrides, this.editorContext); | ||
| if (sameOverrides(prev, next)) { | ||
| return; | ||
| } | ||
| const changed = symmetricDiff(prev, next); | ||
| this.installed = next; | ||
| this.sessionCache.clear(); | ||
| const dom = compileDOMRenderConfigOverrides(this.initialEditorConfig, { | ||
| overrides: next as AnyDOMRenderMatch[], | ||
| }); | ||
| this.editor._config.dom = dom; | ||
| const recreate = recreatePredicate(changed); | ||
| if (!recreate) { | ||
| // $updateDOM-only or export-only change: the recompiled config is enough. | ||
| return; | ||
| } | ||
| // Re-render through a full reconcile, which reuses the existing node | ||
| // instances (no node-map mutation, so no spurious mutation/collaboration | ||
| // changes). The affected nodes must be unmounted and recreated — the removed | ||
| // override may have produced or decorated DOM that only a fresh $createDOM | ||
| // reverts — so install a transient $updateDOM that reports a recreate for | ||
| // matching nodes. | ||
| // | ||
| // This mutates the (shared) active config, so the reconcile MUST run and | ||
| // finish synchronously before the original is restored on the next line — | ||
| // hence `discrete`, and hence this must not be called from within an | ||
| // editor.update (where the commit would defer). A deferred update would | ||
| // either restore the wrapper before the reconcile reads it (no recreate) or | ||
| // leave it armed across a window where an unrelated reconcile would | ||
| // spuriously recreate matching nodes. No history tag is needed: a full | ||
| // reconcile marks no nodes dirty, which history merges/discards without | ||
| // pushing. | ||
| const base = dom.$updateDOM; | ||
| dom.$updateDOM = (nextNode, prevNode, el, editor) => | ||
| recreate(nextNode) ? true : base(nextNode, prevNode, el, editor); | ||
| this.editor.update($fullReconcile, {discrete: true}); | ||
| dom.$updateDOM = base; | ||
| } | ||
| getSessionConfig(): EditorDOMRenderConfig { | ||
| const resident = this.editor._config.dom || DEFAULT_EDITOR_DOM_CONFIG; | ||
| if (!this.hasSessionGates) { | ||
| return resident; | ||
| } | ||
| const reader = makeReader( | ||
| getContextRecord(DOMRenderContextSymbol, this.editor) || | ||
| this.editorContext, | ||
| ); | ||
| const disabledKeys: string[] = []; | ||
| const sessionSet: AnyDOMRenderMatch[] = []; | ||
| this.installed.forEach((o, i) => { | ||
| if (o.disabledForSession && o.disabledForSession(reader)) { | ||
| disabledKeys.push(String(i)); | ||
| } else { | ||
| sessionSet.push(o); | ||
| } | ||
| }); | ||
| if (disabledKeys.length === 0) { | ||
| return resident; | ||
| } | ||
| const key = disabledKeys.join(','); | ||
| let cfg = this.sessionCache.get(key); | ||
| if (!cfg) { | ||
| cfg = compileDOMRenderConfigOverrides(this.initialEditorConfig, { | ||
| overrides: sessionSet, | ||
| }); | ||
| this.sessionCache.set(key, cfg); | ||
| } | ||
| return cfg; | ||
| } | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {AnyDOMImportRule, DOMImportFn} from './types'; | ||
| import {isDOMTextNode, isHTMLElement} from 'lexical'; | ||
| import {getSelectorImpl, type Predicate, type SelectorImpl} from './sel'; | ||
| const __DEV__ = process.env.NODE_ENV !== 'production'; | ||
| /** @internal */ | ||
| export interface CompiledRule { | ||
| readonly name: string; | ||
| readonly predicate: Predicate; | ||
| readonly $import: DOMImportFn<Node, Record<string, RegExpMatchArray>>; | ||
| } | ||
| /** @internal */ | ||
| export interface CompiledDispatch { | ||
| /** All rules in registration order. Index = registration order. */ | ||
| readonly rules: readonly CompiledRule[]; | ||
| /** | ||
| * For each (uppercased) HTML tag name, the ordered list of rule indices | ||
| * considered when dispatching that tag. Includes interleaved wildcard | ||
| * element rules so a single iteration handles both. | ||
| */ | ||
| readonly byTag: ReadonlyMap<string, readonly number[]>; | ||
| /** Indices of rules whose match has no tag restriction. */ | ||
| readonly wildcardIndices: readonly number[]; | ||
| /** Indices of rules whose match is `sel.text()`. */ | ||
| readonly textIndices: readonly number[]; | ||
| /** Indices of rules whose match is `sel.comment()`. */ | ||
| readonly commentIndices: readonly number[]; | ||
| } | ||
| function mergeSortedAsc(a: readonly number[], b: readonly number[]): number[] { | ||
| const out: number[] = []; | ||
| let i = 0; | ||
| let j = 0; | ||
| while (i < a.length && j < b.length) { | ||
| if (a[i] <= b[j]) { | ||
| out.push(a[i++]); | ||
| } else { | ||
| out.push(b[j++]); | ||
| } | ||
| } | ||
| while (i < a.length) { | ||
| out.push(a[i++]); | ||
| } | ||
| while (j < b.length) { | ||
| out.push(b[j++]); | ||
| } | ||
| return out; | ||
| } | ||
| /** | ||
| * Compile an ordered list of {@link DOMImportRule}s into the dispatch tables | ||
| * used by the import runtime. The rule at index 0 is the highest-priority | ||
| * (`mergeConfig` prepends partial.rules so later-merged extensions land | ||
| * first). | ||
| * | ||
| * @internal | ||
| */ | ||
| export function compileImportRules( | ||
| rules: readonly AnyDOMImportRule[], | ||
| ): CompiledDispatch { | ||
| const compiled: CompiledRule[] = []; | ||
| const byTag = new Map<string, number[]>(); | ||
| const wildcardIndices: number[] = []; | ||
| const textIndices: number[] = []; | ||
| const commentIndices: number[] = []; | ||
| const seenNames = new Set<string>(); | ||
| rules.forEach((rule, i) => { | ||
| const sel = getSelectorImpl(rule.match); | ||
| const name = rule.name || defaultRuleName(sel, i); | ||
| if (__DEV__ && typeof rule.name === 'string' && seenNames.has(rule.name)) { | ||
| console.warn( | ||
| `[lexical] duplicate DOMImportRule name "${rule.name}" — keep names unique to aid debugging.`, | ||
| ); | ||
| } | ||
| if (rule.name) { | ||
| seenNames.add(rule.name); | ||
| } | ||
| compiled.push({ | ||
| $import: rule.$import as DOMImportFn< | ||
| Node, | ||
| Record<string, RegExpMatchArray> | ||
| >, | ||
| name, | ||
| predicate: sel.predicate, | ||
| }); | ||
| if (sel.kind === 'text') { | ||
| textIndices.push(i); | ||
| } else if (sel.kind === 'comment') { | ||
| commentIndices.push(i); | ||
| } else if (sel.tags.size === 0) { | ||
| wildcardIndices.push(i); | ||
| } else { | ||
| for (const tag of sel.tags) { | ||
| let list = byTag.get(tag); | ||
| if (!list) { | ||
| list = []; | ||
| byTag.set(tag, list); | ||
| } | ||
| list.push(i); | ||
| } | ||
| } | ||
| }); | ||
| // Interleave wildcard-element indices into each tag's list in registration | ||
| // (ascending-index) order, so iterating a tag bucket visits both tag- | ||
| // specific and wildcard rules in the same priority sequence. | ||
| const finalByTag = new Map<string, readonly number[]>(); | ||
| if (wildcardIndices.length === 0) { | ||
| for (const [tag, list] of byTag) { | ||
| finalByTag.set(tag, list); | ||
| } | ||
| } else { | ||
| for (const [tag, list] of byTag) { | ||
| finalByTag.set(tag, mergeSortedAsc(list, wildcardIndices)); | ||
| } | ||
| } | ||
| return { | ||
| byTag: finalByTag, | ||
| commentIndices, | ||
| rules: compiled, | ||
| textIndices, | ||
| wildcardIndices, | ||
| }; | ||
| } | ||
| function defaultRuleName(sel: SelectorImpl, index: number): string { | ||
| if (sel.kind === 'text') { | ||
| return `#text@${index}`; | ||
| } | ||
| if (sel.kind === 'comment') { | ||
| return `#comment@${index}`; | ||
| } | ||
| if (sel.tags.size === 0) { | ||
| return `*@${index}`; | ||
| } | ||
| const tagList = Array.from(sel.tags).join(',').toLowerCase(); | ||
| return `${tagList}@${index}`; | ||
| } | ||
| /** | ||
| * Look up the (already interleaved) rule indices relevant to `node`. Element | ||
| * nodes hit `byTag` (with wildcards merged in) or fall back to the wildcard | ||
| * bucket if no tag-specific rules exist; text and comment nodes use their | ||
| * own buckets. | ||
| * | ||
| * @internal | ||
| */ | ||
| export function getDispatchIndices( | ||
| dispatch: CompiledDispatch, | ||
| node: Node, | ||
| ): readonly number[] { | ||
| if (isDOMTextNode(node)) { | ||
| return dispatch.textIndices; | ||
| } | ||
| if (node.nodeType === 8 /* COMMENT_NODE */) { | ||
| return dispatch.commentIndices; | ||
| } | ||
| if (isHTMLElement(node)) { | ||
| return dispatch.byTag.get(node.nodeName) || dispatch.wildcardIndices; | ||
| } | ||
| return EMPTY_INDICES; | ||
| } | ||
| const EMPTY_INDICES: readonly number[] = Object.freeze([]); |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import {configExtension, defineExtension} from 'lexical'; | ||
| import {CoreImportRules} from './coreImportRules'; | ||
| import {DOMImportExtension} from './DOMImportExtension'; | ||
| /** | ||
| * Bundles {@link CoreImportRules} into a {@link DOMImportExtension}-aware | ||
| * extension. Depend on this from your editor (directly or via richer | ||
| * extensions like `RichTextImportExtension`) to get the equivalent of the | ||
| * legacy core `importDOM` behavior for `<p>`, `<span>`, `<b>`, | ||
| * `<strong>`, `<em>`, `<i>`, `<code>`, `<mark>`, `<s>`, `<sub>`, `<sup>`, | ||
| * `<u>`, `<br>`, and `#text`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const CoreImportExtension = defineExtension({ | ||
| dependencies: [configExtension(DOMImportExtension, {rules: CoreImportRules})], | ||
| name: '@lexical/html/CoreImport', | ||
| }); |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import { | ||
| $createLineBreakNode, | ||
| $createParagraphNode, | ||
| $createTextNode, | ||
| $generateNodesFromRawText, | ||
| $isTextNode, | ||
| $setDirectionFromDOM, | ||
| $setFormatFromDOM, | ||
| type ElementFormatType, | ||
| IS_BOLD, | ||
| IS_CODE, | ||
| IS_HIGHLIGHT, | ||
| IS_ITALIC, | ||
| IS_STRIKETHROUGH, | ||
| IS_SUBSCRIPT, | ||
| IS_SUPERSCRIPT, | ||
| IS_UNDERLINE, | ||
| isDOMTextNode, | ||
| type LexicalNode, | ||
| setNodeIndentFromDOM, | ||
| } from 'lexical'; | ||
| import {contextValue} from '../ContextRecord'; | ||
| import {defineImportRule} from './defineImportRule'; | ||
| import { | ||
| ImportTextFormat, | ||
| ImportTextStyle, | ||
| ImportWhitespaceConfig, | ||
| type WhitespaceImportConfig, | ||
| } from './ImportContext'; | ||
| import {selBase} from './sel'; | ||
| const sel = selBase; | ||
| const ALIGNMENT_VALUES: ReadonlySet<string> = new Set<ElementFormatType>([ | ||
| 'center', | ||
| 'end', | ||
| 'justify', | ||
| 'left', | ||
| 'right', | ||
| 'start', | ||
| ]); | ||
| /** | ||
| * True if `value` is a non-empty {@link ElementFormatType} (matches one of | ||
| * the supported `text-align` / legacy `align`-attribute values). | ||
| * | ||
| * @internal | ||
| */ | ||
| export function isAlignmentValue(value: string): value is ElementFormatType { | ||
| return ALIGNMENT_VALUES.has(value); | ||
| } | ||
| /** | ||
| * A pair of bitmasks describing which {@link TextFormatType} bits to set | ||
| * and which to clear when descending into an element. The clear pass | ||
| * matters for cases the legacy OR-merge mishandled, e.g. `<b | ||
| * style="font-weight: normal">` clearing an inherited bold, or `<sub>` / | ||
| * `<sup>` clearing each other. | ||
| */ | ||
| interface FormatOverride { | ||
| readonly set: number; | ||
| readonly clear: number; | ||
| } | ||
| /** | ||
| * The small subset of inline-style properties that affect text formatting | ||
| * during import. Modeled as a plain object so tag-implicit defaults and | ||
| * the element's own inline `style` can be merged with `{...defaults, | ||
| * ...override-if-set}` semantics rather than relying on CSSStyleDeclaration. | ||
| */ | ||
| interface FormatStyle { | ||
| fontWeight?: string; | ||
| fontStyle?: string; | ||
| textDecoration?: string; | ||
| verticalAlign?: string; | ||
| } | ||
| /** | ||
| * Default style implied by each inline format tag. `<b>`/`<strong>` set | ||
| * font-weight, `<sub>` sets vertical-align, etc. Any of these can be | ||
| * overridden by the element's own inline `style` (so `<b | ||
| * style="font-weight: normal">` ends up with `fontWeight: 'normal'` in | ||
| * the effective style). | ||
| */ | ||
| const TAG_DEFAULT_STYLE: Record<string, FormatStyle> = { | ||
| B: {fontWeight: 'bold'}, | ||
| EM: {fontStyle: 'italic'}, | ||
| I: {fontStyle: 'italic'}, | ||
| S: {textDecoration: 'line-through'}, | ||
| STRONG: {fontWeight: 'bold'}, | ||
| SUB: {verticalAlign: 'sub'}, | ||
| SUP: {verticalAlign: 'super'}, | ||
| U: {textDecoration: 'underline'}, | ||
| }; | ||
| /** | ||
| * Tags whose effect on TextFormat has no CSS analog (so the style-merge | ||
| * path can't reach them). Applied as a pure "set" override. | ||
| */ | ||
| const TAG_ONLY_SET: Record<string, number> = { | ||
| CODE: IS_CODE, | ||
| MARK: IS_HIGHLIGHT, | ||
| }; | ||
| function readElementFormatStyle(el: HTMLElement): FormatStyle { | ||
| return { | ||
| fontStyle: el.style.fontStyle, | ||
| fontWeight: el.style.fontWeight, | ||
| textDecoration: el.style.textDecoration, | ||
| verticalAlign: el.style.verticalAlign, | ||
| }; | ||
| } | ||
| function mergeStyles( | ||
| defaults: FormatStyle, | ||
| override: FormatStyle, | ||
| ): FormatStyle { | ||
| return { | ||
| fontStyle: override.fontStyle || defaults.fontStyle, | ||
| fontWeight: override.fontWeight || defaults.fontWeight, | ||
| textDecoration: override.textDecoration || defaults.textDecoration, | ||
| verticalAlign: override.verticalAlign || defaults.verticalAlign, | ||
| }; | ||
| } | ||
| /** | ||
| * The CSS property names {@link styleFormatOverride} reads — these are | ||
| * "owned" by {@link ImportTextFormat} (the bit mask). When the | ||
| * {@link ImportTextStyle} record is materialized onto a TextNode's | ||
| * inline style by {@link styleObjectToCSS}, these are skipped so the | ||
| * bit-mask side is the single source of truth and the same property | ||
| * doesn't end up in both places (where the inline-style version would | ||
| * shadow the format's themed CSS). | ||
| */ | ||
| const FORMAT_BIT_STYLE_PROPS: ReadonlySet<string> = new Set([ | ||
| 'font-weight', | ||
| 'font-style', | ||
| 'text-decoration', | ||
| 'vertical-align', | ||
| ]); | ||
| /** | ||
| * Translate a {@link FormatStyle} into a {@link FormatOverride}. Explicit | ||
| * "non-decorating" values (`font-weight: normal`, `text-decoration: none`, | ||
| * `vertical-align: baseline`) produce `clear` bits, so an inner element | ||
| * can remove a format inherited from its ancestors. | ||
| */ | ||
| function styleFormatOverride(style: FormatStyle): FormatOverride { | ||
| let set = 0; | ||
| let clear = 0; | ||
| const {fontWeight, fontStyle, textDecoration, verticalAlign} = style; | ||
| if (fontWeight === '700' || fontWeight === 'bold') { | ||
| set |= IS_BOLD; | ||
| } else if (fontWeight === 'normal' || fontWeight === '400') { | ||
| clear |= IS_BOLD; | ||
| } | ||
| if (fontStyle === 'italic') { | ||
| set |= IS_ITALIC; | ||
| } else if (fontStyle === 'normal') { | ||
| clear |= IS_ITALIC; | ||
| } | ||
| if (textDecoration) { | ||
| const parts = textDecoration.split(' '); | ||
| if (parts.includes('underline')) { | ||
| set |= IS_UNDERLINE; | ||
| } | ||
| if (parts.includes('line-through')) { | ||
| set |= IS_STRIKETHROUGH; | ||
| } | ||
| if (parts.includes('none')) { | ||
| clear |= IS_UNDERLINE | IS_STRIKETHROUGH; | ||
| } | ||
| } | ||
| if (verticalAlign === 'sub') { | ||
| set |= IS_SUBSCRIPT; | ||
| clear |= IS_SUPERSCRIPT; | ||
| } else if (verticalAlign === 'super') { | ||
| set |= IS_SUPERSCRIPT; | ||
| clear |= IS_SUBSCRIPT; | ||
| } else if (verticalAlign === 'baseline') { | ||
| clear |= IS_SUBSCRIPT | IS_SUPERSCRIPT; | ||
| } | ||
| return {clear, set}; | ||
| } | ||
| function applyFormatOverride(format: number, ov: FormatOverride): number { | ||
| return (format & ~ov.clear) | ov.set; | ||
| } | ||
| /** | ||
| * Unified rule for inline-format-bearing tags and `<span>`. The element's | ||
| * effective style is its tag's {@link TAG_DEFAULT_STYLE} merged with its | ||
| * inline `style` (element's own style wins for any property it sets), and | ||
| * the resulting style is translated into a {@link FormatOverride}. Tags | ||
| * with no CSS analog (`<code>`, `<mark>`) contribute their bit as a pure | ||
| * `set` override. | ||
| * | ||
| * This shape lets: | ||
| * - `<b style="font-weight: normal">` clear an inherited IS_BOLD. | ||
| * - `<sub><sup>x</sup></sub>` resolve to IS_SUPERSCRIPT only (sub/sup | ||
| * mutex via the vertical-align clear logic). | ||
| * - `<span style="text-decoration: none">` strip inherited underline / | ||
| * line-through. | ||
| */ | ||
| const InlineFormatRule = defineImportRule({ | ||
| $import: (ctx, el) => { | ||
| const inherited = ctx.get(ImportTextFormat); | ||
| const tagDefault = TAG_DEFAULT_STYLE[el.nodeName]; | ||
| const elStyle = readElementFormatStyle(el); | ||
| const effective = tagDefault ? mergeStyles(tagDefault, elStyle) : elStyle; | ||
| let merged = applyFormatOverride(inherited, styleFormatOverride(effective)); | ||
| const tagOnly = TAG_ONLY_SET[el.nodeName]; | ||
| if (tagOnly) { | ||
| merged |= tagOnly; | ||
| } | ||
| if (merged === inherited) { | ||
| return ctx.$importChildren(el); | ||
| } | ||
| return ctx.$importChildren(el, { | ||
| context: [contextValue(ImportTextFormat, merged)], | ||
| }); | ||
| }, | ||
| match: sel.tag( | ||
| 'b', | ||
| 'strong', | ||
| 'em', | ||
| 'i', | ||
| 'code', | ||
| 'mark', | ||
| 's', | ||
| 'sub', | ||
| 'sup', | ||
| 'u', | ||
| 'span', | ||
| ), | ||
| name: '@lexical/html/inline-format', | ||
| }); | ||
| /** | ||
| * Walk up the DOM ancestor chain to determine whether `node` is inside an | ||
| * element whose whitespace should be preserved, per the supplied | ||
| * {@link WhitespaceImportConfig.preservesWhitespace} predicate. Pure | ||
| * ancestor walk, no caching. | ||
| */ | ||
| function isInsidePreserveWhitespace( | ||
| node: Node, | ||
| wsConfig: WhitespaceImportConfig, | ||
| ): boolean { | ||
| let current: Node | null = node.parentNode; | ||
| while (current !== null) { | ||
| if (wsConfig.preservesWhitespace(current)) { | ||
| return true; | ||
| } | ||
| current = current.parentNode; | ||
| } | ||
| return false; | ||
| } | ||
| function findAdjacentTextOnLine( | ||
| text: Text, | ||
| forward: boolean, | ||
| wsConfig: WhitespaceImportConfig, | ||
| ): Text | null { | ||
| let node: Node = text; | ||
| while (true) { | ||
| let sibling: Node | null = null; | ||
| while ( | ||
| (sibling = forward ? node.nextSibling : node.previousSibling) === null | ||
| ) { | ||
| const parent: Node | null = node.parentNode; | ||
| if (parent === null) { | ||
| return null; | ||
| } | ||
| node = parent; | ||
| } | ||
| node = sibling; | ||
| if (!wsConfig.isInline(node)) { | ||
| return null; | ||
| } | ||
| let descendant: Node | null = node; | ||
| while ((descendant = forward ? node.firstChild : node.lastChild) !== null) { | ||
| node = descendant; | ||
| } | ||
| if (isDOMTextNode(node)) { | ||
| return node; | ||
| } | ||
| if (node.nodeName === 'BR') { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
| function collapseWhitespace( | ||
| textNode: Text, | ||
| wsConfig: WhitespaceImportConfig, | ||
| ): string { | ||
| let textContent = (textNode.textContent || '') | ||
| .replace(/\r/g, '') | ||
| .replace(/[ \t\n]+/g, ' '); | ||
| if (textContent.length === 0) { | ||
| return ''; | ||
| } | ||
| if (textContent[0] === ' ') { | ||
| let neighbor: Text | null = textNode; | ||
| let isStartOfLine = true; | ||
| while ( | ||
| neighbor !== null && | ||
| (neighbor = findAdjacentTextOnLine(neighbor, false, wsConfig)) !== null | ||
| ) { | ||
| const neighborContent = neighbor.textContent || ''; | ||
| if (neighborContent.length > 0) { | ||
| if (/[ \t\n]$/.test(neighborContent)) { | ||
| textContent = textContent.slice(1); | ||
| } | ||
| isStartOfLine = false; | ||
| break; | ||
| } | ||
| } | ||
| if (isStartOfLine) { | ||
| textContent = textContent.slice(1); | ||
| } | ||
| } | ||
| if (textContent.length > 0 && textContent[textContent.length - 1] === ' ') { | ||
| let neighbor: Text | null = textNode; | ||
| let isEndOfLine = true; | ||
| while ( | ||
| neighbor !== null && | ||
| (neighbor = findAdjacentTextOnLine(neighbor, true, wsConfig)) !== null | ||
| ) { | ||
| const neighborContent = (neighbor.textContent || '').replace( | ||
| /^( |\t|\r?\n)+/, | ||
| '', | ||
| ); | ||
| if (neighborContent.length > 0) { | ||
| isEndOfLine = false; | ||
| break; | ||
| } | ||
| } | ||
| if (isEndOfLine) { | ||
| textContent = textContent.slice(0, -1); | ||
| } | ||
| } | ||
| return textContent; | ||
| } | ||
| function $applyFormat(node: LexicalNode, format: number): LexicalNode { | ||
| return format !== 0 && $isTextNode(node) ? node.setFormat(format) : node; | ||
| } | ||
| /** | ||
| * Inverse of {@link getStyleObjectFromCSS}: serialize a parsed style | ||
| * record back into a CSS declaration string suitable for | ||
| * `TextNode.setStyle`. Returns the empty string for an empty record. | ||
| */ | ||
| function styleObjectToCSS(style: Readonly<Record<string, string>>): string { | ||
| let css = ''; | ||
| for (const prop in style) { | ||
| if (FORMAT_BIT_STYLE_PROPS.has(prop)) { | ||
| // Owned by ImportTextFormat (bit mask) — skip so the format-bit | ||
| // CSS is the single source of truth on the rendered TextNode. | ||
| continue; | ||
| } | ||
| css += `${prop}: ${style[prop]}; `; | ||
| } | ||
| return css.trimEnd(); | ||
| } | ||
| function $applyTextStyle( | ||
| node: LexicalNode, | ||
| style: Readonly<Record<string, string>>, | ||
| ): LexicalNode { | ||
| if ($isTextNode(node)) { | ||
| const css = styleObjectToCSS(style); | ||
| if (css !== '') { | ||
| node.setStyle(css); | ||
| } | ||
| } | ||
| return node; | ||
| } | ||
| /** | ||
| * `#text` rule. Inside a `<pre>` ancestor, preserve whitespace and split | ||
| * on `\n` and `\t` into `LineBreakNode`/`TabNode` siblings. Otherwise | ||
| * collapse whitespace using the same neighbor-aware rules as the legacy | ||
| * `$convertTextDOMNode`. | ||
| */ | ||
| const TextRule = defineImportRule({ | ||
| $import: (ctx, el) => { | ||
| const format = ctx.get(ImportTextFormat); | ||
| const style = ctx.get(ImportTextStyle); | ||
| const wsConfig = ctx.get(ImportWhitespaceConfig); | ||
| if (isInsidePreserveWhitespace(el, wsConfig)) { | ||
| const out = $generateNodesFromRawText(el.textContent || ''); | ||
| for (const node of out) { | ||
| $applyFormat(node, format); | ||
| $applyTextStyle(node, style); | ||
| } | ||
| return out; | ||
| } | ||
| const collapsed = collapseWhitespace(el, wsConfig); | ||
| if (collapsed === '') { | ||
| return []; | ||
| } | ||
| const text = $createTextNode(collapsed); | ||
| $applyFormat(text, format); | ||
| $applyTextStyle(text, style); | ||
| return [text]; | ||
| }, | ||
| match: sel.text(), | ||
| name: '@lexical/html/#text', | ||
| }); | ||
| /** | ||
| * Drop `<style>` and `<script>` and skip descending into them — matches | ||
| * the legacy `IGNORE_TAGS` set, but as a regular rule so apps can register | ||
| * a higher-priority `<style>` rule to capture stylesheet text into the | ||
| * import session for later use. | ||
| */ | ||
| const IgnoreScriptStyleRule = defineImportRule({ | ||
| $import: () => [], | ||
| match: sel.tag('script', 'style'), | ||
| name: '@lexical/html/script-style-ignore', | ||
| }); | ||
| const LineBreakRule = defineImportRule({ | ||
| $import: () => [$createLineBreakNode()], | ||
| match: sel.tag('br'), | ||
| name: '@lexical/html/br', | ||
| }); | ||
| /** | ||
| * `<p>` rule. Re-applies format, indent, direction, and the legacy | ||
| * `align` attribute fallback. | ||
| */ | ||
| const ParagraphRule = defineImportRule({ | ||
| $import: (ctx, el) => { | ||
| const p = $createParagraphNode(); | ||
| $setFormatFromDOM(p, el); | ||
| setNodeIndentFromDOM(el, p); | ||
| if (p.getFormatType() === '') { | ||
| const align = el.getAttribute('align'); | ||
| if (align && isAlignmentValue(align)) { | ||
| p.setFormat(align); | ||
| } | ||
| } | ||
| $setDirectionFromDOM(p, el); | ||
| // We deliberately pass no schema: paragraphs accept any inline run as-is. | ||
| // The enclosing context (root / block) is responsible for ensuring the | ||
| // paragraph itself is a valid block child. | ||
| return [p.splice(0, 0, ctx.$importChildren(el))]; | ||
| }, | ||
| match: sel.tag('p'), | ||
| name: '@lexical/html/p', | ||
| }); | ||
| /** | ||
| * Rules covering the {@link ParagraphNode}, {@link TextNode}, | ||
| * {@link LineBreakNode}, and {@link TabNode} cases that the legacy | ||
| * `importDOM` machinery in `@lexical/lexical` handled. Intended to be | ||
| * registered as a dependency of every editor that uses | ||
| * {@link DOMImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const CoreImportRules = [ | ||
| IgnoreScriptStyleRule, | ||
| ParagraphRule, | ||
| TextRule, | ||
| LineBreakRule, | ||
| InlineFormatRule, | ||
| ]; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {CompiledSelector, DOMImportFn, DOMImportRule} from './types'; | ||
| /** | ||
| * Identity helper that infers a rule's matched node type and capture map | ||
| * from its `match` selector and threads them into the `$import` signature. | ||
| * Usage: | ||
| * | ||
| * ```ts | ||
| * defineImportRule({ | ||
| * name: '@lexical/list/li', | ||
| * match: sel.tag('li'), | ||
| * $import: (ctx, el, $next) => { | ||
| * // el: HTMLLIElement | ||
| * return [$createListItemNode()]; | ||
| * }, | ||
| * }); | ||
| * ``` | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function defineImportRule<const S extends CompiledSelector>(rule: { | ||
| readonly name?: string; | ||
| readonly match: S; | ||
| readonly $import: DOMImportFn< | ||
| S extends CompiledSelector<infer N, Record<string, RegExpMatchArray>> | ||
| ? N | ||
| : Node, | ||
| S extends CompiledSelector<Node, infer C> ? C : Record<string, never> | ||
| >; | ||
| }): DOMImportRule<S> { | ||
| return rule as DOMImportRule<S>; | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {AnyDOMImportRule} from './types'; | ||
| import {type CompiledDispatch, compileImportRules} from './compileImportRules'; | ||
| /** | ||
| * Opaque handle for a pre-compiled set of overlay rules. Produce one with | ||
| * {@link defineOverlayRules} and pass it to | ||
| * {@link DOMImportContext.$importChildren} via | ||
| * {@link ImportChildrenOpts.rules}. | ||
| * | ||
| * To merge two or more overlays into a single one, pass them (alongside | ||
| * raw {@link DOMImportRule}s if desired) to a fresh | ||
| * {@link defineOverlayRules} — earlier arguments are higher priority. | ||
| * | ||
| * The internal shape is intentionally not part of the public API: it's a | ||
| * compiled dispatch table tagged with `__type` so callers cannot pass a | ||
| * raw rule array where a compiled overlay is expected. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface CompiledOverlayRules { | ||
| readonly __type: 'CompiledOverlayRules'; | ||
| /** @internal */ | ||
| readonly dispatch: CompiledDispatch; | ||
| /** | ||
| * @internal — flattened source rules retained so an overlay can be | ||
| * recompiled when it is passed to another {@link defineOverlayRules} | ||
| * call or as part of {@link DOMImportConfig.rules}. | ||
| */ | ||
| readonly rules: readonly AnyDOMImportRule[]; | ||
| } | ||
| /** | ||
| * An entry accepted everywhere rules are configured (overlay | ||
| * definitions, {@link DOMImportConfig.rules}). Either a single | ||
| * {@link DOMImportRule} or a {@link CompiledOverlayRules} produced by | ||
| * a previous {@link defineOverlayRules} call — passing the latter | ||
| * inlines the overlay's rules at this position in priority order. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type DOMImportRuleEntry = AnyDOMImportRule | CompiledOverlayRules; | ||
| /** @internal */ | ||
| export function flattenRuleEntries( | ||
| entries: readonly DOMImportRuleEntry[], | ||
| ): AnyDOMImportRule[] { | ||
| const out: AnyDOMImportRule[] = []; | ||
| for (const entry of entries) { | ||
| if (isCompiledOverlayRules(entry)) { | ||
| for (const r of entry.rules) { | ||
| out.push(r); | ||
| } | ||
| } else { | ||
| out.push(entry); | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function isCompiledOverlayRules( | ||
| entry: DOMImportRuleEntry, | ||
| ): entry is CompiledOverlayRules { | ||
| return ( | ||
| typeof entry === 'object' && | ||
| entry !== null && | ||
| '__type' in entry && | ||
| entry.__type === 'CompiledOverlayRules' | ||
| ); | ||
| } | ||
| /** | ||
| * Pre-compile a set of {@link DOMImportRuleEntry}s into a | ||
| * {@link CompiledOverlayRules} handle that can be installed via | ||
| * `ctx.$importChildren(el, {rules: …})`. | ||
| * | ||
| * Entries can be raw {@link DOMImportRule}s or other | ||
| * {@link CompiledOverlayRules} (the latter are inlined at their | ||
| * position in priority order, so the same call composes any number of | ||
| * overlays). Earlier entries are higher priority. | ||
| * | ||
| * Overlay rules installed as a raw array would be re-compiled on every | ||
| * `$importChildren` call. For overlays that are reused (e.g. a GitHub | ||
| * code-table rule that wraps every matching table), call this once at | ||
| * module scope so the dispatch table is built up front. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function defineOverlayRules( | ||
| entries: readonly DOMImportRuleEntry[], | ||
| ): CompiledOverlayRules { | ||
| const rules = flattenRuleEntries(entries); | ||
| return { | ||
| __type: 'CompiledOverlayRules', | ||
| dispatch: compileImportRules(rules), | ||
| rules, | ||
| }; | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {ContextRecord} from '../types'; | ||
| import type {DOMImportRuleEntry} from './defineOverlayRules'; | ||
| import type { | ||
| DOMImportExtensionOutput, | ||
| DOMPreprocessContext, | ||
| DOMPreprocessFn, | ||
| GenerateNodesFromDOMOptions, | ||
| ImportContextPairOrUpdater, | ||
| } from './types'; | ||
| import {$getExtensionOutput} from '@lexical/extension'; | ||
| import {defineExtension, type LexicalNode, shallowMergeConfig} from 'lexical'; | ||
| import {DOMImportContextSymbol, DOMImportExtensionName} from '../constants'; | ||
| import {$withFullContext, contextFromPairs} from '../ContextRecord'; | ||
| import {type CompiledDispatch, compileImportRules} from './compileImportRules'; | ||
| import {defineImportRule} from './defineImportRule'; | ||
| import {flattenRuleEntries} from './defineOverlayRules'; | ||
| import {ImportSessionImpl} from './ImportContext'; | ||
| import {$inlineStylesFromStyleSheets} from './inlineStylesFromStyleSheets'; | ||
| import {$runImport} from './runImport'; | ||
| import {selBase} from './sel'; | ||
| /** | ||
| * Configuration for {@link DOMImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportConfig { | ||
| /** | ||
| * The set of rules contributed by this extension and its dependencies. | ||
| * Entries can be raw {@link DOMImportRule}s or a | ||
| * {@link CompiledOverlayRules} produced by {@link defineOverlayRules} | ||
| * (the latter is inlined in priority order — useful for libraries | ||
| * that already publish a compiled overlay). | ||
| * | ||
| * Rules are dispatched in priority order: rules contributed by | ||
| * extensions merged later (i.e. closer to the editor root) run first | ||
| * and may call `$next()` to delegate to lower-priority rules. | ||
| * | ||
| * `mergeConfig` prepends `partial.rules` to existing `rules`, so later | ||
| * configuration carries higher priority. | ||
| */ | ||
| readonly rules: readonly DOMImportRuleEntry[]; | ||
| /** | ||
| * Default context pairs applied to every `$generateNodesFromDOM` call. | ||
| * Per-call overrides can be supplied via | ||
| * {@link GenerateNodesFromDOMOptions.context}. | ||
| */ | ||
| readonly contextDefaults: readonly ImportContextPairOrUpdater[]; | ||
| /** | ||
| * Functions run in order on the DOM before walking begins, mutating in | ||
| * place. The default config registers | ||
| * {@link $inlineStylesFromStyleSheets} (resolves `<style>` rules to | ||
| * inline styles so the rules' style-driven matchers see them); apps | ||
| * append additional preprocessors (e.g. strip unsafe elements, | ||
| * normalize attributes, resolve relative URLs). | ||
| * | ||
| * `mergeConfig` appends, so each contributing extension's preprocessors | ||
| * run in dependency order. Per-call preprocessors registered via | ||
| * {@link GenerateNodesFromDOMOptions.preprocess} run AFTER these. | ||
| */ | ||
| readonly preprocess: readonly DOMPreprocessFn[]; | ||
| } | ||
| /** | ||
| * Drive a stack of {@link DOMPreprocessFn}s top-to-bottom: the highest- | ||
| * index fn runs first and may call `$next()` to defer to the next-lower | ||
| * one. Matches the export-side `callExportMimeTypeFunctionStack` shape. | ||
| */ | ||
| function $runPreprocessStack( | ||
| stack: readonly DOMPreprocessFn[], | ||
| dom: Document | ParentNode, | ||
| ctx: DOMPreprocessContext, | ||
| ): void { | ||
| let i = stack.length - 1; | ||
| const $next = () => { | ||
| while (i >= 0) { | ||
| const cur = stack[i--]; | ||
| cur(dom, ctx, $next); | ||
| return; | ||
| } | ||
| }; | ||
| $next(); | ||
| } | ||
| /** | ||
| * Lowest-priority catch-all rule used as the default `config.rules` entry | ||
| * for {@link DOMImportExtension}: descends into the element's children | ||
| * and returns whatever they produced. With no other matching rule, an | ||
| * element vanishes and its contents are inserted in its place — the | ||
| * legacy `$createNodesFromDOM` hoisting behavior, but now expressed as a | ||
| * regular rule that apps can override (e.g. with a `sel.any()` rule that | ||
| * captures and discards unknown elements). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const DefaultHoistRule = defineImportRule({ | ||
| $import: (ctx, el) => ctx.$importChildren(el), | ||
| match: selBase.any(), | ||
| name: '@lexical/html/default-hoist', | ||
| }); | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Extension-based replacement for the legacy `importDOM` / `DOMConversion` | ||
| * machinery. Rules are contributed via configuration (see | ||
| * {@link DOMImportConfig.rules}), compiled into a tag-bucketed dispatcher at | ||
| * editor build time, and consumed via the extension's | ||
| * {@link DOMImportExtensionOutput.$generateNodesFromDOM} output. | ||
| * | ||
| * The legacy `$generateNodesFromDOM` continues to work in parallel; the | ||
| * intent is to migrate node packages over to this extension incrementally. | ||
| */ | ||
| export const DOMImportExtension = defineExtension< | ||
| DOMImportConfig, | ||
| typeof DOMImportExtensionName, | ||
| DOMImportExtensionOutput, | ||
| void | ||
| >({ | ||
| build(editor, config) { | ||
| const dispatch: CompiledDispatch = compileImportRules( | ||
| flattenRuleEntries(config.rules), | ||
| ); | ||
| const defaults = contextFromPairs(config.contextDefaults, undefined); | ||
| const configPreprocess = config.preprocess; | ||
| return { | ||
| $generateNodesFromDOM: ( | ||
| dom: Document | ParentNode, | ||
| options?: GenerateNodesFromDOMOptions, | ||
| ) => { | ||
| // The session record IS the root layer of the walk's context. | ||
| // Start with per-call options.context applied on top of the | ||
| // editor's contextDefaults, then ensure we have a *fresh* | ||
| // mutable child (never the shared defaults record) so | ||
| // session.set writes never leak into the editor's config. | ||
| const fromOpts = | ||
| options && options.context | ||
| ? contextFromPairs(options.context, defaults) | ||
| : defaults; | ||
| const sessionRecord: ContextRecord<typeof DOMImportContextSymbol> = | ||
| fromOpts !== undefined && fromOpts !== defaults | ||
| ? fromOpts | ||
| : Object.create(defaults || null); | ||
| const session = new ImportSessionImpl(sessionRecord); | ||
| const preprocessCtx: DOMPreprocessContext = {session}; | ||
| // Stack of preprocessors: config-level first, then per-call. | ||
| // Top of stack (last in array) runs first; `next()` defers to | ||
| // the next-lower one. Matches the GetClipboardDataExtension | ||
| // convention so app-registered preprocessors can wrap built-in | ||
| // ones via `next()`. Preprocess writes via `ctx.session.set` | ||
| // mutate the session record directly. | ||
| const stack: readonly DOMPreprocessFn[] = | ||
| options && options.preprocess | ||
| ? [...configPreprocess, ...options.preprocess] | ||
| : configPreprocess; | ||
| $runPreprocessStack(stack, dom, preprocessCtx); | ||
| return $withFullContext( | ||
| DOMImportContextSymbol, | ||
| sessionRecord, | ||
| () => $runImport(dispatch, editor, dom, session), | ||
| editor, | ||
| ); | ||
| }, | ||
| defaults, | ||
| }; | ||
| }, | ||
| config: { | ||
| contextDefaults: [], | ||
| preprocess: [$inlineStylesFromStyleSheets], | ||
| rules: [DefaultHoistRule], | ||
| }, | ||
| mergeConfig(config, partial) { | ||
| return shallowMergeConfig(config, { | ||
| ...partial, | ||
| ...(partial.contextDefaults && { | ||
| contextDefaults: [ | ||
| ...config.contextDefaults, | ||
| ...partial.contextDefaults, | ||
| ], | ||
| }), | ||
| ...(partial.preprocess && { | ||
| preprocess: [...config.preprocess, ...partial.preprocess], | ||
| }), | ||
| ...(partial.rules && { | ||
| rules: [...partial.rules, ...config.rules], | ||
| }), | ||
| }); | ||
| }, | ||
| name: DOMImportExtensionName, | ||
| }); | ||
| /** | ||
| * Look up the editor's {@link DOMImportExtension} and run its | ||
| * `$generateNodesFromDOM`. Designed as a drop-in replacement for the | ||
| * legacy `$generateNodesFromDOM(editor, dom)` signature so it can be | ||
| * supplied to `ClipboardImportExtension.$generateNodesFromDOM` (or any | ||
| * other consumer that wants to route through the extension pipeline). | ||
| * | ||
| * Throws if the editor was not built with {@link DOMImportExtension} as a | ||
| * dependency. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function $generateNodesFromDOMViaExtension( | ||
| dom: Document | ParentNode, | ||
| options?: GenerateNodesFromDOMOptions, | ||
| ): LexicalNode[] { | ||
| return $getExtensionOutput(DOMImportExtension).$generateNodesFromDOM( | ||
| dom, | ||
| options, | ||
| ); | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import { | ||
| $createHorizontalRuleNode, | ||
| HorizontalRuleExtension, | ||
| } from '@lexical/extension'; | ||
| import {configExtension, defineExtension} from 'lexical'; | ||
| import {CoreImportExtension} from './CoreImportExtension'; | ||
| import {defineImportRule} from './defineImportRule'; | ||
| import {DOMImportExtension} from './DOMImportExtension'; | ||
| import {selBase} from './sel'; | ||
| const HorizontalRuleRule = defineImportRule({ | ||
| $import: () => [$createHorizontalRuleNode()], | ||
| match: selBase.tag('hr'), | ||
| name: '@lexical/html/hr', | ||
| }); | ||
| /** | ||
| * Import rules for {@link HorizontalRuleNode}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const HorizontalRuleImportRules = [HorizontalRuleRule]; | ||
| /** | ||
| * Bundles {@link HorizontalRuleImportRules} (plus | ||
| * {@link CoreImportExtension}) into a single dependency. The legacy | ||
| * {@link HorizontalRuleExtension.importDOM} continues to work in parallel; | ||
| * depend on this extension to opt into the new pipeline. | ||
| * | ||
| * Lives in `@lexical/html` (not `@lexical/extension`) because | ||
| * {@link DOMImportExtension} itself is in `@lexical/html`, and | ||
| * `@lexical/extension` is upstream of `@lexical/html` in the dependency | ||
| * graph — same arrangement as {@link CoreImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const HorizontalRuleImportExtension = defineExtension({ | ||
| dependencies: [ | ||
| CoreImportExtension, | ||
| HorizontalRuleExtension, | ||
| configExtension(DOMImportExtension, {rules: HorizontalRuleImportRules}), | ||
| ], | ||
| name: '@lexical/html/HorizontalRuleImport', | ||
| }); |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {ContextRecord} from '../types'; | ||
| import type {CompiledOverlayRules} from './defineOverlayRules'; | ||
| import type {DOMImportExtension} from './DOMImportExtension'; | ||
| import type { | ||
| ImportContextPairOrUpdater, | ||
| ImportSession, | ||
| ImportStateConfig, | ||
| } from './types'; | ||
| import {getPeerDependencyFromEditor} from '@lexical/extension'; | ||
| import { | ||
| $getEditor, | ||
| isBlockDomNode, | ||
| isDOMTextNode, | ||
| isHTMLElement, | ||
| isInlineDomNode, | ||
| type LexicalEditor, | ||
| } from 'lexical'; | ||
| import {DOMImportContextSymbol, DOMImportExtensionName} from '../constants'; | ||
| import { | ||
| $withContext, | ||
| createContextState, | ||
| getContextRecord, | ||
| getContextValue, | ||
| } from '../ContextRecord'; | ||
| type ImportContextRecord = ContextRecord<typeof DOMImportContextSymbol>; | ||
| /** | ||
| * Create an import context state. The phantom symbol prevents accidental | ||
| * use of a render-context state in an import context (and vice versa). | ||
| * | ||
| * Note: to support the value-or-updater pattern, `V` cannot be a function | ||
| * type; wrap it in an array or object if needed. | ||
| * | ||
| * `getDefaultValue` is called **once at state creation** and the result is | ||
| * shared between every session that reads the state without first writing | ||
| * a value. Defaults must therefore be immutable (primitives, frozen | ||
| * objects, or read-only arrays / records). If your state needs mutable | ||
| * per-session storage, lazily initialize it inside your rule (e.g. | ||
| * `if (!ctx.session.has(cfg)) ctx.session.set(cfg, new …())`). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function createImportState<V>( | ||
| name: string, | ||
| getDefaultValue: () => V, | ||
| isEqual?: (a: V, b: V) => boolean, | ||
| ): ImportStateConfig<V> { | ||
| return createContextState( | ||
| DOMImportContextSymbol, | ||
| name, | ||
| getDefaultValue, | ||
| isEqual, | ||
| ); | ||
| } | ||
| /** | ||
| * The kind of operation that produced this import. Lets rules adapt | ||
| * their behavior (e.g. preserve more whitespace on `'paste'`). | ||
| * Defaults to `'unknown'`. Apps that need a different vocabulary can | ||
| * define their own {@link ImportStateConfig} with whatever value type | ||
| * they want. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type ImportSourceKind = 'paste' | 'unknown'; | ||
| /** | ||
| * Built-in import-context state identifying how this import was initiated. | ||
| * Callers of `$generateNodesFromDOM` should set it via the `context` option. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ImportSource: ImportStateConfig<ImportSourceKind> = | ||
| createImportState<ImportSourceKind>('importSource', () => 'unknown'); | ||
| /** | ||
| * Built-in import-context state holding the {@link DataTransfer} the | ||
| * import was sourced from, if any. `null` outside paste/drop flows. | ||
| * | ||
| * The clipboard import pipeline passes the original `DataTransfer` | ||
| * through to its per-MIME-type handler stack (see | ||
| * {@link ImportMimeTypeFunction}); handlers that route HTML through | ||
| * the {@link DOMImportExtension} pipeline should forward it into the | ||
| * walk via `context: [contextValue(ImportSourceDataTransfer, | ||
| * dataTransfer)]` so rules and preprocessors can call | ||
| * `ctx.get(ImportSourceDataTransfer)` to inspect companion MIME types | ||
| * (e.g. an `'application/rtf'` alternative or an attached | ||
| * `'application/x-officedrawing'` payload), the file list, or any | ||
| * custom drag-and-drop slot. | ||
| * | ||
| * Use sparingly: the safer pattern is to decide *which* MIME-type | ||
| * payload to walk in the clipboard handler stack and hand a finalized | ||
| * DOM to the rules; only fall back to peeking at `ImportSourceDataTransfer` | ||
| * when the source-detection signal genuinely lives in a companion | ||
| * slot. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ImportSourceDataTransfer: ImportStateConfig<DataTransfer | null> = | ||
| createImportState<DataTransfer | null>( | ||
| 'importSourceDataTransfer', | ||
| () => null, | ||
| ); | ||
| /** | ||
| * Built-in import-context state holding the bit-packed | ||
| * {@link TextFormatType} formats that should apply to {@link TextNode}s | ||
| * produced during the current subtree. Used by inline-format wrappers | ||
| * (`<b>`, `<i>`, `<u>`, …) to propagate formatting through the context | ||
| * record instead of via the legacy `forChild` chain. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ImportTextFormat: ImportStateConfig<number> = createImportState( | ||
| 'textFormat', | ||
| () => 0, | ||
| ); | ||
| /** | ||
| * Built-in import-context state holding a parsed CSS-style record | ||
| * (the {@link getStyleObjectFromCSS} shape) that should apply to | ||
| * {@link TextNode}s produced during the current subtree. Mirrors the | ||
| * format-bit propagation in {@link ImportTextFormat} for properties | ||
| * that don't fit into the format bit mask — `color`, `font-family`, | ||
| * `font-size`, etc. | ||
| * | ||
| * Ancestor rules that contribute a style branch the context with a | ||
| * merged record; the core `#text` rule materializes the non-empty | ||
| * record to a CSS string and calls `setStyle` on the new TextNode. | ||
| * Once TextNode adopts a parsed style record, the materialization | ||
| * step will go away. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ImportTextStyle: ImportStateConfig< | ||
| Readonly<Record<string, string>> | ||
| > = createImportState<Readonly<Record<string, string>>>( | ||
| 'textStyle', | ||
| () => ({}), | ||
| ); | ||
| /** | ||
| * Determines whether a given DOM element should be treated as preserving | ||
| * whitespace (i.e. text content under it is not collapsed and is split on | ||
| * `\n` / `\t` into `LineBreakNode` / `TabNode`). The default matches the | ||
| * legacy behavior: the element itself is `<pre>` or its inline | ||
| * `white-space` style begins with `'pre'`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type IsPreserveWhitespaceDom = (node: Node) => boolean; | ||
| /** | ||
| * Determines whether a given DOM node sits on the same visual line as its | ||
| * adjacent text siblings, governing whether leading/trailing whitespace in | ||
| * a `#text` is collapsed against neighbors. The default consults | ||
| * {@link isInlineDomNode} from `lexical` (style.display or a fixed inline | ||
| * tag-name set) and additionally treats elements with an explicit | ||
| * non-inline `display` style as block. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type IsInlineForWhitespace = (node: Node) => boolean; | ||
| /** | ||
| * Configuration for the core text whitespace-collapse logic. Override via | ||
| * {@link ImportWhitespaceConfig} either as a `contextDefaults` entry on | ||
| * the {@link DOMImportExtension} or per-call on `$generateNodesFromDOM`'s | ||
| * `context` option. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface WhitespaceImportConfig { | ||
| /** See {@link IsPreserveWhitespaceDom}. */ | ||
| readonly preservesWhitespace: IsPreserveWhitespaceDom; | ||
| /** See {@link IsInlineForWhitespace}. */ | ||
| readonly isInline: IsInlineForWhitespace; | ||
| } | ||
| /** | ||
| * Default {@link WhitespaceImportConfig.preservesWhitespace}: matches | ||
| * `<pre>` and any element with `white-space: pre*`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function defaultPreservesWhitespace(node: Node): boolean { | ||
| if (!isHTMLElement(node)) { | ||
| return false; | ||
| } | ||
| if (node.nodeName === 'PRE') { | ||
| return true; | ||
| } | ||
| const ws = node.style.whiteSpace; | ||
| return typeof ws === 'string' && ws.startsWith('pre'); | ||
| } | ||
| /** | ||
| * Default {@link WhitespaceImportConfig.isInline}: treats an element as | ||
| * inline iff its inline `display` style is `inline*` OR (no explicit | ||
| * non-inline display) its nodeName is a known inline tag (`isInlineDomNode`). | ||
| * Text nodes are always inline; comments and other non-elements are not. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function defaultIsInline(node: Node): boolean { | ||
| if (isDOMTextNode(node)) { | ||
| return true; | ||
| } | ||
| if (!isHTMLElement(node)) { | ||
| return false; | ||
| } | ||
| const display = node.style.display; | ||
| if (display) { | ||
| return display.startsWith('inline'); | ||
| } | ||
| if (isBlockDomNode(node)) { | ||
| return false; | ||
| } | ||
| return isInlineDomNode(node); | ||
| } | ||
| /** | ||
| * Built-in import-context state controlling text-node whitespace handling | ||
| * (collapse vs. preserve, what counts as an inline sibling). Override per | ||
| * editor via {@link DOMImportConfig.contextDefaults} or per call via | ||
| * {@link GenerateNodesFromDOMOptions.context}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ImportWhitespaceConfig: ImportStateConfig<WhitespaceImportConfig> = | ||
| createImportState<WhitespaceImportConfig>('whitespaceConfig', () => ({ | ||
| isInline: defaultIsInline, | ||
| preservesWhitespace: defaultPreservesWhitespace, | ||
| })); | ||
| /** | ||
| * Built-in session slot for runtime overlay rules that should be in | ||
| * effect for the entire walk. A preprocessor writes here when it wants | ||
| * to conditionally install handling for a particular paste source | ||
| * (e.g. "if the Microsoft Word generator meta tag is present, push the | ||
| * Word-paste overlay"). Each entry contributes an overlay dispatcher | ||
| * to the runtime's overlay stack; later array entries are higher | ||
| * priority. Use `ctx.session.update(ImportOverlays, prev => […])` to | ||
| * append. | ||
| * | ||
| * This is the walk-wide counterpart to | ||
| * `$importChildren({rules: …})` (which scopes an overlay to one | ||
| * subtree): write to {@link ImportOverlays} when the overlay should | ||
| * apply for the whole document; use `$importChildren`'s `rules` when | ||
| * the overlay should only apply for a deeper region. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const ImportOverlays: ImportStateConfig< | ||
| readonly CompiledOverlayRules[] | ||
| > = createImportState<readonly CompiledOverlayRules[]>( | ||
| 'importOverlays', | ||
| () => [], | ||
| ); | ||
| /** | ||
| * The session IS the root-layer {@link ContextRecord} of the walk. Reads | ||
| * fall through the prototype chain to the editor's `contextDefaults`, | ||
| * writes mutate the record's own properties, and any branch pushed by | ||
| * `$importChildren({context})` sits above this layer and can shadow | ||
| * (but does not overwrite) slots. | ||
| * | ||
| * @internal | ||
| */ | ||
| export class ImportSessionImpl implements ImportSession { | ||
| constructor(readonly record: ImportContextRecord) {} | ||
| get<V>(cfg: ImportStateConfig<V>): V { | ||
| return getContextValue(this.record, cfg); | ||
| } | ||
| set<V>(cfg: ImportStateConfig<V>, value: V): void { | ||
| this.record[cfg.key] = value; | ||
| } | ||
| update<V>(cfg: ImportStateConfig<V>, updater: (prev: V) => V): void { | ||
| this.record[cfg.key] = updater(getContextValue(this.record, cfg)); | ||
| } | ||
| has<V>(cfg: ImportStateConfig<V>): boolean { | ||
| return Object.prototype.hasOwnProperty.call(this.record, cfg.key); | ||
| } | ||
| } | ||
| function getDefaultImportContext( | ||
| editor: LexicalEditor, | ||
| ): undefined | ContextRecord<typeof DOMImportContextSymbol> { | ||
| const dep = getPeerDependencyFromEditor<typeof DOMImportExtension>( | ||
| editor, | ||
| DOMImportExtensionName, | ||
| ); | ||
| return dep ? dep.output.defaults : undefined; | ||
| } | ||
| function getImportContext( | ||
| editor: LexicalEditor, | ||
| ): undefined | ContextRecord<typeof DOMImportContextSymbol> { | ||
| return ( | ||
| getContextRecord(DOMImportContextSymbol, editor) || | ||
| getDefaultImportContext(editor) | ||
| ); | ||
| } | ||
| /** | ||
| * Read an import context value during an import operation. | ||
| * @experimental | ||
| */ | ||
| export function $getImportContextValue<V>( | ||
| cfg: ImportStateConfig<V>, | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): V { | ||
| return getContextValue(getImportContext(editor), cfg); | ||
| } | ||
| /** | ||
| * Run `f` with the given context pairs applied on top of the editor's | ||
| * current import context. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const $withImportContext: ( | ||
| cfg: readonly ImportContextPairOrUpdater[], | ||
| editor?: LexicalEditor, | ||
| ) => <T>(f: () => T) => T = $withContext( | ||
| DOMImportContextSymbol, | ||
| getDefaultImportContext, | ||
| ); |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import {parseSelector} from './parseCss'; | ||
| import {selBase} from './sel'; | ||
| /** | ||
| * Combinator-and-parser-based builder for {@link CompiledSelector}s. The | ||
| * runtime shape returned by these factory methods is opaque; consumers | ||
| * should never inspect or construct selector objects directly. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const sel = { | ||
| any: selBase.any, | ||
| comment: selBase.comment, | ||
| /** | ||
| * Parse a reduced CSS-selector subset and return a builder you can chain | ||
| * combinator methods off of. | ||
| */ | ||
| css: parseSelector, | ||
| tag: selBase.tag, | ||
| text: selBase.text, | ||
| } as const; | ||
| export {CoreImportExtension} from './CoreImportExtension'; | ||
| export {CoreImportRules} from './coreImportRules'; | ||
| export {defineImportRule} from './defineImportRule'; | ||
| export { | ||
| type CompiledOverlayRules, | ||
| defineOverlayRules, | ||
| type DOMImportRuleEntry, | ||
| } from './defineOverlayRules'; | ||
| export { | ||
| $generateNodesFromDOMViaExtension, | ||
| type DOMImportConfig, | ||
| DOMImportExtension, | ||
| } from './DOMImportExtension'; | ||
| export { | ||
| HorizontalRuleImportExtension, | ||
| HorizontalRuleImportRules, | ||
| } from './HorizontalRuleImportExtension'; | ||
| export { | ||
| $getImportContextValue, | ||
| $withImportContext, | ||
| createImportState, | ||
| defaultIsInline, | ||
| defaultPreservesWhitespace, | ||
| ImportOverlays, | ||
| ImportSource, | ||
| ImportSourceDataTransfer, | ||
| type ImportSourceKind, | ||
| ImportTextFormat, | ||
| ImportTextStyle, | ||
| ImportWhitespaceConfig, | ||
| type IsInlineForWhitespace, | ||
| type IsPreserveWhitespaceDom, | ||
| type WhitespaceImportConfig, | ||
| } from './ImportContext'; | ||
| export {$inlineStylesFromStyleSheets} from './inlineStylesFromStyleSheets'; | ||
| export {parseSelector} from './parseCss'; | ||
| export { | ||
| $distributeInlineWrapper, | ||
| $isBlockLevel, | ||
| BlockSchema, | ||
| InlineSchema, | ||
| NestedBlockSchema, | ||
| RootSchema, | ||
| } from './schemas'; | ||
| export {isElementOfTag} from './sel'; | ||
| export type { | ||
| AnyDOMImportRule, | ||
| AttrMatchOptions, | ||
| CapturesOfSelector, | ||
| ChildSchema, | ||
| CompiledSelector, | ||
| DOMImportContext, | ||
| DOMImportExtensionOutput, | ||
| DOMImportFn, | ||
| DOMImportRule, | ||
| DOMPreprocessContext, | ||
| DOMPreprocessFn, | ||
| ElementSelectorBuilder, | ||
| GenerateNodesFromDOMOptions, | ||
| ImportChildrenOpts, | ||
| ImportContextPairOrUpdater, | ||
| ImportNodeOpts, | ||
| ImportSession, | ||
| ImportStateConfig, | ||
| NodeOfSelector, | ||
| StyleMatchOptions, | ||
| } from './types'; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {DOMPreprocessFn} from './types'; | ||
| import {objectKlassEquals} from '@lexical/utils'; | ||
| import {isDOMDocumentNode, isHTMLElement} from 'lexical'; | ||
| /** | ||
| * Inlines CSS rules from `<style>` tags onto matching elements as inline | ||
| * styles. | ||
| * | ||
| * Used by apps like Excel that generate HTML where styles live in | ||
| * class-based `<style>` rules (e.g. `.xl65 { background: #FFFF00; color: | ||
| * blue; }`) rather than inline styles. Since Lexical's import converters | ||
| * read inline styles, we resolve stylesheet rules into inline styles | ||
| * before conversion. | ||
| * | ||
| * Mutates the DOM in-place. Original inline styles always take | ||
| * precedence over stylesheet rules (matching CSS specificity behavior). | ||
| * | ||
| * No-op for {@link ParentNode}s that are not {@link Document}s — only a | ||
| * full document carries `styleSheets` we can iterate. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const $inlineStylesFromStyleSheets: DOMPreprocessFn = ( | ||
| dom, | ||
| _ctx, | ||
| $next, | ||
| ) => { | ||
| $inlineStylesFromStyleSheetsDOM(dom); | ||
| $next(); | ||
| }; | ||
| export function $inlineStylesFromStyleSheetsDOM( | ||
| dom: Document | ParentNode, | ||
| ): void { | ||
| if (!isDOMDocumentNode(dom)) { | ||
| return; | ||
| } | ||
| const doc = dom; | ||
| if (doc.querySelector('style') === null) { | ||
| return; | ||
| } | ||
| const originalInlineStyles = new Map<HTMLElement, Set<string>>(); | ||
| function getOriginalInlineProps(el: HTMLElement): Set<string> { | ||
| let props = originalInlineStyles.get(el); | ||
| if (props === undefined) { | ||
| props = new Set<string>(); | ||
| for (let i = 0; i < el.style.length; i++) { | ||
| props.add(el.style[i]); | ||
| } | ||
| originalInlineStyles.set(el, props); | ||
| } | ||
| return props; | ||
| } | ||
| try { | ||
| for (const sheet of Array.from(doc.styleSheets)) { | ||
| let rules: CSSRuleList; | ||
| try { | ||
| rules = sheet.cssRules; | ||
| } catch { | ||
| continue; | ||
| } | ||
| for (const rule of Array.from(rules)) { | ||
| if (!objectKlassEquals(rule, CSSStyleRule)) { | ||
| continue; | ||
| } | ||
| let elements: NodeListOf<Element>; | ||
| try { | ||
| elements = doc.querySelectorAll(rule.selectorText); | ||
| } catch { | ||
| continue; | ||
| } | ||
| for (const el of Array.from(elements)) { | ||
| if (!isHTMLElement(el)) { | ||
| continue; | ||
| } | ||
| const originalProps = getOriginalInlineProps(el); | ||
| for (let i = 0; i < rule.style.length; i++) { | ||
| const prop = rule.style[i]; | ||
| if (!originalProps.has(prop)) { | ||
| el.style.setProperty( | ||
| prop, | ||
| rule.style.getPropertyValue(prop), | ||
| rule.style.getPropertyPriority(prop), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } catch { | ||
| // styleSheets API not supported in this environment | ||
| } | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {Predicate} from './sel'; | ||
| import type {ElementSelectorBuilder} from './types'; | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import {buildAttrPredicate, buildClassAllPredicate, buildSelector} from './sel'; | ||
| const IDENT_CHAR = /[A-Za-z0-9_-]/; | ||
| class Cursor { | ||
| constructor( | ||
| public readonly source: string, | ||
| public pos: number, | ||
| ) {} | ||
| peek(offset = 0): string { | ||
| return this.source[this.pos + offset] || ''; | ||
| } | ||
| consume(): string { | ||
| return this.source[this.pos++] || ''; | ||
| } | ||
| eof(): boolean { | ||
| return this.pos >= this.source.length; | ||
| } | ||
| skipWhitespace(): void { | ||
| while (!this.eof() && /\s/.test(this.peek())) { | ||
| this.pos++; | ||
| } | ||
| } | ||
| readIdent(): string { | ||
| const start = this.pos; | ||
| while (!this.eof() && IDENT_CHAR.test(this.peek())) { | ||
| this.pos++; | ||
| } | ||
| return this.source.slice(start, this.pos); | ||
| } | ||
| readQuoted(): string { | ||
| const quote = this.consume(); | ||
| this.assert(quote === '"' || quote === "'", 'expected quote'); | ||
| const start = this.pos; | ||
| while (!this.eof() && this.peek() !== quote) { | ||
| if (this.peek() === '\\') { | ||
| this.pos += 2; | ||
| } else { | ||
| this.pos++; | ||
| } | ||
| } | ||
| this.assert(!this.eof(), 'unterminated string'); | ||
| const value = this.source.slice(start, this.pos); | ||
| this.pos++; // consume closing quote | ||
| return value.replace(/\\(.)/g, '$1'); | ||
| } | ||
| /** | ||
| * `invariant(cond, fmt, …)`-flavored assertion that also surfaces the | ||
| * cursor's position context. Use for parse-time errors so a malformed | ||
| * CSS selector gets a useful, position-annotated message. | ||
| */ | ||
| assert(cond: boolean, msg: string): asserts cond { | ||
| invariant( | ||
| cond, | ||
| 'invalid CSS selector at col %s: %s in %s', | ||
| String(this.pos + 1), | ||
| msg, | ||
| this.source, | ||
| ); | ||
| } | ||
| } | ||
| interface ParsedSimpleSelector { | ||
| readonly tags: Set<string>; | ||
| readonly predicates: Predicate[]; | ||
| } | ||
| function parseSimpleSelector(c: Cursor): ParsedSimpleSelector { | ||
| const tags = new Set<string>(); | ||
| const predicates: Predicate[] = []; | ||
| const classes: string[] = []; | ||
| c.skipWhitespace(); | ||
| // Optional tag or '*' | ||
| if (c.peek() === '*') { | ||
| c.consume(); | ||
| } else if (IDENT_CHAR.test(c.peek())) { | ||
| const tag = c.readIdent(); | ||
| if (tag) { | ||
| tags.add(tag.toUpperCase()); | ||
| } | ||
| } | ||
| // Zero or more refinements: .class, #id, [attr] | ||
| while (!c.eof()) { | ||
| const ch = c.peek(); | ||
| if (ch === '.') { | ||
| c.consume(); | ||
| const cls = c.readIdent(); | ||
| c.assert(cls !== '', 'expected class name after "."'); | ||
| classes.push(cls); | ||
| } else if (ch === '#') { | ||
| c.consume(); | ||
| const id = c.readIdent(); | ||
| c.assert(id !== '', 'expected id after "#"'); | ||
| predicates.push(buildAttrPredicate('id', id)); | ||
| } else if (ch === '[') { | ||
| c.consume(); | ||
| c.skipWhitespace(); | ||
| const name = c.readIdent(); | ||
| c.assert(name !== '', 'expected attribute name after "["'); | ||
| c.skipWhitespace(); | ||
| let value: true | string = true; | ||
| if (c.peek() === '=') { | ||
| c.consume(); | ||
| c.skipWhitespace(); | ||
| const next = c.peek(); | ||
| if (next === '"' || next === "'") { | ||
| value = c.readQuoted(); | ||
| } else { | ||
| value = c.readIdent(); | ||
| c.assert(value !== '', 'expected attribute value'); | ||
| } | ||
| c.skipWhitespace(); | ||
| } | ||
| c.assert(c.peek() === ']', 'expected "]"'); | ||
| c.consume(); | ||
| predicates.push(buildAttrPredicate(name, value)); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| if (classes.length > 0) { | ||
| predicates.push(buildClassAllPredicate(classes)); | ||
| } | ||
| return {predicates, tags}; | ||
| } | ||
| /** | ||
| * Parse a reduced CSS-selector subset and return a {@link CompiledSelector}. | ||
| * Supported: | ||
| * - Tag (`p`), wildcard (`*`). | ||
| * - Tag list (`h1, h2, h3`). | ||
| * - Class (`.foo`, `.foo.bar`). | ||
| * - ID (`#foo`). | ||
| * - Attribute presence (`[name]`). | ||
| * - Attribute equality (`[name="value"]`, `[name=value]`). | ||
| * | ||
| * Anything outside the subset (regex attribute, inline-style match, | ||
| * combinators, pseudo-classes) is intentionally rejected — chain combinator | ||
| * methods off the returned builder instead. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function parseSelector( | ||
| source: string, | ||
| ): ElementSelectorBuilder<HTMLElement> { | ||
| const c: Cursor = new Cursor(source, 0); | ||
| const groups: ParsedSimpleSelector[] = []; | ||
| while (true) { | ||
| const group = parseSimpleSelector(c); | ||
| if (group.tags.size === 0 && group.predicates.length === 0) { | ||
| // Empty group with neither tag nor refinement — only OK if it came | ||
| // from the lone `*` (which produces zero tags but no preds either). | ||
| // We accept this as "wildcard element". | ||
| } | ||
| groups.push(group); | ||
| c.skipWhitespace(); | ||
| if (c.eof()) { | ||
| break; | ||
| } | ||
| c.assert( | ||
| c.peek() === ',', | ||
| 'expected "," (selector lists are the only supported combinator)', | ||
| ); | ||
| c.consume(); | ||
| c.skipWhitespace(); | ||
| } | ||
| if (groups.length === 1) { | ||
| return buildSelector(groups[0].tags, groups[0].predicates); | ||
| } | ||
| // Comma-separated list. Merge tag sets and OR-combine the per-group | ||
| // refinement predicates so that each candidate node satisfies *some* | ||
| // group entirely. | ||
| const tags = new Set<string>(); | ||
| for (const g of groups) { | ||
| for (const t of g.tags) { | ||
| tags.add(t); | ||
| } | ||
| } | ||
| const orPredicate: Predicate = (node, captures) => { | ||
| for (const g of groups) { | ||
| const upper = node.nodeName; | ||
| if (g.tags.size > 0 && !g.tags.has(upper)) { | ||
| continue; | ||
| } | ||
| let ok = true; | ||
| for (const p of g.predicates) { | ||
| if (!p(node, captures)) { | ||
| ok = false; | ||
| break; | ||
| } | ||
| } | ||
| if (ok) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| return buildSelector(tags, [orPredicate]); | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { | ||
| ChildSchema, | ||
| DOMImportContext, | ||
| ImportChildrenOpts, | ||
| ImportNodeOpts, | ||
| ImportSession, | ||
| ImportStateConfig, | ||
| } from './types'; | ||
| import {isDOMDocumentNode, type LexicalEditor, type LexicalNode} from 'lexical'; | ||
| import { | ||
| type CompiledDispatch, | ||
| type CompiledRule, | ||
| getDispatchIndices, | ||
| } from './compileImportRules'; | ||
| import { | ||
| $getImportContextValue, | ||
| $withImportContext, | ||
| ImportOverlays, | ||
| } from './ImportContext'; | ||
| import {$applySchema, RootSchema} from './schemas'; | ||
| const __DEV__ = process.env.NODE_ENV !== 'production'; | ||
| const NO_CAPTURES: Record<string, RegExpMatchArray> = Object.freeze( | ||
| {} as Record<string, RegExpMatchArray>, | ||
| ); | ||
| interface Runtime { | ||
| readonly dispatch: CompiledDispatch; | ||
| readonly editor: LexicalEditor; | ||
| readonly session: ImportSession; | ||
| /** | ||
| * Stack of overlay dispatchers installed via `$importChildren({rules})`. | ||
| * The most recently pushed overlay is at the highest index and is | ||
| * tried first; rules within an overlay are dispatched in their own | ||
| * registration order. When all overlay rules for a node have been | ||
| * exhausted (or have called `$next()` to defer), the main dispatcher | ||
| * is consulted. This lets app rules scope cost-bearing predicates to | ||
| * the subtrees where they apply. | ||
| */ | ||
| readonly overlays: CompiledDispatch[]; | ||
| } | ||
| function makeContext( | ||
| runtime: Runtime, | ||
| captures: Readonly<Record<string, RegExpMatchArray>>, | ||
| ): DOMImportContext<Record<string, RegExpMatchArray>> { | ||
| const ctx: DOMImportContext<Record<string, RegExpMatchArray>> = { | ||
| $importChildren: (parent, opts) => | ||
| $importChildrenInternal(runtime, parent, opts), | ||
| $importOne: (node, opts) => $importOneInternal(runtime, node, opts), | ||
| captures, | ||
| get<V>(cfg: ImportStateConfig<V>): V { | ||
| return $getImportContextValue(cfg, runtime.editor); | ||
| }, | ||
| session: runtime.session, | ||
| }; | ||
| return ctx; | ||
| } | ||
| function $importChildrenInternal( | ||
| runtime: Runtime, | ||
| parent: ParentNode, | ||
| opts: ImportChildrenOpts | undefined, | ||
| ): LexicalNode[] { | ||
| const overlay = opts && opts.rules ? opts.rules.dispatch : undefined; | ||
| if (overlay) { | ||
| runtime.overlays.push(overlay); | ||
| } | ||
| try { | ||
| const run = () => $importChildrenRun(runtime, parent, opts); | ||
| return opts && opts.context | ||
| ? $withImportContext(opts.context, runtime.editor)(run) | ||
| : run(); | ||
| } finally { | ||
| if (overlay) { | ||
| runtime.overlays.pop(); | ||
| } | ||
| } | ||
| } | ||
| function $importChildrenRun( | ||
| runtime: Runtime, | ||
| parent: ParentNode, | ||
| opts: ImportChildrenOpts | undefined, | ||
| ): LexicalNode[] { | ||
| const onChild = opts && opts.$onChild; | ||
| const collected: LexicalNode[] = []; | ||
| for (const child of Array.from(parent.childNodes)) { | ||
| const produced = $importOneInternal(runtime, child, undefined); | ||
| for (const lex of produced) { | ||
| const result = onChild ? onChild(lex) : lex; | ||
| if (result != null) { | ||
| collected.push(result); | ||
| } | ||
| } | ||
| } | ||
| const afterApplied = opts && opts.$after ? opts.$after(collected) : collected; | ||
| const schema: ChildSchema | undefined = opts && opts.schema; | ||
| if (!schema) { | ||
| return afterApplied; | ||
| } | ||
| return $applySchema(schema, afterApplied, null, parent); | ||
| } | ||
| function $importOneInternal( | ||
| runtime: Runtime, | ||
| node: Node, | ||
| opts: ImportNodeOpts | undefined, | ||
| ): LexicalNode[] { | ||
| const run = () => $dispatch(runtime, node); | ||
| const out = | ||
| opts && opts.context | ||
| ? $withImportContext(opts.context, runtime.editor)(run) | ||
| : run(); | ||
| // Surface to callers as a mutable array per the DOMImportContext contract. | ||
| return out as LexicalNode[]; | ||
| } | ||
| /** | ||
| * Build the candidate (dispatch, indices) list for `node`. Overlays are | ||
| * tried first in top-of-stack order; the main dispatcher comes last. The | ||
| * `$next()` chain walks through all of them in sequence — an overlay rule | ||
| * can defer to a lower overlay rule, or all the way through to a main | ||
| * rule, just by calling `$next()`. | ||
| */ | ||
| function getCandidates( | ||
| runtime: Runtime, | ||
| node: Node, | ||
| ): readonly {dispatch: CompiledDispatch; indices: readonly number[]}[] { | ||
| const candidates: { | ||
| dispatch: CompiledDispatch; | ||
| indices: readonly number[]; | ||
| }[] = []; | ||
| for (let i = runtime.overlays.length - 1; i >= 0; i--) { | ||
| const d = runtime.overlays[i]; | ||
| const idx = getDispatchIndices(d, node); | ||
| if (idx.length > 0) { | ||
| candidates.push({dispatch: d, indices: idx}); | ||
| } | ||
| } | ||
| const mainIdx = getDispatchIndices(runtime.dispatch, node); | ||
| if (mainIdx.length > 0) { | ||
| candidates.push({dispatch: runtime.dispatch, indices: mainIdx}); | ||
| } | ||
| return candidates; | ||
| } | ||
| function $dispatch(runtime: Runtime, node: Node): readonly LexicalNode[] { | ||
| const candidates = getCandidates(runtime, node); | ||
| if (candidates.length === 0) { | ||
| return $hoistChildrenOf(runtime, node); | ||
| } | ||
| let groupCursor = 0; | ||
| let ruleCursor = 0; | ||
| const $next = (): readonly LexicalNode[] => { | ||
| while (groupCursor < candidates.length) { | ||
| const {dispatch, indices} = candidates[groupCursor]; | ||
| while (ruleCursor < indices.length) { | ||
| const idx = indices[ruleCursor++]; | ||
| const rule: CompiledRule = dispatch.rules[idx]; | ||
| const captures: Record<string, RegExpMatchArray> = {}; | ||
| if (rule.predicate(node, captures)) { | ||
| const ctx = makeContext( | ||
| runtime, | ||
| Object.keys(captures).length === 0 ? NO_CAPTURES : captures, | ||
| ); | ||
| try { | ||
| return rule.$import(ctx, node, $next); | ||
| } catch (e) { | ||
| if (__DEV__) { | ||
| console.error( | ||
| `[lexical] DOM import rule "${rule.name}" threw on node`, | ||
| node, | ||
| e, | ||
| ); | ||
| } | ||
| throw e; | ||
| } | ||
| } | ||
| } | ||
| groupCursor++; | ||
| ruleCursor = 0; | ||
| } | ||
| return $hoistChildrenOf(runtime, node); | ||
| }; | ||
| return $next(); | ||
| } | ||
| /** | ||
| * Fallback when no rule matched and `$next()` was called past the end of the | ||
| * chain: hoist the element's children to take its place, recursively. Pure | ||
| * elements with no rule become invisible, matching the legacy | ||
| * `$createNodesFromDOM` hoisting behavior. | ||
| */ | ||
| function $hoistChildrenOf(runtime: Runtime, node: Node): LexicalNode[] { | ||
| if (node.childNodes.length === 0) { | ||
| return []; | ||
| } | ||
| const collected: LexicalNode[] = []; | ||
| for (const child of Array.from(node.childNodes)) { | ||
| const produced = $importOneInternal(runtime, child, undefined); | ||
| for (const lex of produced) { | ||
| collected.push(lex); | ||
| } | ||
| } | ||
| return collected; | ||
| } | ||
| /** | ||
| * Top-level walker for a compiled dispatcher. Iterates the DOM children of | ||
| * `dom` (using the document body if a {@link Document} is passed) and | ||
| * applies `RootSchema` to the produced lexical nodes so runs of inlines are | ||
| * wrapped in paragraphs — same shape as the legacy `$generateNodesFromDOM`. | ||
| * | ||
| * @internal | ||
| */ | ||
| export function $runImport( | ||
| dispatch: CompiledDispatch, | ||
| editor: LexicalEditor, | ||
| dom: Document | ParentNode, | ||
| session: ImportSession, | ||
| ): LexicalNode[] { | ||
| // Prime the overlay stack with any overlays a preprocess wrote to | ||
| // ImportOverlays. These remain in effect for the entire walk; nested | ||
| // `$importChildren({rules})` calls push on top. | ||
| const installed = session.get(ImportOverlays); | ||
| const runtime: Runtime = { | ||
| dispatch, | ||
| editor, | ||
| overlays: installed.map(o => o.dispatch), | ||
| session, | ||
| }; | ||
| const rootParent: ParentNode = isDOMDocumentNode(dom) ? dom.body : dom; | ||
| return $importChildrenRun(runtime, rootParent, {schema: RootSchema}); | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {ChildSchema} from './types'; | ||
| import { | ||
| $createParagraphNode, | ||
| $isBlockElementNode, | ||
| $isDecoratorNode, | ||
| $isElementNode, | ||
| type ElementNode, | ||
| isHTMLElement, | ||
| type LexicalNode, | ||
| } from 'lexical'; | ||
| import {isAlignmentValue} from './coreImportRules'; | ||
| /** | ||
| * True if the node fills a block slot at the root or inside another | ||
| * block — covers both ElementNode-style blocks (paragraph, heading, | ||
| * quote) and block-level DecoratorNodes (HorizontalRuleNode, | ||
| * ImageNode-as-block, etc.). Used by {@link BlockSchema}, | ||
| * {@link RootSchema}, and {@link NestedBlockSchema}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function $isBlockLevel(node: LexicalNode): boolean { | ||
| return ( | ||
| $isBlockElementNode(node) || ($isDecoratorNode(node) && !node.isInline()) | ||
| ); | ||
| } | ||
| /** | ||
| * Distribute an inline wrapper (`LinkNode`, `MarkNode`, …) across a | ||
| * heterogeneous run of children produced by `$importChildren`, lifting | ||
| * any block children to the top level while keeping the wrapper around | ||
| * the leaf inline content. | ||
| * | ||
| * Use from a rule whose DOM source is an inline element that the | ||
| * browser permitted to enclose block elements — the canonical case is | ||
| * `<a href="…"><h1>title</h1><div>body</div></a>`, which a link rule | ||
| * wants to surface as two block siblings (heading + paragraph), each | ||
| * with its own link wrapping the original inline content. Schemas | ||
| * can't express this because they reason about a parent's children | ||
| * only — they cannot lift the parent out of itself. | ||
| * | ||
| * For each top-level child: | ||
| * - **Inline children** are collected into runs; each run is wrapped | ||
| * in a single fresh wrapper (from `$makeWrapper()`). | ||
| * - **Block children** are descended into: their own children are | ||
| * recursively distributed with `$makeWrapper`, then re-attached so | ||
| * the block keeps its position at the top level. | ||
| * | ||
| * The returned list will contain a mix of blocks and wrapped inline | ||
| * runs. The enclosing schema (typically {@link BlockSchema}) will | ||
| * then package those inline wrappers into paragraphs as usual. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function $distributeInlineWrapper( | ||
| children: readonly LexicalNode[], | ||
| $makeWrapper: () => ElementNode, | ||
| ): LexicalNode[] { | ||
| const out: LexicalNode[] = []; | ||
| let inlineRun: LexicalNode[] = []; | ||
| const flushInline = () => { | ||
| if (inlineRun.length === 0) { | ||
| return; | ||
| } | ||
| out.push($makeWrapper().splice(0, 0, inlineRun)); | ||
| inlineRun = []; | ||
| }; | ||
| for (const child of children) { | ||
| if ($isBlockLevel(child)) { | ||
| flushInline(); | ||
| // Recursively distribute the wrapper into the block's own | ||
| // children. A block DecoratorNode (no children) is left alone. | ||
| if ($isElementNode(child)) { | ||
| const wrapped = $distributeInlineWrapper( | ||
| child.getChildren(), | ||
| $makeWrapper, | ||
| ); | ||
| child.splice(0, child.getChildrenSize(), wrapped); | ||
| } | ||
| out.push(child); | ||
| } else { | ||
| inlineRun.push(child); | ||
| } | ||
| } | ||
| flushInline(); | ||
| return out; | ||
| } | ||
| /** | ||
| * Apply a {@link ChildSchema} to a flat list of children produced by | ||
| * `$importChildren`. Walks the list once, partitions into accepted vs. | ||
| * rejected runs, packages or drops rejected runs, then runs `$finalize`. | ||
| * | ||
| * @internal | ||
| */ | ||
| export function $applySchema( | ||
| schema: ChildSchema, | ||
| children: LexicalNode[], | ||
| parent: LexicalNode | null, | ||
| domParent: Node | null, | ||
| ): LexicalNode[] { | ||
| const out: LexicalNode[] = []; | ||
| let run: LexicalNode[] | null = null; | ||
| const flushRun = () => { | ||
| if (run === null) { | ||
| return; | ||
| } | ||
| const rejected = run; | ||
| run = null; | ||
| if (schema.$packageRun) { | ||
| const packaged = schema.$packageRun(rejected, parent, domParent); | ||
| if (packaged.length > 0) { | ||
| for (const n of packaged) { | ||
| out.push(n); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| // No $packageRun (or it returned []) — apply onReject. 'drop' (default) | ||
| // discards the run. 'hoist' lets it through unchanged at this level. | ||
| if (schema.onReject === 'hoist') { | ||
| for (const n of rejected) { | ||
| out.push(n); | ||
| } | ||
| } | ||
| }; | ||
| for (const child of children) { | ||
| if (schema.$accepts(child, parent)) { | ||
| flushRun(); | ||
| out.push(child); | ||
| } else { | ||
| if (run === null) { | ||
| run = []; | ||
| } | ||
| run.push(child); | ||
| } | ||
| } | ||
| flushRun(); | ||
| return schema.$finalize ? schema.$finalize(out, parent) : out; | ||
| } | ||
| /** | ||
| * Wrap a run of inline lexical nodes in a fresh paragraph, propagating the | ||
| * `text-align` of `domParent` as the paragraph's format type (matching the | ||
| * legacy `wrapContinuousInlines` behavior). | ||
| */ | ||
| function $paragraphPackageRun( | ||
| run: LexicalNode[], | ||
| _parent: LexicalNode | null, | ||
| domParent: Node | null, | ||
| ): LexicalNode[] { | ||
| const paragraph = $createParagraphNode(); | ||
| if (isHTMLElement(domParent)) { | ||
| const textAlign = domParent.style.textAlign; | ||
| if (isAlignmentValue(textAlign)) { | ||
| paragraph.setFormat(textAlign); | ||
| } | ||
| } | ||
| return [paragraph.splice(0, 0, run)]; | ||
| } | ||
| /** | ||
| * Default schema for block-level positions (root of the document, the body | ||
| * of a block element node). Accepts block lexical nodes; packages runs of | ||
| * inline children into fresh paragraph nodes. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const BlockSchema: ChildSchema = { | ||
| $accepts: $isBlockLevel, | ||
| $packageRun: $paragraphPackageRun, | ||
| name: 'BlockSchema', | ||
| }; | ||
| /** | ||
| * Schema for inline-only positions (the body of an inline lexical node such | ||
| * as a link). Accepts non-block lexical nodes; runs of block children are | ||
| * dropped (`onReject: 'drop'` is the default). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const InlineSchema: ChildSchema = { | ||
| $accepts: child => !$isBlockLevel(child), | ||
| name: 'InlineSchema', | ||
| }; | ||
| /** | ||
| * Schema for nested block positions — the equivalent of the legacy | ||
| * `ArtificialNode__DO_NOT_USE` flow used when a block DOM element appears | ||
| * inside another block lexical ancestor. Accepts block nodes; runs of inline | ||
| * children are emitted with a line break between consecutive runs (instead | ||
| * of being wrapped in a paragraph, which would introduce an extra level of | ||
| * nesting). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const NestedBlockSchema: ChildSchema = { | ||
| $accepts: $isBlockLevel, | ||
| /** | ||
| * Pass an inline run through unchanged. Because the schema iterator only | ||
| * groups *maximal* rejected runs (each separated from the next by an | ||
| * accepted block child), the legacy "linebreak between adjacent inline | ||
| * groups" case never arises — adjacent inline siblings are already | ||
| * coalesced into one run. | ||
| */ | ||
| $packageRun: run => run, | ||
| name: 'NestedBlockSchema', | ||
| }; | ||
| /** | ||
| * Schema for the topmost level of `$generateNodesFromDOM`. Identical to | ||
| * {@link BlockSchema}; aliased for clarity at the entry point and so it can | ||
| * be overridden separately in the future (e.g. to synthesize a `ListNode` | ||
| * around runs of orphan `ListItemNode`s). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export const RootSchema: ChildSchema = { | ||
| $accepts: $isBlockLevel, | ||
| $packageRun: $paragraphPackageRun, | ||
| name: 'RootSchema', | ||
| }; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { | ||
| AttrMatchOptions, | ||
| CompiledSelector, | ||
| ElementSelectorBuilder, | ||
| StyleMatchOptions, | ||
| } from './types'; | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import {isDOMTextNode, isHTMLElement} from 'lexical'; | ||
| /** | ||
| * @internal | ||
| * | ||
| * A predicate that may write into the per-invocation `captures` map. Returns | ||
| * `true` if the rule matches; `false` otherwise. | ||
| */ | ||
| export type Predicate = ( | ||
| node: Node, | ||
| captures: Record<string, RegExpMatchArray>, | ||
| ) => boolean; | ||
| /** @internal */ | ||
| export type SelectorKind = 'element' | 'text' | 'comment'; | ||
| /** @internal The runtime shape of a {@link CompiledSelector}. */ | ||
| export interface SelectorImpl { | ||
| readonly kind: SelectorKind; | ||
| /** | ||
| * Uppercased tag names this selector is restricted to. Empty for wildcard | ||
| * element selectors and for text / comment selectors (dispatched by | ||
| * `kind`). | ||
| */ | ||
| readonly tags: ReadonlySet<string>; | ||
| /** Composed predicate run against a candidate node. */ | ||
| readonly predicate: Predicate; | ||
| } | ||
| const IMPL = Symbol.for('@lexical/html/SelectorImpl'); | ||
| /** @internal */ | ||
| export function getSelectorImpl(sel: CompiledSelector): SelectorImpl { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const impl = (sel as any)[IMPL] as SelectorImpl | undefined; | ||
| invariant( | ||
| impl !== undefined, | ||
| 'match must be a CompiledSelector produced by sel.* or sel.css(); received a raw object.', | ||
| ); | ||
| return impl; | ||
| } | ||
| function combinePredicates(preds: readonly Predicate[]): Predicate { | ||
| if (preds.length === 0) { | ||
| return isHTMLElement; | ||
| } | ||
| if (preds.length === 1) { | ||
| return preds[0]; | ||
| } | ||
| return (node, captures) => { | ||
| for (const p of preds) { | ||
| if (!p(node, captures)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| } | ||
| /** | ||
| * @internal | ||
| * | ||
| * Build a selector value from a tag set and a predicate list. Used by the | ||
| * combinator API and the CSS parser. | ||
| */ | ||
| export function buildSelector( | ||
| tags: ReadonlySet<string>, | ||
| predicates: readonly Predicate[], | ||
| ): ElementSelectorBuilder<HTMLElement> { | ||
| const impl: SelectorImpl = { | ||
| kind: 'element', | ||
| predicate: combinePredicates(predicates), | ||
| tags, | ||
| }; | ||
| const refine = (additional: Predicate) => | ||
| buildSelector(tags, [...predicates, additional]); | ||
| const builder = { | ||
| [IMPL]: impl, | ||
| attr: (name: string, value: unknown, options?: AttrMatchOptions) => | ||
| refine(buildAttrPredicate(name, value, options)), | ||
| classAll: (...classes: readonly string[]) => | ||
| refine(buildClassAllPredicate(classes)), | ||
| classAny: (...classes: readonly string[]) => | ||
| refine(buildClassAnyPredicate(classes)), | ||
| styleAny: (prop: string, value: unknown, options?: StyleMatchOptions) => | ||
| refine(buildStylePredicate(prop, value, options)), | ||
| }; | ||
| // The runtime is fully type-erased; cast to satisfy the surface. | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| return builder as any; | ||
| } | ||
| function normalizeClassList(classes: readonly string[]): readonly string[] { | ||
| const out: string[] = []; | ||
| for (const c of classes) { | ||
| if (c) { | ||
| out.push(c); | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| /** @internal */ | ||
| export function buildClassAllPredicate(classes: readonly string[]): Predicate { | ||
| const ns = normalizeClassList(classes); | ||
| if (ns.length === 0) { | ||
| return () => true; | ||
| } | ||
| return node => { | ||
| if (!isHTMLElement(node)) { | ||
| return false; | ||
| } | ||
| const cl = node.classList; | ||
| for (const c of ns) { | ||
| if (!cl.contains(c)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| } | ||
| /** @internal */ | ||
| export function buildClassAnyPredicate(classes: readonly string[]): Predicate { | ||
| const ns = normalizeClassList(classes); | ||
| if (ns.length === 0) { | ||
| return () => false; | ||
| } | ||
| return node => { | ||
| if (!isHTMLElement(node)) { | ||
| return false; | ||
| } | ||
| const cl = node.classList; | ||
| for (const c of ns) { | ||
| if (cl.contains(c)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| } | ||
| /** @internal */ | ||
| export function buildAttrPredicate( | ||
| name: string, | ||
| value: unknown, | ||
| options?: AttrMatchOptions, | ||
| ): Predicate { | ||
| if (value === true) { | ||
| return node => isHTMLElement(node) && node.hasAttribute(name); | ||
| } | ||
| if (typeof value === 'string') { | ||
| return node => isHTMLElement(node) && node.getAttribute(name) === value; | ||
| } | ||
| if (value instanceof RegExp) { | ||
| const capture = options && options.capture; | ||
| const re = value; | ||
| return (node, captures) => { | ||
| if (!isHTMLElement(node)) { | ||
| return false; | ||
| } | ||
| const v = node.getAttribute(name); | ||
| if (v == null) { | ||
| return false; | ||
| } | ||
| const m = v.match(re); | ||
| if (m === null) { | ||
| return false; | ||
| } | ||
| if (capture !== undefined) { | ||
| captures[capture] = m; | ||
| } | ||
| return true; | ||
| }; | ||
| } | ||
| invariant( | ||
| false, | ||
| 'sel.attr(%s, ...) requires true, a string, or a RegExp', | ||
| JSON.stringify(name), | ||
| ); | ||
| } | ||
| function buildStylePredicate( | ||
| prop: string, | ||
| value: unknown, | ||
| options?: StyleMatchOptions, | ||
| ): Predicate { | ||
| if (typeof value === 'string') { | ||
| return node => | ||
| isHTMLElement(node) && node.style.getPropertyValue(prop) === value; | ||
| } | ||
| if (value instanceof RegExp) { | ||
| const capture = options && options.capture; | ||
| const re = value; | ||
| return (node, captures) => { | ||
| if (!isHTMLElement(node)) { | ||
| return false; | ||
| } | ||
| const v = node.style.getPropertyValue(prop); | ||
| if (!v) { | ||
| return false; | ||
| } | ||
| const m = v.match(re); | ||
| if (m === null) { | ||
| return false; | ||
| } | ||
| if (capture !== undefined) { | ||
| captures[capture] = m; | ||
| } | ||
| return true; | ||
| }; | ||
| } | ||
| invariant( | ||
| false, | ||
| 'sel.styleAny(%s, ...) requires a string or a RegExp', | ||
| JSON.stringify(prop), | ||
| ); | ||
| } | ||
| const TEXT_SELECTOR_IMPL: SelectorImpl = { | ||
| kind: 'text', | ||
| predicate: isDOMTextNode, | ||
| tags: new Set(), | ||
| }; | ||
| // The `as` cast is needed because `CompiledSelector` is an opaque | ||
| // branded interface — neither the object literal nor a typed const can | ||
| // declare the internal `IMPL` symbol without exposing it. | ||
| const TEXT_SELECTOR = {[IMPL]: TEXT_SELECTOR_IMPL} as CompiledSelector<Text>; | ||
| const COMMENT_SELECTOR_IMPL: SelectorImpl = { | ||
| kind: 'comment', | ||
| predicate: node => node.nodeType === 8 /* COMMENT_NODE */, | ||
| tags: new Set(), | ||
| }; | ||
| const COMMENT_SELECTOR = { | ||
| [IMPL]: COMMENT_SELECTOR_IMPL, | ||
| } as CompiledSelector<Comment>; | ||
| /** | ||
| * Combinator API for building {@link CompiledSelector}s. The public | ||
| * `sel` is augmented from this in `./index.ts` (where the CSS parser is | ||
| * available without a circular import); consumers outside `@lexical/html` | ||
| * should always import the public `sel` from the package root. | ||
| * | ||
| * @internal | ||
| */ | ||
| export const selBase = { | ||
| /** Match any {@link HTMLElement}. */ | ||
| any(): ElementSelectorBuilder<HTMLElement> { | ||
| return buildSelector(new Set(), []); | ||
| }, | ||
| /** Match DOM {@link Comment} nodes. */ | ||
| comment(): CompiledSelector<Comment> { | ||
| return COMMENT_SELECTOR; | ||
| }, | ||
| /** | ||
| * Match by tag name(s). With one literal tag the element type is narrowed | ||
| * (e.g. `'a' → HTMLAnchorElement`); with multiple, it is the union of | ||
| * their `HTMLElementTagNameMap` entries. | ||
| */ | ||
| tag<const Tags extends readonly string[]>( | ||
| ...tags: Tags | ||
| ): ElementSelectorBuilder< | ||
| Tags[number] extends keyof HTMLElementTagNameMap | ||
| ? HTMLElementTagNameMap[Tags[number]] | ||
| : HTMLElement | ||
| > { | ||
| invariant(tags.length > 0, 'sel.tag() requires at least one tag name'); | ||
| const upper = new Set<string>(); | ||
| for (const t of tags) { | ||
| upper.add(t.toUpperCase()); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| return buildSelector(upper, []) as any; | ||
| }, | ||
| /** Match DOM {@link Text} nodes. */ | ||
| text(): CompiledSelector<Text> { | ||
| return TEXT_SELECTOR; | ||
| }, | ||
| } as const; | ||
| /** | ||
| * Cross-frame-safe replacement for `node instanceof HTMLXxxElement`. Returns | ||
| * true when `node` is an HTMLElement whose `nodeName` equals `tag` (compared | ||
| * case-insensitively). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function isElementOfTag<T extends keyof HTMLElementTagNameMap>( | ||
| node: Node, | ||
| tag: T, | ||
| ): node is HTMLElementTagNameMap[T] { | ||
| return isHTMLElement(node) && node.nodeName === tag.toUpperCase(); | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {DOMImportContextSymbol} from '../constants'; | ||
| import type { | ||
| AnyContextConfigPairOrUpdater, | ||
| ContextConfig, | ||
| ContextRecord, | ||
| } from '../types'; | ||
| import type {CompiledOverlayRules} from './defineOverlayRules'; | ||
| import type {LexicalNode} from 'lexical'; | ||
| /** | ||
| * Phantom-typed branding so consumers cannot construct or mutate a | ||
| * {@link CompiledSelector} directly; the only way to obtain one is via the | ||
| * {@link sel} builder or {@link parseSelector}. The actual runtime shape is | ||
| * an internal implementation detail (see `./sel`). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export declare const NodeBrand: unique symbol; | ||
| /** @experimental */ | ||
| export declare const CaptureBrand: unique symbol; | ||
| /** | ||
| * An opaque, compiled selector used as the `match` field of a | ||
| * {@link DOMImportRule}. The two phantom type parameters carry the matched | ||
| * Node subtype (`N`) and a record of named regex captures (`C`) so the | ||
| * importer body gets correctly-typed `ctx` and `node` arguments without | ||
| * casts. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface CompiledSelector< | ||
| N extends Node = Node, | ||
| C extends Record<string, RegExpMatchArray> = Record<string, RegExpMatchArray>, | ||
| > { | ||
| readonly [NodeBrand]?: N; | ||
| readonly [CaptureBrand]?: C; | ||
| } | ||
| /** | ||
| * The Node subtype matched by a selector (e.g. `HTMLAnchorElement` for | ||
| * `sel.tag('a')`, `Text` for `sel.text()`). | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type NodeOfSelector<S> = | ||
| S extends CompiledSelector<infer N, Record<string, RegExpMatchArray>> | ||
| ? N | ||
| : Node; | ||
| /** | ||
| * The named-capture map for a selector. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type CapturesOfSelector<S> = | ||
| S extends CompiledSelector<Node, infer C> ? C : Record<string, never>; | ||
| /** | ||
| * Options bag for {@link ElementSelectorBuilder.attr} when the value is a | ||
| * regex. Future options will be added here without breaking existing | ||
| * call-sites. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface AttrMatchOptions<K extends string = string> { | ||
| /** | ||
| * If provided, the {@link RegExpMatchArray} from the successful match is | ||
| * stored on `ctx.captures[capture]` for the importer to consume — saving | ||
| * a second regex execution. | ||
| */ | ||
| readonly capture?: K; | ||
| } | ||
| /** | ||
| * Options bag for {@link ElementSelectorBuilder.styleAny} when the value is a | ||
| * regex. See {@link AttrMatchOptions} for capture semantics. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface StyleMatchOptions<K extends string = string> { | ||
| readonly capture?: K; | ||
| } | ||
| /** | ||
| * Fluent builder for an element selector. The two type parameters carry the | ||
| * matched element type and the named-capture map; each call refines them. | ||
| * | ||
| * The builder itself implements {@link CompiledSelector} so it can be used | ||
| * directly as the `match` field of a rule — no `.build()` call needed. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ElementSelectorBuilder< | ||
| E extends HTMLElement, | ||
| C extends Record<string, RegExpMatchArray> = Record<string, never>, | ||
| > extends CompiledSelector<E, C> { | ||
| /** Require every listed class to be present on the element. */ | ||
| classAll(...classes: readonly string[]): ElementSelectorBuilder<E, C>; | ||
| /** Require at least one of the listed classes to be present. */ | ||
| classAny(...classes: readonly string[]): ElementSelectorBuilder<E, C>; | ||
| /** Require the attribute to be present (any value). */ | ||
| attr(name: string, value: true): ElementSelectorBuilder<E, C>; | ||
| /** Require the attribute to equal the given string. */ | ||
| attr(name: string, value: string): ElementSelectorBuilder<E, C>; | ||
| /** | ||
| * Require the attribute to match the given regex. With | ||
| * `{capture: 'name'}` the match result is exposed on | ||
| * `ctx.captures.name`. | ||
| */ | ||
| attr<const O extends AttrMatchOptions>( | ||
| name: string, | ||
| value: RegExp, | ||
| options?: O, | ||
| ): ElementSelectorBuilder< | ||
| E, | ||
| O extends {capture: infer K} ? C & Record<K & string, RegExpMatchArray> : C | ||
| >; | ||
| /** Require the inline-style declaration to equal `value`. */ | ||
| styleAny(prop: string, value: string): ElementSelectorBuilder<E, C>; | ||
| /** Require the inline-style declaration to match `value`. */ | ||
| styleAny<const O extends StyleMatchOptions>( | ||
| prop: string, | ||
| value: RegExp, | ||
| options?: O, | ||
| ): ElementSelectorBuilder< | ||
| E, | ||
| O extends {capture: infer K} ? C & Record<K & string, RegExpMatchArray> : C | ||
| >; | ||
| } | ||
| /** | ||
| * Argument to {@link DOMImportContext.branch} / `$importChildren({context})` | ||
| * — see {@link ContextConfigPair} / {@link ContextConfigUpdater}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type ImportContextPairOrUpdater = AnyContextConfigPairOrUpdater< | ||
| typeof DOMImportContextSymbol | ||
| >; | ||
| /** | ||
| * A typed context-state key for the import pipeline. Create with | ||
| * {@link createImportState}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type ImportStateConfig<V> = ContextConfig< | ||
| typeof DOMImportContextSymbol, | ||
| V | ||
| >; | ||
| /** | ||
| * A mutable, document-order-shared store for the import pipeline. Lets a | ||
| * rule visited early in the document write information that rules visited | ||
| * later can read — e.g. parse `<style>` or `<meta>` and influence | ||
| * subsequent matching. | ||
| * | ||
| * Implemented as the root-layer {@link ContextRecord} of the import walk: | ||
| * `ctx.session.set(cfg, v)` mutates the slot on that root record, and | ||
| * every unshadowed `ctx.get(cfg)` read in any branch picks it up. A | ||
| * `$importChildren({context: [...]})` branch that explicitly writes the | ||
| * same slot shadows the session value for the duration of that branch. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ImportSession { | ||
| /** Read the current value, returning the config's default if unset. */ | ||
| get<V>(cfg: ImportStateConfig<V>): V; | ||
| /** Write `value` into the slot. */ | ||
| set<V>(cfg: ImportStateConfig<V>, value: V): void; | ||
| /** Read-modify-write. */ | ||
| update<V>(cfg: ImportStateConfig<V>, updater: (prev: V) => V): void; | ||
| /** Returns `true` if the slot has been written since session creation. */ | ||
| has<V>(cfg: ImportStateConfig<V>): boolean; | ||
| } | ||
| /** | ||
| * Context exposed to a rule's `$import` function. Mirrors the existing render | ||
| * context (see {@link RenderContext}) but is import-scoped. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportContext< | ||
| C extends Record<string, RegExpMatchArray> = Record<string, never>, | ||
| > { | ||
| /** Captures from this rule's selector. Fresh per rule invocation. */ | ||
| readonly captures: Readonly<C>; | ||
| /** | ||
| * Mutable, document-order-shared store. Use to make information from | ||
| * earlier-visited nodes available to later-visited ones (e.g. a | ||
| * `<style>` or `<meta>` at the top of the document influencing how | ||
| * later elements are interpreted). One {@link ImportSession} instance | ||
| * is created per top-level `$generateNodesFromDOM` call and is shared | ||
| * across all recursive `$importChildren` / `$importOne` invocations. | ||
| */ | ||
| readonly session: ImportSession; | ||
| /** Read a typed context value. */ | ||
| get<V>(cfg: ImportStateConfig<V>): V; | ||
| /** | ||
| * Recursively import every child of `parent` and return the produced | ||
| * lexical nodes, optionally enforcing a {@link ChildSchema} and/or | ||
| * branching the import context for the duration of the call (via | ||
| * `opts.context`). | ||
| */ | ||
| $importChildren(parent: ParentNode, opts?: ImportChildrenOpts): LexicalNode[]; | ||
| /** | ||
| * Recursively import a single DOM node. | ||
| */ | ||
| $importOne(node: Node, opts?: ImportNodeOpts): LexicalNode[]; | ||
| } | ||
| /** | ||
| * Options accepted by {@link DOMImportContext.$importChildren}. The combination | ||
| * of `schema`, `$onChild`, and `$after` is sufficient to express every | ||
| * child-handling pattern in the legacy `forChild` / `after` / wrap-continuous | ||
| * machinery. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ImportChildrenOpts { | ||
| /** | ||
| * How to validate and (re)package produced children. Defaults to whichever | ||
| * schema the parent's importer passed; the top-level entry uses | ||
| * {@link BlockSchema}. | ||
| */ | ||
| readonly schema?: ChildSchema; | ||
| /** | ||
| * Called for each produced lexical child immediately after its rule | ||
| * returned, with the chance to substitute or drop it. Equivalent to the | ||
| * old `forChild` hook but scoped to one `$importChildren` call. | ||
| */ | ||
| readonly $onChild?: (child: LexicalNode) => LexicalNode | null | undefined; | ||
| /** | ||
| * Called once with the full child array after all DOM children have been | ||
| * recursively imported but before {@link ChildSchema.$packageRun} is | ||
| * applied. Equivalent to the old `after` hook. | ||
| */ | ||
| readonly $after?: (children: LexicalNode[]) => LexicalNode[]; | ||
| /** Context overrides scoped to the children traversal. */ | ||
| readonly context?: readonly ImportContextPairOrUpdater[]; | ||
| /** | ||
| * Additional {@link DOMImportRule}s active only for this children | ||
| * traversal (and any nested `$importChildren` calls that don't push | ||
| * their own overlay). The overlay is checked BEFORE the main | ||
| * dispatcher, so its rules take precedence; calling `$next()` from an | ||
| * overlay rule falls through to the next overlay-or-main rule. | ||
| * | ||
| * Use this to scope cost-bearing rules to where they apply. For | ||
| * example, a GitHub code-table rule installs an overlay that | ||
| * unwraps `<tr>` / `<td>` inside the table, without paying that | ||
| * predicate cost on every other `<tr>` / `<td>` paste. | ||
| * | ||
| * The value must be produced by | ||
| * {@link defineOverlayRules}; this forces the dispatcher to be | ||
| * compiled once at module scope and reused across | ||
| * `$importChildren` calls, instead of being recompiled per invocation. | ||
| */ | ||
| readonly rules?: CompiledOverlayRules; | ||
| } | ||
| /** @experimental */ | ||
| export interface ImportNodeOpts { | ||
| readonly context?: readonly ImportContextPairOrUpdater[]; | ||
| } | ||
| /** | ||
| * A {@link ChildSchema} encodes which lexical nodes a parent accepts as | ||
| * children and how to package or reject the rest. The legacy | ||
| * `wrapContinuousInlines` / `ArtificialNode__DO_NOT_USE` logic is the | ||
| * `BlockSchema` and `NestedBlockSchema` cases of this primitive. | ||
| * | ||
| * A schema only controls how the *children* are assembled before being | ||
| * appended to the parent the calling rule already chose. It cannot | ||
| * change the parent itself. Cases where the parent's shape needs to | ||
| * change in response to its children — e.g. an inline `<a>` that | ||
| * encloses a block `<h1>`, which must be lifted so the heading takes | ||
| * the link's place and the link is redistributed onto the heading's | ||
| * inline contents — belong in the rule body, not the schema. See the | ||
| * "Lifting blocks out of an inline parent" section of the docs. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface ChildSchema { | ||
| /** Optional name for debug output. */ | ||
| readonly name?: string; | ||
| /** | ||
| * Returns `true` if `child` is a valid child of `parent` in this position. | ||
| */ | ||
| $accepts(child: LexicalNode, parent: LexicalNode | null): boolean; | ||
| /** | ||
| * Package a maximal run of non-accepted siblings into zero or more | ||
| * accepted nodes. The returned nodes replace the rejected run at the | ||
| * same position in the child list and are not re-checked against | ||
| * `$accepts` — the caller is trusted to return valid children. If | ||
| * omitted, or if it returns an empty array, {@link ChildSchema.onReject} | ||
| * is consulted instead. | ||
| */ | ||
| $packageRun?( | ||
| rejected: LexicalNode[], | ||
| parent: LexicalNode | null, | ||
| domParent: Node | null, | ||
| ): LexicalNode[]; | ||
| /** | ||
| * Fallback strategy for a run of non-accepted children when | ||
| * `$packageRun` is missing or returns an empty array: | ||
| * | ||
| * - `'drop'` (default) — silently discards the run. Use when the | ||
| * schema is strict and rejected content is meaningless in this | ||
| * position (e.g. text between table rows). | ||
| * - `'hoist'` — emits the rejected nodes unchanged at the same | ||
| * position in the assembled child list. The caller's parent then | ||
| * receives them as-is, which is only useful if the calling rule | ||
| * intends to surface mixed content to *its* parent (a less common | ||
| * pattern; usually `$packageRun` should re-shape the run first). | ||
| * | ||
| * `'hoist'` does NOT lift the run all the way up out of the calling | ||
| * rule's parent — that requires the rule itself to detect the | ||
| * situation and emit a different parent structure (see the | ||
| * "Lifting blocks out of an inline parent" section). | ||
| */ | ||
| readonly onReject?: 'hoist' | 'drop'; | ||
| /** | ||
| * Final pass over the assembled child list (after `$packageRun`). Returns | ||
| * the children to actually attach. Use to enforce structural invariants | ||
| * (e.g. drop empty runs, pad short table rows). | ||
| */ | ||
| $finalize?( | ||
| children: LexicalNode[], | ||
| parent: LexicalNode | null, | ||
| ): LexicalNode[]; | ||
| } | ||
| /** | ||
| * The middleware signature of an import rule. Call `$next()` to delegate to | ||
| * the next-matching rule for this node (returning its result, which may then | ||
| * be inspected or wrapped); return `[]` to drop the node. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type DOMImportFn< | ||
| E extends Node, | ||
| C extends Record<string, RegExpMatchArray> = Record<string, never>, | ||
| > = ( | ||
| ctx: DOMImportContext<C>, | ||
| node: E, | ||
| $next: () => readonly LexicalNode[], | ||
| ) => readonly LexicalNode[]; | ||
| /** | ||
| * An importer for a DOM node, dispatched by `match` and implemented by | ||
| * `$import`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportRule<S extends CompiledSelector = CompiledSelector> { | ||
| /** | ||
| * Optional identifier surfaced in dev-mode logs, error messages, and | ||
| * introspection devtools. Convention: `'@scope/package/rule-id'` for | ||
| * library rules. | ||
| */ | ||
| readonly name?: string; | ||
| /** A {@link CompiledSelector} produced by the {@link sel} builder. */ | ||
| readonly match: S; | ||
| /** Middleware that converts the matched DOM node into lexical nodes. */ | ||
| readonly $import: DOMImportFn<NodeOfSelector<S>, CapturesOfSelector<S>>; | ||
| } | ||
| /** @experimental */ | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| export type AnyDOMImportRule = DOMImportRule<any>; | ||
| /** | ||
| * Context exposed to a {@link DOMPreprocessFn}. Lets the preprocessor: | ||
| * | ||
| * - Write to the per-import {@link ImportSession} (the same | ||
| * `ctx.session` rules see during the walk). Writes mutate the | ||
| * root-layer context record, so they are visible to every scoped | ||
| * `ctx.get(cfg)` read that hasn't been shadowed by a branch. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMPreprocessContext { | ||
| /** | ||
| * Document-order-shared store, same instance as `ctx.session` in | ||
| * rules. Use to pass info from the DOM preprocess phase (e.g. | ||
| * inspected `<meta>` tags, collected `<style>` text) to the | ||
| * importer rules. | ||
| */ | ||
| readonly session: ImportSession; | ||
| } | ||
| /** | ||
| * A middleware step in the DOM-preprocess chain. Runs before walking | ||
| * begins and may: | ||
| * | ||
| * - Mutate the input DOM in place (e.g. inline stylesheets, strip | ||
| * unsafe elements, normalize attributes). | ||
| * - Write to {@link DOMPreprocessContext.session} for rules to read | ||
| * (and for unshadowed scoped reads to pick up). | ||
| * - Call `$next()` to defer to the next-lower preprocessor in the | ||
| * stack; omit the call to short-circuit and skip the rest. | ||
| * | ||
| * The preprocess phase runs inside the same editor read / update | ||
| * context as the walk that follows, so a preprocess function may call | ||
| * `$`-prefixed Lexical APIs (e.g. `$getState`, `$getRoot`) as needed. | ||
| * The `$next` parameter is named with a `$` prefix to make that | ||
| * editor-context expectation visible to readers and lints. | ||
| * | ||
| * Append-style merge applies: an extension's preprocessors are appended | ||
| * to the existing stack, so later-registered preprocessors run first | ||
| * and may delegate to earlier (lower-priority) ones via `$next()`. Same | ||
| * convention as {@link ExportMimeTypeFunction} on the export side. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export type DOMPreprocessFn = ( | ||
| dom: Document | ParentNode, | ||
| ctx: DOMPreprocessContext, | ||
| $next: () => void, | ||
| ) => void; | ||
| /** | ||
| * Per-call options to the extension's `$generateNodesFromDOM`. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface GenerateNodesFromDOMOptions { | ||
| /** | ||
| * Context pairs/updaters applied for the duration of this import only — | ||
| * use to communicate per-call info such as the {@link ImportSource}. | ||
| */ | ||
| readonly context?: readonly ImportContextPairOrUpdater[]; | ||
| /** | ||
| * Additional preprocessors to run on this call only, on top of the | ||
| * extension's configured {@link DOMImportConfig.preprocess}. Per-call | ||
| * preprocessors run AFTER the configured ones. | ||
| */ | ||
| readonly preprocess?: readonly DOMPreprocessFn[]; | ||
| } | ||
| /** | ||
| * Output of {@link DOMImportExtension}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export interface DOMImportExtensionOutput { | ||
| /** | ||
| * Convert a {@link Document} or {@link ParentNode} into lexical nodes, | ||
| * using the dispatcher compiled from this extension's configured | ||
| * {@link DOMImportRule}s. | ||
| * | ||
| * Must be called within an `editor.update()` or `editor.read()` because | ||
| * the importers may invoke `$create...` helpers. | ||
| */ | ||
| $generateNodesFromDOM( | ||
| dom: Document | ParentNode, | ||
| options?: GenerateNodesFromDOMOptions, | ||
| ): LexicalNode[]; | ||
| /** @internal */ | ||
| readonly defaults: undefined | ContextRecord<typeof DOMImportContextSymbol>; | ||
| } |
+555
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { | ||
| BaseSelection, | ||
| DOMChildConversion, | ||
| DOMConversion, | ||
| DOMConversionFn, | ||
| EditorDOMRenderConfig, | ||
| ElementFormatType, | ||
| LexicalEditor, | ||
| LexicalNode, | ||
| } from 'lexical'; | ||
| import invariant from '@lexical/internal/invariant'; | ||
| import {$sliceSelectedTextNodeContent} from '@lexical/selection'; | ||
| import { | ||
| $createLineBreakNode, | ||
| $createParagraphNode, | ||
| $getEditor, | ||
| $getEditorDOMRenderConfig, | ||
| $getRoot, | ||
| $isBlockElementNode, | ||
| $isElementNode, | ||
| $isRootOrShadowRoot, | ||
| $isTextNode, | ||
| ArtificialNode__DO_NOT_USE, | ||
| ElementNode, | ||
| isBlockDomNode, | ||
| isDocumentFragment, | ||
| isDOMDocumentNode, | ||
| isHTMLElement, | ||
| isInlineDomNode, | ||
| } from 'lexical'; | ||
| import {contextValue} from './ContextRecord'; | ||
| import {$inlineStylesFromStyleSheetsDOM} from './import/inlineStylesFromStyleSheets'; | ||
| import { | ||
| $getSessionDOMRenderConfig, | ||
| $withRenderContext, | ||
| RenderContextExport, | ||
| RenderContextRoot, | ||
| } from './RenderContext'; | ||
| export {contextUpdater, contextValue} from './ContextRecord'; | ||
| export {domOverride} from './domOverride'; | ||
| export {DOMRenderExtension} from './DOMRenderExtension'; | ||
| export type { | ||
| AnyDOMImportRule, | ||
| AttrMatchOptions, | ||
| CapturesOfSelector, | ||
| ChildSchema, | ||
| CompiledOverlayRules, | ||
| CompiledSelector, | ||
| DOMImportContext, | ||
| DOMImportExtensionOutput, | ||
| DOMImportFn, | ||
| DOMImportRule, | ||
| DOMImportRuleEntry, | ||
| DOMPreprocessContext, | ||
| DOMPreprocessFn, | ||
| ElementSelectorBuilder, | ||
| GenerateNodesFromDOMOptions, | ||
| ImportChildrenOpts, | ||
| ImportContextPairOrUpdater, | ||
| ImportNodeOpts, | ||
| ImportSession, | ||
| ImportStateConfig, | ||
| NodeOfSelector, | ||
| StyleMatchOptions, | ||
| } from './import'; | ||
| export { | ||
| $distributeInlineWrapper, | ||
| $generateNodesFromDOMViaExtension, | ||
| $getImportContextValue, | ||
| $inlineStylesFromStyleSheets, | ||
| $isBlockLevel, | ||
| $withImportContext, | ||
| BlockSchema, | ||
| CoreImportExtension, | ||
| CoreImportRules, | ||
| createImportState, | ||
| defaultIsInline, | ||
| defaultPreservesWhitespace, | ||
| defineImportRule, | ||
| defineOverlayRules, | ||
| type DOMImportConfig, | ||
| DOMImportExtension, | ||
| HorizontalRuleImportExtension, | ||
| HorizontalRuleImportRules, | ||
| ImportOverlays, | ||
| ImportSource, | ||
| ImportSourceDataTransfer, | ||
| type ImportSourceKind, | ||
| ImportTextFormat, | ||
| ImportTextStyle, | ||
| ImportWhitespaceConfig, | ||
| InlineSchema, | ||
| isElementOfTag, | ||
| type IsInlineForWhitespace, | ||
| type IsPreserveWhitespaceDom, | ||
| NestedBlockSchema, | ||
| parseSelector, | ||
| RootSchema, | ||
| sel, | ||
| type WhitespaceImportConfig, | ||
| } from './import'; | ||
| export { | ||
| $getRenderContextValue, | ||
| $getSessionDOMRenderConfig, | ||
| $setRenderContextValue, | ||
| $updateRenderContextValue, | ||
| $withRenderContext, | ||
| createRenderState, | ||
| RenderContextExport, | ||
| RenderContextRoot, | ||
| } from './RenderContext'; | ||
| export type { | ||
| AnyDOMRenderMatch, | ||
| AnyRenderStateConfig, | ||
| AnyRenderStateConfigPairOrUpdater, | ||
| ContextPairOrUpdater, | ||
| DOMOverrideOptions, | ||
| DOMRenderConfig, | ||
| DOMRenderExtensionOutput, | ||
| DOMRenderMatch, | ||
| DOMRenderMatchConfig, | ||
| NodeMatch, | ||
| RenderContextReader, | ||
| } from './types'; | ||
| const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']); | ||
| /** | ||
| * How you parse your html string to get a document is left up to you. In the browser you can use the native | ||
| * DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom | ||
| * or an equivalent library and pass in the document here. | ||
| */ | ||
| export function $generateNodesFromDOM( | ||
| editor: LexicalEditor, | ||
| dom: Document | ParentNode, | ||
| ): Array<LexicalNode> { | ||
| $inlineStylesFromStyleSheetsDOM(dom); | ||
| const elements = isDOMDocumentNode(dom) | ||
| ? dom.body.childNodes | ||
| : dom.childNodes; | ||
| const lexicalNodes: Array<LexicalNode> = []; | ||
| const allArtificialNodes: Array<ArtificialNode__DO_NOT_USE> = []; | ||
| for (const element of elements) { | ||
| if (!IGNORE_TAGS.has(element.nodeName)) { | ||
| const lexicalNode = $createNodesFromDOM( | ||
| element, | ||
| editor, | ||
| allArtificialNodes, | ||
| false, | ||
| ); | ||
| if (lexicalNode !== null) { | ||
| for (const node of lexicalNode) { | ||
| lexicalNodes.push(node); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| $unwrapArtificialNodes(allArtificialNodes); | ||
| return lexicalNodes; | ||
| } | ||
| /** | ||
| * Generate DOM nodes from the editor state into the given container element, | ||
| * using the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| export function $generateDOMFromNodes<T extends HTMLElement | DocumentFragment>( | ||
| container: T, | ||
| selection: null | BaseSelection = null, | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): T { | ||
| return $withRenderContext( | ||
| [contextValue(RenderContextExport, true)], | ||
| editor, | ||
| )(() => { | ||
| const root = $getRoot(); | ||
| const domConfig = $getSessionDOMRenderConfig(editor); | ||
| const parentElementAppend = container.append.bind(container); | ||
| for (const topLevelNode of root.getChildren()) { | ||
| $appendNodesToHTML( | ||
| editor, | ||
| topLevelNode, | ||
| parentElementAppend, | ||
| selection, | ||
| domConfig, | ||
| ); | ||
| } | ||
| return container; | ||
| }); | ||
| } | ||
| /** | ||
| * Generate DOM nodes from a root node into the given container element, | ||
| * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| export function $generateDOMFromRoot<T extends HTMLElement | DocumentFragment>( | ||
| container: T, | ||
| root: LexicalNode = $getRoot(), | ||
| ): T { | ||
| const editor = $getEditor(); | ||
| return $withRenderContext( | ||
| [ | ||
| contextValue(RenderContextExport, true), | ||
| contextValue(RenderContextRoot, true), | ||
| ], | ||
| editor, | ||
| )(() => { | ||
| const selection = null; | ||
| const domConfig = $getSessionDOMRenderConfig(editor); | ||
| const parentElementAppend = container.append.bind(container); | ||
| $appendNodesToHTML(editor, root, parentElementAppend, selection, domConfig); | ||
| return container; | ||
| }); | ||
| } | ||
| /** | ||
| * Generate an HTML string from the editor's current state (or `selection` | ||
| * if provided). | ||
| * | ||
| * Must be called inside an active editor scope — i.e. `editor.update(...)`, | ||
| * `editor.read(...)`, or `editor.getEditorState().read(callback, {editor})`. | ||
| * The legacy `editor.getEditorState().read(callback)` call (without the | ||
| * `{editor}` option) does not set an active editor and is not supported; | ||
| * `editor.read(...)` is the drop-in replacement. | ||
| */ | ||
| export function $generateHtmlFromNodes( | ||
| editor: LexicalEditor, | ||
| selection: BaseSelection | null = null, | ||
| ): string { | ||
| if ( | ||
| typeof document === 'undefined' || | ||
| (typeof window === 'undefined' && typeof global.window === 'undefined') | ||
| ) { | ||
| invariant( | ||
| false, | ||
| '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.', | ||
| ); | ||
| } | ||
| return $generateDOMFromNodes(document.createElement('div'), selection, editor) | ||
| .innerHTML; | ||
| } | ||
| function $appendNodesToHTML( | ||
| editor: LexicalEditor, | ||
| currentNode: LexicalNode, | ||
| parentElementAppend: (element: Node) => void, | ||
| selection: BaseSelection | null = null, | ||
| domConfig: EditorDOMRenderConfig = $getEditorDOMRenderConfig(editor), | ||
| ): boolean { | ||
| let shouldInclude = domConfig.$shouldInclude(currentNode, selection, editor); | ||
| const shouldExclude = domConfig.$shouldExclude( | ||
| currentNode, | ||
| selection, | ||
| editor, | ||
| ); | ||
| let target = currentNode; | ||
| if (selection !== null && $isTextNode(currentNode)) { | ||
| target = $sliceSelectedTextNodeContent(selection, currentNode, 'clone'); | ||
| } | ||
| const exportProps = domConfig.$exportDOM(target, editor); | ||
| const {element, after, append, $getChildNodes} = exportProps; | ||
| if (!element) { | ||
| return false; | ||
| } | ||
| const fragment = document.createDocumentFragment(); | ||
| const children = $getChildNodes | ||
| ? $getChildNodes() | ||
| : $isElementNode(target) | ||
| ? target.getChildren() | ||
| : []; | ||
| const fragmentAppend = fragment.append.bind(fragment); | ||
| for (const childNode of children) { | ||
| const shouldIncludeChild = $appendNodesToHTML( | ||
| editor, | ||
| childNode, | ||
| fragmentAppend, | ||
| selection, | ||
| domConfig, | ||
| ); | ||
| if ( | ||
| !shouldInclude && | ||
| shouldIncludeChild && | ||
| domConfig.$extractWithChild( | ||
| currentNode, | ||
| childNode, | ||
| selection, | ||
| 'html', | ||
| editor, | ||
| ) | ||
| ) { | ||
| shouldInclude = true; | ||
| } | ||
| } | ||
| if (shouldInclude && !shouldExclude) { | ||
| if (isHTMLElement(element) || isDocumentFragment(element)) { | ||
| if (append) { | ||
| append(fragment); | ||
| } else { | ||
| element.append(fragment); | ||
| } | ||
| } | ||
| parentElementAppend(element); | ||
| if (after) { | ||
| const newElement = after.call(target, element); | ||
| if (newElement) { | ||
| if (isDocumentFragment(element)) { | ||
| element.replaceChildren(newElement); | ||
| } else { | ||
| element.replaceWith(newElement); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| parentElementAppend(fragment); | ||
| } | ||
| return shouldInclude; | ||
| } | ||
| function getConversionFunction( | ||
| domNode: Node, | ||
| editor: LexicalEditor, | ||
| ): DOMConversionFn | null { | ||
| const {nodeName} = domNode; | ||
| const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase()); | ||
| let currentConversion: DOMConversion | null = null; | ||
| if (cachedConversions !== undefined) { | ||
| for (const cachedConversion of cachedConversions) { | ||
| const domConversion = cachedConversion(domNode); | ||
| if ( | ||
| domConversion !== null && | ||
| (currentConversion === null || | ||
| // Given equal priority, prefer the last registered importer | ||
| // which is typically an application custom node or HTMLConfig['import'] | ||
| (currentConversion.priority || 0) <= (domConversion.priority || 0)) | ||
| ) { | ||
| currentConversion = domConversion; | ||
| } | ||
| } | ||
| } | ||
| return currentConversion !== null ? currentConversion.conversion : null; | ||
| } | ||
| function $createNodesFromDOM( | ||
| node: Node, | ||
| editor: LexicalEditor, | ||
| allArtificialNodes: Array<ArtificialNode__DO_NOT_USE>, | ||
| hasBlockAncestorLexicalNode: boolean, | ||
| forChildMap: Map<string, DOMChildConversion> = new Map(), | ||
| parentLexicalNode?: LexicalNode | null | undefined, | ||
| ): Array<LexicalNode> { | ||
| const lexicalNodes: Array<LexicalNode> = []; | ||
| if (IGNORE_TAGS.has(node.nodeName)) { | ||
| return lexicalNodes; | ||
| } | ||
| let currentLexicalNode = null; | ||
| const transformFunction = getConversionFunction(node, editor); | ||
| const transformOutput = transformFunction | ||
| ? transformFunction(node as HTMLElement) | ||
| : null; | ||
| let postTransform = null; | ||
| if (transformOutput !== null) { | ||
| postTransform = transformOutput.after; | ||
| const transformNodes = transformOutput.node; | ||
| currentLexicalNode = Array.isArray(transformNodes) | ||
| ? transformNodes[transformNodes.length - 1] | ||
| : transformNodes; | ||
| if (currentLexicalNode !== null) { | ||
| for (const [, forChildFunction] of forChildMap) { | ||
| currentLexicalNode = forChildFunction( | ||
| currentLexicalNode, | ||
| parentLexicalNode, | ||
| ); | ||
| if (!currentLexicalNode) { | ||
| break; | ||
| } | ||
| } | ||
| if (currentLexicalNode) { | ||
| lexicalNodes.push( | ||
| ...(Array.isArray(transformNodes) | ||
| ? transformNodes | ||
| : [currentLexicalNode]), | ||
| ); | ||
| } | ||
| } | ||
| if (transformOutput.forChild != null) { | ||
| forChildMap.set(node.nodeName, transformOutput.forChild); | ||
| } | ||
| } | ||
| // If the DOM node doesn't have a transformer, we don't know what | ||
| // to do with it but we still need to process any childNodes. | ||
| const children = node.childNodes; | ||
| let childLexicalNodes = []; | ||
| const hasBlockAncestorLexicalNodeForChildren = | ||
| currentLexicalNode != null && $isRootOrShadowRoot(currentLexicalNode) | ||
| ? false | ||
| : (currentLexicalNode != null && | ||
| $isBlockElementNode(currentLexicalNode)) || | ||
| hasBlockAncestorLexicalNode; | ||
| for (let i = 0; i < children.length; i++) { | ||
| childLexicalNodes.push( | ||
| ...$createNodesFromDOM( | ||
| children[i], | ||
| editor, | ||
| allArtificialNodes, | ||
| hasBlockAncestorLexicalNodeForChildren, | ||
| new Map(forChildMap), | ||
| currentLexicalNode, | ||
| ), | ||
| ); | ||
| } | ||
| if (postTransform != null) { | ||
| childLexicalNodes = postTransform(childLexicalNodes); | ||
| } | ||
| if (isBlockDomNode(node)) { | ||
| if (!hasBlockAncestorLexicalNodeForChildren) { | ||
| childLexicalNodes = wrapContinuousInlines( | ||
| node, | ||
| childLexicalNodes, | ||
| $createParagraphNode, | ||
| ); | ||
| } else { | ||
| childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => { | ||
| const artificialNode = new ArtificialNode__DO_NOT_USE(); | ||
| allArtificialNodes.push(artificialNode); | ||
| return artificialNode; | ||
| }); | ||
| } | ||
| } | ||
| if (currentLexicalNode == null) { | ||
| if (childLexicalNodes.length > 0) { | ||
| // If it hasn't been converted to a LexicalNode, we hoist its children | ||
| // up to the same level as it. | ||
| for (const childNode of childLexicalNodes) { | ||
| lexicalNodes.push(childNode); | ||
| } | ||
| } else { | ||
| if (isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) { | ||
| // Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes | ||
| lexicalNodes.push($createLineBreakNode()); | ||
| } | ||
| } | ||
| } else { | ||
| if ($isElementNode(currentLexicalNode)) { | ||
| // If the current node is a ElementNode after conversion, | ||
| // we can append all the children to it. | ||
| currentLexicalNode.append(...childLexicalNodes); | ||
| } | ||
| } | ||
| return lexicalNodes; | ||
| } | ||
| function wrapContinuousInlines( | ||
| domNode: Node, | ||
| nodes: Array<LexicalNode>, | ||
| createWrapperFn: () => ElementNode, | ||
| ): Array<LexicalNode> { | ||
| const textAlign = (domNode as HTMLElement).style | ||
| .textAlign as ElementFormatType; | ||
| const out: Array<LexicalNode> = []; | ||
| let continuousInlines: Array<LexicalNode> = []; | ||
| // wrap contiguous inline child nodes in para | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if ($isBlockElementNode(node)) { | ||
| if (textAlign && !node.getFormat()) { | ||
| node.setFormat(textAlign); | ||
| } | ||
| out.push(node); | ||
| } else { | ||
| continuousInlines.push(node); | ||
| if ( | ||
| i === nodes.length - 1 || | ||
| (i < nodes.length - 1 && $isBlockElementNode(nodes[i + 1])) | ||
| ) { | ||
| const wrapper = createWrapperFn(); | ||
| wrapper.setFormat(textAlign); | ||
| wrapper.append(...continuousInlines); | ||
| out.push(wrapper); | ||
| continuousInlines = []; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function $unwrapArtificialNodes( | ||
| allArtificialNodes: Array<ArtificialNode__DO_NOT_USE>, | ||
| ) { | ||
| // Replace artificial node with its children, inserting a linebreak | ||
| // between adjacent artificial nodes | ||
| for (const node of allArtificialNodes) { | ||
| if ( | ||
| node.getParent() && | ||
| node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE | ||
| ) { | ||
| node.insertAfter($createLineBreakNode()); | ||
| } | ||
| } | ||
| for (const node of allArtificialNodes) { | ||
| const parent = node.getParent(); | ||
| if (parent) { | ||
| parent.splice(node.getIndexWithinParent(), 1, node.getChildren()); | ||
| } | ||
| } | ||
| } | ||
| function isDomNodeBetweenTwoInlineNodes(node: Node): boolean { | ||
| if (node.nextSibling == null || node.previousSibling == null) { | ||
| return false; | ||
| } | ||
| return ( | ||
| isInlineDomNode(node.nextSibling) && isInlineDomNode(node.previousSibling) | ||
| ); | ||
| } |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {EditorDOMRenderConfig} from 'lexical'; | ||
| import {getPeerDependencyFromEditor} from '@lexical/extension'; | ||
| import {$getEditor, $getEditorDOMRenderConfig, LexicalEditor} from 'lexical'; | ||
| import {DOMRenderContextSymbol, DOMRenderExtensionName} from './constants'; | ||
| import { | ||
| $withContext, | ||
| createContextState, | ||
| getContextRecord, | ||
| getContextValue, | ||
| } from './ContextRecord'; | ||
| import {DOMRenderExtension} from './DOMRenderExtension'; | ||
| import { | ||
| AnyRenderStateConfigPairOrUpdater, | ||
| ContextRecord, | ||
| RenderStateConfig, | ||
| } from './types'; | ||
| /** | ||
| * Create a context state to be used during render. | ||
| * | ||
| * Note that to support the ValueOrUpdater pattern you can not use a | ||
| * function for V (but you may wrap it in an array or object). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export function createRenderState<V>( | ||
| name: string, | ||
| getDefaultValue: () => V, | ||
| isEqual?: (a: V, b: V) => boolean, | ||
| ): RenderStateConfig<V> { | ||
| return createContextState( | ||
| DOMRenderContextSymbol, | ||
| name, | ||
| getDefaultValue, | ||
| isEqual, | ||
| ); | ||
| } | ||
| /** | ||
| * Render context state that is true if the export was initiated from the root of the document. | ||
| * @experimental | ||
| */ | ||
| export const RenderContextRoot = createRenderState('root', Boolean); | ||
| /** | ||
| * Render context state that is true if this is an export operation ($generateHtmlFromNodes). | ||
| * @experimental | ||
| */ | ||
| export const RenderContextExport = createRenderState('isExport', Boolean); | ||
| function getDefaultRenderContext( | ||
| editor: LexicalEditor, | ||
| ): undefined | ContextRecord<typeof DOMRenderContextSymbol> { | ||
| const dep = getPeerDependencyFromEditor<typeof DOMRenderExtension>( | ||
| editor, | ||
| DOMRenderExtensionName, | ||
| ); | ||
| return dep ? dep.output.defaults : undefined; | ||
| } | ||
| function getRenderContext( | ||
| editor: LexicalEditor, | ||
| ): undefined | ContextRecord<typeof DOMRenderContextSymbol> { | ||
| return ( | ||
| getContextRecord(DOMRenderContextSymbol, editor) || | ||
| getDefaultRenderContext(editor) | ||
| ); | ||
| } | ||
| /** | ||
| * Get a render context value during a DOM render or export operation. | ||
| * @experimental | ||
| */ | ||
| export function $getRenderContextValue<V>( | ||
| cfg: RenderStateConfig<V>, | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): V { | ||
| return getContextValue(getRenderContext(editor), cfg); | ||
| } | ||
| function getRuntime(editor: LexicalEditor) { | ||
| const dep = getPeerDependencyFromEditor<typeof DOMRenderExtension>( | ||
| editor, | ||
| DOMRenderExtensionName, | ||
| ); | ||
| return dep ? dep.output.runtime : undefined; | ||
| } | ||
| /** | ||
| * Imperatively set a value in the persistent editor render context. | ||
| * | ||
| * Unlike {@link $withRenderContext} (which scopes values to a callback), this | ||
| * persists on the editor. If the change flips any override's | ||
| * `disabledForEditor` result, the resident render config is recompiled and the | ||
| * affected nodes are re-rendered. No-op if {@link DOMRenderExtension} is not | ||
| * installed. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function $setRenderContextValue<V>( | ||
| cfg: RenderStateConfig<V>, | ||
| value: V, | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): void { | ||
| const runtime = getRuntime(editor); | ||
| if (runtime) { | ||
| runtime.setContextValue(cfg, value); | ||
| } | ||
| } | ||
| /** | ||
| * Imperatively update a value in the persistent editor render context with an | ||
| * updater function. See {@link $setRenderContextValue}. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function $updateRenderContextValue<V>( | ||
| cfg: RenderStateConfig<V>, | ||
| updater: (prev: V) => V, | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): void { | ||
| const runtime = getRuntime(editor); | ||
| if (runtime) { | ||
| runtime.setContextValue( | ||
| cfg, | ||
| updater(getContextValue(runtime.editorContext, cfg)), | ||
| ); | ||
| } | ||
| } | ||
| /** | ||
| * Resolve the {@link EditorDOMRenderConfig} to use for the current | ||
| * export/generate session, applying any `disabledForSession` overrides against | ||
| * the active session context. Falls back to the editor's resident config when | ||
| * {@link DOMRenderExtension} is not installed. | ||
| * | ||
| * @experimental | ||
| */ | ||
| export function $getSessionDOMRenderConfig( | ||
| editor: LexicalEditor = $getEditor(), | ||
| ): EditorDOMRenderConfig { | ||
| const runtime = getRuntime(editor); | ||
| return runtime | ||
| ? runtime.getSessionConfig() | ||
| : $getEditorDOMRenderConfig(editor); | ||
| } | ||
| /** | ||
| * Execute a callback within a render context with the given config pairs. | ||
| * @experimental | ||
| */ | ||
| export const $withRenderContext: ( | ||
| cfg: readonly AnyRenderStateConfigPairOrUpdater[], | ||
| editor?: LexicalEditor, | ||
| ) => <T>(f: () => T) => T = $withContext( | ||
| DOMRenderContextSymbol, | ||
| getDefaultRenderContext, | ||
| ); |
+470
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type {DOMImportContextSymbol, DOMRenderContextSymbol} from './constants'; | ||
| import type { | ||
| BaseSelection, | ||
| DOMExportOutput, | ||
| DOMSlotForNode, | ||
| EditorDOMRenderConfig, | ||
| Klass, | ||
| LexicalEditor, | ||
| LexicalNode, | ||
| StateConfig, | ||
| } from 'lexical'; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any ContextSymbol for {@link ContextConfig} (DOM render or DOM import). | ||
| */ | ||
| export type AnyContextSymbol = | ||
| | typeof DOMRenderContextSymbol | ||
| | typeof DOMImportContextSymbol; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Context with a phantom type for its purpose (such as {@link DOMRenderContextSymbol}). | ||
| * | ||
| * A ContextRecord is a data structure used in the export and import pipelines | ||
| * to allow for information to be passed throughout the chain without explicit | ||
| * argument passing, e.g. to specify whether the intended use case for HTML | ||
| * export is for serialization or for clipboard copy. | ||
| */ | ||
| export type ContextRecord<_K extends symbol> = Record<string | symbol, unknown>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * A data structure much like StateConfig (they share implementation details) | ||
| * but for managing context during an export or import pipeline rather than | ||
| * individual node state. | ||
| */ | ||
| export type ContextConfig<Sym extends symbol, V> = StateConfig<symbol, V> & { | ||
| readonly [K in Sym]?: true; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Update the context at `cfg` with updater, constructed with {@link contextUpdater} | ||
| */ | ||
| export type ContextConfigUpdater<Ctx extends AnyContextSymbol, V> = { | ||
| readonly cfg: ContextConfig<Ctx, V>; | ||
| /** | ||
| * @param prev The current or default value | ||
| * @returns The new value | ||
| */ | ||
| readonly updater: (prev: V) => V; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Set the the context at `cfg` to a specific value, constructed with {@link contextValue} | ||
| */ | ||
| export type ContextConfigPair<Ctx extends AnyContextSymbol, V> = readonly [ | ||
| ContextConfig<Ctx, V>, | ||
| V, | ||
| ]; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Set or update a context value, constructed with {@link contextValue} or {@link contextUpdater} | ||
| */ | ||
| export type ContextPairOrUpdater<Ctx extends AnyContextSymbol, V> = | ||
| | ContextConfigPair<Ctx, V> | ||
| | ContextConfigUpdater<Ctx, V>; | ||
| /** @experimental */ | ||
| export type AnyContextConfigPairOrUpdater<Ctx extends AnyContextSymbol> = | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| ContextPairOrUpdater<Ctx, any>; | ||
| /** @experimental */ | ||
| export interface DOMRenderExtensionOutput { | ||
| /** @internal */ | ||
| defaults: undefined | ContextRecord<typeof DOMRenderContextSymbol>; | ||
| /** @internal */ | ||
| runtime: DOMRenderRuntime; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * A read-only view of a render context layer, passed to the | ||
| * {@link DOMOverrideOptions} predicates so they can decide whether an | ||
| * override should be installed based only on context values. | ||
| */ | ||
| export interface RenderContextReader { | ||
| get<V>(cfg: RenderStateConfig<V>): V; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * | ||
| * Per-editor runtime state for {@link DOMRenderExtension} that backs the | ||
| * imperative editor context ({@link createRenderState} writes via | ||
| * `$setRenderContextValue`) and the conditional install of overrides. | ||
| */ | ||
| export interface DOMRenderRuntime { | ||
| /** | ||
| * The mutable, persistent editor-level context record. Reads of a | ||
| * {@link RenderStateConfig} during reconciliation (and as the base layer | ||
| * during a session) fall through to this record. It is also the layer | ||
| * that {@link DOMOverrideOptions.disabledForEditor} predicates read from. | ||
| */ | ||
| readonly editorContext: ContextRecord<typeof DOMRenderContextSymbol>; | ||
| /** | ||
| * Imperatively set a value in the editor context. If the change flips any | ||
| * override's `disabledForEditor` result, the resident render config is | ||
| * recompiled and the affected nodes are re-rendered (recreating DOM for | ||
| * structural overrides). | ||
| */ | ||
| setContextValue<V>(cfg: RenderStateConfig<V>, value: V): void; | ||
| /** | ||
| * Resolve the {@link EditorDOMRenderConfig} for the current export/generate | ||
| * session, applying any {@link DOMOverrideOptions.disabledForSession} | ||
| * predicates against the active session context. Returns the resident | ||
| * config when no session gating applies. | ||
| */ | ||
| getSessionConfig(): EditorDOMRenderConfig; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Options for {@link domOverride} controlling *whether* an override is | ||
| * installed, based only on render context. Both predicates default to | ||
| * "not disabled". | ||
| */ | ||
| export interface DOMOverrideOptions { | ||
| /** | ||
| * Gate residency in the editor's render config (used by reconciliation and | ||
| * as the base for export/generate). Evaluated against the persistent editor | ||
| * context at compile time, and re-evaluated when that context changes via | ||
| * `$setRenderContextValue`; a change recompiles the config and re-renders | ||
| * affected nodes. Return `true` to remove the override. Default: not disabled. | ||
| */ | ||
| disabledForEditor?: (ctx: RenderContextReader) => boolean; | ||
| /** | ||
| * Gate participation in a single export/generate session. Evaluated once at | ||
| * the start of each session against that session's context. Has no effect on | ||
| * live reconciliation (which is not a session). Return `true` to remove the | ||
| * override for that session. Default: not disabled. | ||
| */ | ||
| disabledForSession?: (ctx: RenderContextReader) => boolean; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Context configuration for render context, created with {@link createRenderState} | ||
| */ | ||
| export type RenderStateConfig<V> = ContextConfig< | ||
| typeof DOMRenderContextSymbol, | ||
| V | ||
| >; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any setter or updater for {@link RenderStateConfig} | ||
| */ | ||
| export type AnyRenderStateConfigPairOrUpdater = AnyContextConfigPairOrUpdater< | ||
| typeof DOMRenderContextSymbol | ||
| >; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any {@link RenderStateConfig} | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| export type AnyRenderStateConfig = RenderStateConfig<any>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Configuration for {@link DOMRenderExtension} | ||
| */ | ||
| export interface DOMRenderConfig { | ||
| /** | ||
| * {@link DOMRenderMatch} overrides to customize node behavior, | ||
| * the final priority of these will be based on the following criteria: | ||
| * | ||
| * - Wildcards (`'*'`) have highest priority | ||
| * - Predicates (`$isParagraphNode`) have next priority | ||
| * - Subclasses have higher priority (e.g. `ParagraphNode` before `ElementNode`) | ||
| * - Extensions closer to the root have higher priority | ||
| * - Extensions depended on later have higher priority | ||
| * - Overrides defined later have higher priority | ||
| */ | ||
| overrides: AnyDOMRenderMatch[]; | ||
| /** | ||
| * Default context to provide in all exports, the configurations are created | ||
| * with {@link createRenderState} and should be created at the module-level. | ||
| * | ||
| * Only specify these if overriding the default value globally, since each | ||
| * configuration has a built-in default value that will be used if not | ||
| * already present in the context. | ||
| */ | ||
| contextDefaults: AnyRenderStateConfigPairOrUpdater[]; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * Any {@link DOMRenderMatch} | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| export type AnyDOMRenderMatch = DOMRenderMatch<any>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Match a node (and any subclass of that node) by its LexicalNode class, | ||
| * or with a guard (e.g. `ElementNode` or `$isElementNode`). | ||
| * | ||
| * Note that using the class compiles to significantly more efficient code | ||
| * than using a guard. | ||
| */ | ||
| export type NodeMatch<T extends LexicalNode> = | ||
| | Klass<T> | ||
| | ((node: LexicalNode) => node is T); | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Used to define overrides for the render and export | ||
| * behavior for nodes matching the `nodes` predicate. | ||
| * | ||
| * All of these overrides are in a middleware style where you may use the | ||
| * result of `$next()` to enhance the result of the default implementation | ||
| * (or a lower priority override) by calling it and manipulating the result, | ||
| * or you may choose not to call `$next()` to entirely replace the behavior. | ||
| * | ||
| * It is not permitted to update the lexical editor state during any of | ||
| * these calls, you should only be doing read-only operations. | ||
| */ | ||
| export interface DOMRenderMatch<T extends LexicalNode> { | ||
| /** | ||
| * '*' for all nodes, or an array of `NodeClass | $isNodeGuard` to match | ||
| * nodes more specifically. Using classes is more efficient, but will | ||
| * also target subclasses. | ||
| */ | ||
| readonly nodes: '*' | readonly NodeMatch<T>[]; | ||
| /** | ||
| * Control where an ElementNode's children are inserted into the DOM, | ||
| * this is useful to add a wrapping node or accessory nodes before or | ||
| * after the children. The root of the node returned by createDOM must | ||
| * still be exactly one HTMLElement. | ||
| * | ||
| * Generally you will call `$next()` to get a slot and then use its methods | ||
| * to create a new one. The slot type is narrowed via {@link DOMSlotForNode}: | ||
| * for `ElementNode` it resolves to {@link ElementDOMSlot} with | ||
| * children-management semantics; for non-Element nodes the base | ||
| * {@link DOMSlot} pointing at the keyed DOM. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param dom The rendered HTMLElement | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns The slot for this node | ||
| */ | ||
| $getDOMSlot?: ( | ||
| node: T, | ||
| dom: HTMLElement, | ||
| $next: () => DOMSlotForNode<T>, | ||
| editor: LexicalEditor, | ||
| ) => DOMSlotForNode<T>; | ||
| /** | ||
| * Called during the reconciliation process to determine which nodes | ||
| * to insert into the DOM for this Lexical Node. This is also the default | ||
| * implementation of `$exportDOM` for most nodes. | ||
| * | ||
| * This method must return exactly one `HTMLElement`. | ||
| * | ||
| * Nested elements are not supported except with `DecoratorNode` | ||
| * (which have unmanaged contents) or `ElementNode` using an appropriate | ||
| * `$getDOMSlot` return value. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns The HTMLElement for this node to be rendered in the editor | ||
| */ | ||
| $createDOM?: ( | ||
| node: T, | ||
| $next: () => HTMLElement, | ||
| editor: LexicalEditor, | ||
| ) => HTMLElement; | ||
| /** | ||
| * Called when a node changes and should update the DOM | ||
| * in whatever way is necessary to make it align with any changes that might | ||
| * have happened during the update. | ||
| * | ||
| * Returning `true` here will cause lexical to unmount and recreate the DOM | ||
| * node (by calling `$createDOM`). You would need to do this if the element | ||
| * tag changes, for instance. | ||
| * | ||
| * @param nextNode The current version of this node | ||
| * @param prevNode The previous version of this node | ||
| * @param dom The previously rendered HTMLElement for this node | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns `false` if no update needed or was performed in-place, `true` if `$createDOM` should be called to re-create the node | ||
| */ | ||
| $updateDOM?: ( | ||
| nextNode: T, | ||
| prevNode: T, | ||
| dom: HTMLElement, | ||
| $next: () => boolean, | ||
| editor: LexicalEditor, | ||
| ) => boolean; | ||
| /** | ||
| * Called after a node is created or updated and should make any in-place | ||
| * updates to the DOM in whatever way is necessary to make it align with | ||
| * any changes that might have happened during the `$createDOM` or | ||
| * `$updateDOM`. This also runs after any children have been reconciled. | ||
| * | ||
| * Use this when you have code that you would need to duplicate in both | ||
| * methods, or if there is a need to ensure that the children are also | ||
| * reconciled before performing this in-place update. | ||
| * | ||
| * Unlike other overrides, all applicable `$decorateDOM` functions are | ||
| * called unconditionally. There is no `$next` argument, because there | ||
| * are no known use cases for avoiding the next implementation and due | ||
| * to the void return value it would be error-prone and add boilerplate | ||
| * to require calling it. | ||
| * | ||
| * The ordering here is equivalent to an implicit `$next` call *first*. | ||
| * | ||
| * @param nextNode The current version of this node | ||
| * @param prevNode The previous version of this node if `$updateDOM` returned `false`, or `null` if `$createDOM` was just called | ||
| * @param dom The previously rendered `HTMLElement` for this node | ||
| * @param editor The editor | ||
| */ | ||
| $decorateDOM?: ( | ||
| nextNode: T, | ||
| prevNode: null | T, | ||
| dom: HTMLElement, | ||
| editor: LexicalEditor, | ||
| ) => void; | ||
| /** | ||
| * Controls how the this node is serialized to HTML. This is important for | ||
| * copy and paste between Lexical and non-Lexical editors, or Lexical | ||
| * editors with different namespaces, in which case the primary transfer | ||
| * format is HTML. It's also important if you're serializing to HTML for | ||
| * any other reason via {@link @lexical/html!$generateHtmlFromNodes}. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns A {@link DOMExportOutput} structure that defines how the node should be exported to HTML | ||
| */ | ||
| $exportDOM?: ( | ||
| node: T, | ||
| $next: () => DOMExportOutput, | ||
| editor: LexicalEditor, | ||
| ) => DOMExportOutput; | ||
| /** | ||
| * Equivalent to `ElementNode.excludeFromCopy`, if it returns `true` this | ||
| * lexical node will not be exported to DOM (but if it's an `ElementNode` | ||
| * its children may still be inserted in its place). | ||
| * | ||
| * Has higher precedence than `$shouldInclude` and `$extractWithChild`. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param selection The current selection | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns true to exclude this node, false otherwise | ||
| */ | ||
| $shouldExclude?: ( | ||
| node: T, | ||
| selection: null | BaseSelection, | ||
| $next: () => boolean, | ||
| editor: LexicalEditor, | ||
| ) => boolean; | ||
| /** | ||
| * Return `true` if this node should be included in the export, typically based | ||
| * on the current selection (all nodes by default are included when there | ||
| * is no selection). | ||
| * | ||
| * The default implementation is equivalent to | ||
| * `selection ? node.isSelected(selection) : true`. | ||
| * | ||
| * This has lower precedence than `$extractWithChild` and `$shouldExclude`. | ||
| * | ||
| * @param node The current node | ||
| * @param selection The current selection | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns `true` if this node should be included in the export, `false` otherwise | ||
| */ | ||
| $shouldInclude?: ( | ||
| node: T, | ||
| selection: null | BaseSelection, | ||
| $next: () => boolean, | ||
| editor: LexicalEditor, | ||
| ) => boolean; | ||
| /** | ||
| * Return `true` if this node should be included in the export based on | ||
| * `childNode`, even if it would not otherwise be included based on its | ||
| * `$shouldInclude` result. | ||
| * | ||
| * Typically used to ensure that required wrapping nodes are always | ||
| * present with its children, e.g. a ListNode when some of its ListItemNode | ||
| * children are selected. | ||
| * | ||
| * This has higher precedence than `$extractWithChild` and lower precedence | ||
| * than `$shouldExclude`. | ||
| * | ||
| * @param node The lexical node | ||
| * @param childNode A child of this lexical node | ||
| * @param selection The current selection | ||
| * @param destination Currently always `'html'` | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns true if this | ||
| */ | ||
| $extractWithChild?: ( | ||
| node: T, | ||
| childNode: LexicalNode, | ||
| selection: null | BaseSelection, | ||
| destination: 'clone' | 'html', | ||
| $next: () => boolean, | ||
| editor: LexicalEditor, | ||
| ) => boolean; | ||
| /** | ||
| * Set via {@link domOverride}'s options argument, not directly. See | ||
| * {@link DOMOverrideOptions.disabledForEditor}. | ||
| */ | ||
| disabledForEditor?: (ctx: RenderContextReader) => boolean; | ||
| /** | ||
| * Set via {@link domOverride}'s options argument, not directly. See | ||
| * {@link DOMOverrideOptions.disabledForSession}. | ||
| */ | ||
| disabledForSession?: (ctx: RenderContextReader) => boolean; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The hook fields of a {@link DOMRenderMatch} — i.e. without `nodes` or the | ||
| * {@link DOMOverrideOptions} predicates, which are passed separately to | ||
| * {@link domOverride}. | ||
| */ | ||
| export type DOMRenderMatchConfig<T extends LexicalNode> = Omit< | ||
| DOMRenderMatch<T>, | ||
| 'nodes' | keyof DOMOverrideOptions | ||
| >; |
+33
-18
@@ -11,5 +11,5 @@ { | ||
| "license": "MIT", | ||
| "version": "0.44.1-nightly.20260519.0", | ||
| "main": "LexicalHtml.js", | ||
| "types": "index.d.ts", | ||
| "version": "0.45.0", | ||
| "main": "./dist/LexicalHtml.js", | ||
| "types": "./dist/index.d.ts", | ||
| "repository": { | ||
@@ -21,26 +21,41 @@ "type": "git", | ||
| "dependencies": { | ||
| "@lexical/extension": "0.44.1-nightly.20260519.0", | ||
| "@lexical/selection": "0.44.1-nightly.20260519.0", | ||
| "@lexical/utils": "0.44.1-nightly.20260519.0", | ||
| "lexical": "0.44.1-nightly.20260519.0" | ||
| "@lexical/extension": "0.45.0", | ||
| "@lexical/internal": "0.45.0", | ||
| "@lexical/selection": "0.45.0", | ||
| "@lexical/utils": "0.45.0", | ||
| "lexical": "0.45.0" | ||
| }, | ||
| "module": "LexicalHtml.mjs", | ||
| "module": "./dist/LexicalHtml.mjs", | ||
| "sideEffects": false, | ||
| "exports": { | ||
| ".": { | ||
| "source": "./src/index.ts", | ||
| "import": { | ||
| "types": "./index.d.ts", | ||
| "development": "./LexicalHtml.dev.mjs", | ||
| "production": "./LexicalHtml.prod.mjs", | ||
| "node": "./LexicalHtml.node.mjs", | ||
| "default": "./LexicalHtml.mjs" | ||
| "types": "./dist/index.d.ts", | ||
| "development": "./dist/LexicalHtml.dev.mjs", | ||
| "production": "./dist/LexicalHtml.prod.mjs", | ||
| "node": "./dist/LexicalHtml.node.mjs", | ||
| "default": "./dist/LexicalHtml.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./index.d.ts", | ||
| "development": "./LexicalHtml.dev.js", | ||
| "production": "./LexicalHtml.prod.js", | ||
| "default": "./LexicalHtml.js" | ||
| "types": "./dist/index.d.ts", | ||
| "development": "./dist/LexicalHtml.dev.js", | ||
| "production": "./dist/LexicalHtml.prod.js", | ||
| "default": "./dist/LexicalHtml.js" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "src", | ||
| "!src/__tests__", | ||
| "!src/__bench__", | ||
| "!src/__mocks__", | ||
| "!src/**/*.test.ts", | ||
| "!src/**/*.test.tsx", | ||
| "!src/**/*.bench.ts", | ||
| "!src/**/*.bench.tsx", | ||
| "README.md", | ||
| "LICENSE" | ||
| ] | ||
| } |
| import { type EditorDOMRenderConfig, InitialEditorConfig, Klass, type LexicalNode } from 'lexical'; | ||
| import { AnyDOMRenderMatch, DOMRenderConfig } from './types'; | ||
| interface TypeRecord { | ||
| readonly klass: Klass<LexicalNode>; | ||
| readonly types: { | ||
| [NodeAndSubclasses in string]?: boolean; | ||
| }; | ||
| } | ||
| type TypeTree = { | ||
| [NodeType in string]?: TypeRecord; | ||
| }; | ||
| export declare function buildTypeTree(editorConfig: Pick<InitialEditorConfig, 'nodes'>): TypeTree; | ||
| type TypeRender<T> = { | ||
| [NodeType in string]?: T[]; | ||
| }; | ||
| type AnyRender<T> = readonly [(node: LexicalNode) => boolean, T] | readonly ['types', TypeRender<T>]; | ||
| type PreEditorDOMRenderConfig = { | ||
| [K in keyof EditorDOMRenderConfig]: AnyRender<AnyDOMRenderMatch[K]>[]; | ||
| }; | ||
| export declare function precompileDOMRenderConfigOverrides(editorConfig: Pick<InitialEditorConfig, 'nodes'>, overrides: DOMRenderConfig['overrides']): PreEditorDOMRenderConfig; | ||
| export declare function compileDOMRenderConfigOverrides(editorConfig: InitialEditorConfig, { overrides }: DOMRenderConfig): EditorDOMRenderConfig; | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| export declare const DOMRenderExtensionName = "@lexical/html/DOM"; | ||
| export declare const DOMRenderContextSymbol: unique symbol; | ||
| export declare const ALWAYS_TRUE: () => true; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { AnyContextConfigPairOrUpdater, AnyContextSymbol, ContextConfig, ContextConfigPair, ContextConfigUpdater, ContextRecord } from './types'; | ||
| import { type LexicalEditor } from 'lexical'; | ||
| type WithContext<Ctx extends AnyContextSymbol> = { | ||
| [K in Ctx]?: undefined | ContextRecord<Ctx>; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The LexicalEditor with context | ||
| */ | ||
| export type EditorContext = { | ||
| editor: LexicalEditor; | ||
| } & WithContext<AnyContextSymbol>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value or defaultValue of cfg | ||
| */ | ||
| export declare function getContextValue<Ctx extends AnyContextSymbol, V>(contextRecord: undefined | ContextRecord<Ctx>, cfg: ContextConfig<Ctx, V>): V; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Read and delete cfg from this layer of context | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value of the configuration that was removed | ||
| */ | ||
| export declare function popOwnContextValue<Ctx extends AnyContextSymbol, V>(contextRecord: ContextRecord<Ctx>, cfg: ContextConfig<Ctx, V>): undefined | V; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Get the value without a default | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The current value in this context or `undefined` if not set | ||
| */ | ||
| export declare function getOwnContextValue<Ctx extends AnyContextSymbol, V>(contextRecord: ContextRecord<Ctx>, cfg: ContextConfig<Ctx, V>): undefined | V; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol) | ||
| * @param editor The editor | ||
| * @returns The current context or undefined | ||
| */ | ||
| export declare function getContextRecord<Ctx extends AnyContextSymbol>(sym: Ctx, editor: LexicalEditor): undefined | ContextRecord<Ctx>; | ||
| /** | ||
| * Construct a new context from a parent context and pairs | ||
| * | ||
| * @param pairs The pairs and updaters to build the context from | ||
| * @param parent The parent context | ||
| * @returns The new context | ||
| */ | ||
| export declare function contextFromPairs<Ctx extends AnyContextSymbol>(pairs: readonly AnyContextConfigPairOrUpdater<Ctx>[], parent: undefined | ContextRecord<Ctx>): undefined | ContextRecord<Ctx>; | ||
| /** | ||
| * Create a context config pair that sets a value in the render context. | ||
| * @experimental | ||
| */ | ||
| export declare function contextValue<Ctx extends AnyContextSymbol, V>(cfg: ContextConfig<Ctx, V>, value: V): ContextConfigPair<Ctx, V>; | ||
| /** | ||
| * Create a context config updater that transforms a value in the render context. | ||
| * @experimental | ||
| */ | ||
| export declare function contextUpdater<Ctx extends AnyContextSymbol, V>(cfg: ContextConfig<Ctx, V>, updater: (prev: V) => V): ContextConfigUpdater<Ctx, V>; | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function $withFullContext<Ctx extends AnyContextSymbol, T>(sym: Ctx, contextRecord: ContextRecord<Ctx>, f: () => T, editor?: LexicalEditor): T; | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function $withContext<Ctx extends AnyContextSymbol>(sym: Ctx, $defaults?: (editor: LexicalEditor) => undefined | ContextRecord<Ctx>): (cfg: readonly AnyContextConfigPairOrUpdater<Ctx>[], editor?: LexicalEditor) => (<T>(f: () => T) => T); | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function createContextState<Tag extends symbol, V>(tag: Tag, name: string, getDefaultValue: () => V, isEqual?: (a: V, b: V) => boolean): ContextConfig<Tag, V>; | ||
| export {}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMRenderMatch, NodeMatch } from './types'; | ||
| import type { LexicalNode } from 'lexical'; | ||
| /** | ||
| * A convenience function for type inference when constructing DOM overrides for | ||
| * use with {@link DOMRenderExtension}. | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function domOverride(nodes: '*', config: Omit<DOMRenderMatch<LexicalNode>, 'nodes'>): DOMRenderMatch<LexicalNode>; | ||
| export declare function domOverride<T extends LexicalNode>(nodes: readonly NodeMatch<T>[], config: Omit<DOMRenderMatch<T>, 'nodes'>): DOMRenderMatch<T>; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMRenderConfig, DOMRenderExtensionOutput } from './types'; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * An extension that allows overriding the render and export behavior for an | ||
| * editor. This is highly experimental and subject to change from one version | ||
| * to the next. | ||
| **/ | ||
| export declare const DOMRenderExtension: import("lexical").LexicalExtension<DOMRenderConfig, "@lexical/html/DOM", DOMRenderExtensionOutput, void>; |
-32
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { BaseSelection, LexicalEditor, LexicalNode } from 'lexical'; | ||
| export { contextUpdater, contextValue } from './ContextRecord'; | ||
| export { domOverride } from './domOverride'; | ||
| export { DOMRenderExtension } from './DOMRenderExtension'; | ||
| export { $getRenderContextValue, $withRenderContext, createRenderState, RenderContextExport, RenderContextRoot, } from './RenderContext'; | ||
| export type { AnyDOMRenderMatch, AnyRenderStateConfig, AnyRenderStateConfigPairOrUpdater, ContextPairOrUpdater, DOMRenderConfig, DOMRenderExtensionOutput, DOMRenderMatch, NodeMatch, } from './types'; | ||
| /** | ||
| * How you parse your html string to get a document is left up to you. In the browser you can use the native | ||
| * DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom | ||
| * or an equivalent library and pass in the document here. | ||
| */ | ||
| export declare function $generateNodesFromDOM(editor: LexicalEditor, dom: Document | ParentNode): Array<LexicalNode>; | ||
| /** | ||
| * Generate DOM nodes from the editor state into the given container element, | ||
| * using the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| export declare function $generateDOMFromNodes<T extends HTMLElement | DocumentFragment>(container: T, selection?: null | BaseSelection, editor?: LexicalEditor): T; | ||
| /** | ||
| * Generate DOM nodes from a root node into the given container element, | ||
| * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| export declare function $generateDOMFromRoot<T extends HTMLElement | DocumentFragment>(container: T, root?: LexicalNode): T; | ||
| export declare function $generateHtmlFromNodes(editor: LexicalEditor, selection?: BaseSelection | null): string; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| 'use strict'; | ||
| var selection = require('@lexical/selection'); | ||
| var lexical = require('lexical'); | ||
| var extension = require('@lexical/extension'); | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| // Do not require this module directly! Use normal `invariant` calls. | ||
| function formatDevErrorMessage(message) { | ||
| throw new Error(message); | ||
| } | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| let activeContext; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The LexicalEditor with context | ||
| */ | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value or defaultValue of cfg | ||
| */ | ||
| function getContextValue(contextRecord, cfg) { | ||
| const { | ||
| key | ||
| } = cfg; | ||
| return contextRecord && key in contextRecord ? contextRecord[key] : cfg.defaultValue; | ||
| } | ||
| function getEditorContext(editor) { | ||
| return activeContext && activeContext.editor === editor ? activeContext : undefined; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol) | ||
| * @param editor The editor | ||
| * @returns The current context or undefined | ||
| */ | ||
| function getContextRecord(sym, editor) { | ||
| const editorContext = getEditorContext(editor); | ||
| return editorContext && editorContext[sym]; | ||
| } | ||
| function toPair(contextRecord, pairOrUpdater) { | ||
| if ('cfg' in pairOrUpdater) { | ||
| const { | ||
| cfg, | ||
| updater | ||
| } = pairOrUpdater; | ||
| return [cfg, updater(getContextValue(contextRecord, cfg))]; | ||
| } | ||
| return pairOrUpdater; | ||
| } | ||
| /** | ||
| * Construct a new context from a parent context and pairs | ||
| * | ||
| * @param pairs The pairs and updaters to build the context from | ||
| * @param parent The parent context | ||
| * @returns The new context | ||
| */ | ||
| function contextFromPairs(pairs, parent) { | ||
| let rval = parent; | ||
| for (const pairOrUpdater of pairs) { | ||
| const [k, v] = toPair(rval, pairOrUpdater); | ||
| const key = k.key; | ||
| if (rval === parent && getContextValue(rval, k) === v) { | ||
| continue; | ||
| } | ||
| const ctx = rval || createChildContext(parent); | ||
| ctx[key] = v; | ||
| rval = ctx; | ||
| } | ||
| return rval; | ||
| } | ||
| function createChildContext(parent) { | ||
| return Object.create(parent || null); | ||
| } | ||
| /** | ||
| * Create a context config pair that sets a value in the render context. | ||
| * @experimental | ||
| */ | ||
| function contextValue(cfg, value) { | ||
| return [cfg, value]; | ||
| } | ||
| /** | ||
| * Create a context config updater that transforms a value in the render context. | ||
| * @experimental | ||
| */ | ||
| function contextUpdater(cfg, updater) { | ||
| return { | ||
| cfg, | ||
| updater | ||
| }; | ||
| } | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function $withFullContext(sym, contextRecord, f, editor = lexical.$getEditor()) { | ||
| const prevDOMContext = activeContext; | ||
| const parentEditorContext = getEditorContext(editor); | ||
| try { | ||
| activeContext = { | ||
| ...parentEditorContext, | ||
| editor, | ||
| [sym]: contextRecord | ||
| }; | ||
| return f(); | ||
| } finally { | ||
| activeContext = prevDOMContext; | ||
| } | ||
| } | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function $withContext(sym, $defaults = () => undefined) { | ||
| return (cfg, editor = lexical.$getEditor()) => { | ||
| return f => { | ||
| const parentEditorContext = getEditorContext(editor); | ||
| const parentContextRecord = parentEditorContext && parentEditorContext[sym]; | ||
| const contextRecord = contextFromPairs(cfg, parentContextRecord || $defaults(editor)); | ||
| if (!contextRecord || contextRecord === parentContextRecord) { | ||
| return f(); | ||
| } | ||
| return $withFullContext(sym, contextRecord, f, editor); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function createContextState(tag, name, getDefaultValue, isEqual) { | ||
| return Object.assign(lexical.createState(Symbol(name), { | ||
| isEqual, | ||
| parse: getDefaultValue | ||
| }), { | ||
| [tag]: true | ||
| }); | ||
| } | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| const DOMRenderExtensionName = '@lexical/html/DOM'; | ||
| const DOMRenderContextSymbol = Symbol.for('@lexical/html/DOMExportContext'); | ||
| const ALWAYS_TRUE = () => true; | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * Create a context state to be used during render. | ||
| * | ||
| * Note that to support the ValueOrUpdater pattern you can not use a | ||
| * function for V (but you may wrap it in an array or object). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function createRenderState(name, getDefaultValue, isEqual) { | ||
| return createContextState(DOMRenderContextSymbol, name, getDefaultValue, isEqual); | ||
| } | ||
| /** | ||
| * Render context state that is true if the export was initiated from the root of the document. | ||
| * @experimental | ||
| */ | ||
| const RenderContextRoot = createRenderState('root', Boolean); | ||
| /** | ||
| * Render context state that is true if this is an export operation ($generateHtmlFromNodes). | ||
| * @experimental | ||
| */ | ||
| const RenderContextExport = createRenderState('isExport', Boolean); | ||
| function getDefaultRenderContext(editor) { | ||
| const dep = extension.getPeerDependencyFromEditor(editor, DOMRenderExtensionName); | ||
| return dep ? dep.output.defaults : undefined; | ||
| } | ||
| function getRenderContext(editor) { | ||
| return getContextRecord(DOMRenderContextSymbol, editor) || getDefaultRenderContext(editor); | ||
| } | ||
| /** | ||
| * Get a render context value during a DOM render or export operation. | ||
| * @experimental | ||
| */ | ||
| function $getRenderContextValue(cfg, editor = lexical.$getEditor()) { | ||
| return getContextValue(getRenderContext(editor), cfg); | ||
| } | ||
| /** | ||
| * Execute a callback within a render context with the given config pairs. | ||
| * @experimental | ||
| */ | ||
| const $withRenderContext = $withContext(DOMRenderContextSymbol, getDefaultRenderContext); | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * A convenience function for type inference when constructing DOM overrides for | ||
| * use with {@link DOMRenderExtension}. | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function domOverride(nodes, config) { | ||
| return { | ||
| ...config, | ||
| nodes | ||
| }; | ||
| } | ||
| function buildTypeTree(editorConfig) { | ||
| const t = {}; | ||
| const { | ||
| nodes | ||
| } = extension.getKnownTypesAndNodes(editorConfig); | ||
| for (const klass of nodes) { | ||
| const type = klass.getType(); | ||
| t[type] = { | ||
| klass, | ||
| types: {} | ||
| }; | ||
| } | ||
| for (const baseRec of Object.values(t)) { | ||
| if (baseRec) { | ||
| const baseType = baseRec.klass.getType(); | ||
| for (let { | ||
| klass | ||
| } = baseRec; lexical.$isLexicalNode(klass.prototype); klass = Object.getPrototypeOf(klass)) { | ||
| const { | ||
| ownNodeType | ||
| } = lexical.getStaticNodeConfig(klass); | ||
| const superRec = ownNodeType && t[ownNodeType]; | ||
| if (superRec) { | ||
| superRec.types[baseType] = true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return t; | ||
| } | ||
| function buildNodePredicate(klass) { | ||
| return node => node instanceof klass; | ||
| } | ||
| function getPredicate(typeTree, { | ||
| nodes | ||
| }) { | ||
| if (nodes === '*') { | ||
| return ALWAYS_TRUE; | ||
| } | ||
| let types = {}; | ||
| const predicates = []; | ||
| for (const klassOrPredicate of nodes) { | ||
| if ('getType' in klassOrPredicate) { | ||
| const type = klassOrPredicate.getType(); | ||
| if (types) { | ||
| const tree = typeTree[type]; | ||
| if (!(tree !== undefined)) { | ||
| formatDevErrorMessage(`Node class ${klassOrPredicate.name} with type ${type} not registered in editor`); | ||
| } | ||
| types = Object.assign(types, tree.types); | ||
| } | ||
| predicates.push(buildNodePredicate(klassOrPredicate)); | ||
| } else { | ||
| types = undefined; | ||
| predicates.push(klassOrPredicate); | ||
| } | ||
| } | ||
| if (types) { | ||
| return types; | ||
| } else if (predicates.length === 1) { | ||
| return predicates[0]; | ||
| } | ||
| return node => { | ||
| for (const predicate of predicates) { | ||
| if (predicate(node)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| } | ||
| function makePrerender() { | ||
| return { | ||
| $createDOM: [], | ||
| $decorateDOM: [], | ||
| $exportDOM: [], | ||
| $extractWithChild: [], | ||
| $getDOMSlot: [], | ||
| $shouldExclude: [], | ||
| $shouldInclude: [], | ||
| $updateDOM: [] | ||
| }; | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| function ignoreNext2(acc) { | ||
| return (node, _$next, editor) => acc(node, editor); | ||
| } | ||
| function ignoreNext3(acc) { | ||
| return (node, a, _$next, editor) => acc(node, a, editor); | ||
| } | ||
| function ignoreNext4(acc) { | ||
| return (node, a, b, _$next, editor) => acc(node, a, b, editor); | ||
| } | ||
| function ignoreNext5(acc) { | ||
| return (node, a, b, c, _$next, editor) => acc(node, a, b, c, editor); | ||
| } | ||
| function merge2($acc, $getOverride) { | ||
| return (node, editor) => { | ||
| const $next = () => $acc(node, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge3(acc, $getOverride) { | ||
| return (node, a, editor) => { | ||
| const $next = () => acc(node, a, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge4($acc, $getOverride) { | ||
| return (node, a, b, editor) => { | ||
| const $next = () => $acc(node, a, b, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, b, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge5(acc, $getOverride) { | ||
| return (node, a, b, c, editor) => { | ||
| const $next = () => acc(node, a, b, c, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, b, c, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function sequence4($acc, $getOverride) { | ||
| return (node, a, b, editor) => { | ||
| $acc(node, a, b, editor); | ||
| const $override = $getOverride(node); | ||
| if ($override) { | ||
| $override(node, a, b, editor); | ||
| } | ||
| }; | ||
| } | ||
| function compilePrerenderKey(prerender, k, defaults, mergeFunction, ignoreNextFunction) { | ||
| let acc = defaults[k]; | ||
| for (const pair of prerender[k]) { | ||
| if (typeof pair[0] === 'function') { | ||
| const [$predicate, $override] = pair; | ||
| acc = mergeFunction(acc, node => $predicate(node) && $override || undefined); | ||
| } else { | ||
| const typeOverrides = pair[1]; | ||
| const compiled = {}; | ||
| for (const type in typeOverrides) { | ||
| const arr = typeOverrides[type]; | ||
| if (arr) { | ||
| compiled[type] = arr.reduce(($acc, $override) => mergeFunction($acc, () => $override), acc); | ||
| } | ||
| } | ||
| acc = mergeFunction(acc, node => { | ||
| const f = compiled[node.getType()]; | ||
| return f && ignoreNextFunction(f); | ||
| }); | ||
| } | ||
| } | ||
| defaults[k] = acc; | ||
| } | ||
| function addOverride(prerender, k, predicateOrTypes, override) { | ||
| if (!override) { | ||
| return; | ||
| } | ||
| const arr = prerender[k]; | ||
| if (typeof predicateOrTypes === 'function') { | ||
| arr.push([predicateOrTypes, override]); | ||
| } else { | ||
| const last = arr[arr.length - 1]; | ||
| let types; | ||
| if (last && last[0] === 'types') { | ||
| types = last[1]; | ||
| } else { | ||
| types = {}; | ||
| arr.push(['types', types]); | ||
| } | ||
| for (const type in predicateOrTypes) { | ||
| const typeArr = types[type] || []; | ||
| types[type] = typeArr; | ||
| typeArr.push(override); | ||
| } | ||
| } | ||
| } | ||
| function isWildcard(override) { | ||
| return override.nodes === '*'; | ||
| } | ||
| function sortedOverrides(overrides) { | ||
| const byWildcard = []; | ||
| const byPredicate = []; | ||
| const byNode = []; | ||
| for (const override of overrides) { | ||
| if (isWildcard(override)) { | ||
| byWildcard.push(override); | ||
| } else if (Array.isArray(override.nodes)) { | ||
| for (const klassOrPredicate of override.nodes) { | ||
| if (lexical.$isLexicalNode(klassOrPredicate.prototype)) { | ||
| byNode.push(override.nodes.length === 1 ? override : { | ||
| ...override, | ||
| nodes: [klassOrPredicate] | ||
| }); | ||
| } else { | ||
| byPredicate.push(override.nodes.length === 1 ? override : { | ||
| ...override, | ||
| nodes: [klassOrPredicate] | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const depths = new Map(); | ||
| const depthOf = klass => { | ||
| let depth = depths.get(klass); | ||
| if (depth === undefined) { | ||
| depth = 0; | ||
| for (let k = klass; lexical.$isLexicalNode(k.prototype); k = Object.getPrototypeOf(k)) { | ||
| depth++; | ||
| } | ||
| depths.set(klass, depth); | ||
| } | ||
| return depth; | ||
| }; | ||
| byNode.sort((a, b) => depthOf(a.nodes[0]) - depthOf(b.nodes[0])); | ||
| return [...byNode, ...byPredicate, ...byWildcard]; | ||
| } | ||
| function precompileDOMRenderConfigOverrides(editorConfig, overrides) { | ||
| const typeTree = buildTypeTree(editorConfig); | ||
| const prerender = makePrerender(); | ||
| for (const override of sortedOverrides(overrides)) { | ||
| const predicateOrTypes = getPredicate(typeTree, override); | ||
| for (const k_ in prerender) { | ||
| const k = k_; | ||
| addOverride(prerender, k, predicateOrTypes, override[k]); | ||
| } | ||
| } | ||
| return prerender; | ||
| } | ||
| function identity(v) { | ||
| return v; | ||
| } | ||
| function compileDOMRenderConfigOverrides(editorConfig, { | ||
| overrides | ||
| }) { | ||
| const prerender = precompileDOMRenderConfigOverrides(editorConfig, overrides); | ||
| const dom = { | ||
| ...lexical.DEFAULT_EDITOR_DOM_CONFIG, | ||
| ...editorConfig.dom | ||
| }; | ||
| compilePrerenderKey(prerender, '$createDOM', dom, merge2, ignoreNext2); | ||
| compilePrerenderKey(prerender, '$exportDOM', dom, merge2, ignoreNext2); | ||
| compilePrerenderKey(prerender, '$extractWithChild', dom, merge5, ignoreNext5); | ||
| compilePrerenderKey(prerender, '$getDOMSlot', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$shouldExclude', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$shouldInclude', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$updateDOM', dom, merge4, ignoreNext4); | ||
| compilePrerenderKey(prerender, '$decorateDOM', dom, sequence4, identity); | ||
| return dom; | ||
| } | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * @experimental | ||
| * | ||
| * An extension that allows overriding the render and export behavior for an | ||
| * editor. This is highly experimental and subject to change from one version | ||
| * to the next. | ||
| **/ | ||
| const DOMRenderExtension = lexical.defineExtension({ | ||
| build(editor, config, state) { | ||
| return { | ||
| defaults: contextFromPairs(config.contextDefaults, undefined) | ||
| }; | ||
| }, | ||
| config: { | ||
| contextDefaults: [], | ||
| overrides: [] | ||
| }, | ||
| html: { | ||
| // Define a RootNode export for $generateDOMFromRoot | ||
| export: new Map([[lexical.RootNode, () => { | ||
| const element = document.createElement('div'); | ||
| element.role = 'textbox'; | ||
| return { | ||
| element | ||
| }; | ||
| }]]) | ||
| }, | ||
| init(editorConfig, config) { | ||
| editorConfig.dom = compileDOMRenderConfigOverrides(editorConfig, config); | ||
| }, | ||
| mergeConfig(config, partial) { | ||
| const merged = lexical.shallowMergeConfig(config, partial); | ||
| for (const k of ['overrides', 'contextDefaults']) { | ||
| if (partial[k]) { | ||
| merged[k] = [...config[k], ...partial[k]]; | ||
| } | ||
| } | ||
| return merged; | ||
| }, | ||
| name: DOMRenderExtensionName | ||
| }); | ||
| function isStyleRule(rule) { | ||
| return rule.constructor.name === CSSStyleRule.name; | ||
| } | ||
| /** | ||
| * Inlines CSS rules from <style> tags onto matching elements as inline styles. | ||
| * This is needed because apps like Excel generate HTML where styles live in | ||
| * class-based <style> rules (e.g. `.xl65 { background: #FFFF00; color: blue; }`) | ||
| * rather than inline styles. Since Lexical's import converters read inline styles, | ||
| * we resolve stylesheet rules into inline styles before conversion. | ||
| * | ||
| * Mutates the DOM in-place. Original inline styles always take precedence over | ||
| * stylesheet rules (matching CSS specificity behavior). | ||
| */ | ||
| function inlineStylesFromStyleSheets(doc) { | ||
| if (doc.querySelector('style') === null) { | ||
| return; | ||
| } | ||
| const originalInlineStyles = new Map(); | ||
| function getOriginalInlineProps(el) { | ||
| let props = originalInlineStyles.get(el); | ||
| if (props === undefined) { | ||
| props = new Set(); | ||
| for (let i = 0; i < el.style.length; i++) { | ||
| props.add(el.style[i]); | ||
| } | ||
| originalInlineStyles.set(el, props); | ||
| } | ||
| return props; | ||
| } | ||
| try { | ||
| for (const sheet of Array.from(doc.styleSheets)) { | ||
| let rules; | ||
| try { | ||
| rules = sheet.cssRules; | ||
| } catch (_unused) { | ||
| continue; | ||
| } | ||
| for (const rule of Array.from(rules)) { | ||
| if (!isStyleRule(rule)) { | ||
| continue; | ||
| } | ||
| let elements; | ||
| try { | ||
| elements = doc.querySelectorAll(rule.selectorText); | ||
| } catch (_unused2) { | ||
| continue; | ||
| } | ||
| for (const el of Array.from(elements)) { | ||
| if (!lexical.isHTMLElement(el)) { | ||
| continue; | ||
| } | ||
| const originalProps = getOriginalInlineProps(el); | ||
| for (let i = 0; i < rule.style.length; i++) { | ||
| const prop = rule.style[i]; | ||
| if (!originalProps.has(prop)) { | ||
| el.style.setProperty(prop, rule.style.getPropertyValue(prop), rule.style.getPropertyPriority(prop)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } catch (_unused3) { | ||
| // styleSheets API not supported in this environment | ||
| } | ||
| } | ||
| const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']); | ||
| /** | ||
| * How you parse your html string to get a document is left up to you. In the browser you can use the native | ||
| * DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom | ||
| * or an equivalent library and pass in the document here. | ||
| */ | ||
| function $generateNodesFromDOM(editor, dom) { | ||
| if (lexical.isDOMDocumentNode(dom)) { | ||
| inlineStylesFromStyleSheets(dom); | ||
| } | ||
| const elements = lexical.isDOMDocumentNode(dom) ? dom.body.childNodes : dom.childNodes; | ||
| const lexicalNodes = []; | ||
| const allArtificialNodes = []; | ||
| for (const element of elements) { | ||
| if (!IGNORE_TAGS.has(element.nodeName)) { | ||
| const lexicalNode = $createNodesFromDOM(element, editor, allArtificialNodes, false); | ||
| if (lexicalNode !== null) { | ||
| for (const node of lexicalNode) { | ||
| lexicalNodes.push(node); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| $unwrapArtificialNodes(allArtificialNodes); | ||
| return lexicalNodes; | ||
| } | ||
| /** | ||
| * Generate DOM nodes from the editor state into the given container element, | ||
| * using the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| function $generateDOMFromNodes(container, selection = null, editor = lexical.$getEditor()) { | ||
| return $withRenderContext([contextValue(RenderContextExport, true)], editor)(() => { | ||
| const root = lexical.$getRoot(); | ||
| const domConfig = lexical.$getEditorDOMRenderConfig(editor); | ||
| const parentElementAppend = container.append.bind(container); | ||
| for (const topLevelNode of root.getChildren()) { | ||
| $appendNodesToHTML(editor, topLevelNode, parentElementAppend, selection, domConfig); | ||
| } | ||
| return container; | ||
| }); | ||
| } | ||
| /** | ||
| * Generate DOM nodes from a root node into the given container element, | ||
| * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| function $generateDOMFromRoot(container, root = lexical.$getRoot()) { | ||
| const editor = lexical.$getEditor(); | ||
| return $withRenderContext([contextValue(RenderContextExport, true), contextValue(RenderContextRoot, true)], editor)(() => { | ||
| const selection = null; | ||
| const domConfig = lexical.$getEditorDOMRenderConfig(editor); | ||
| const parentElementAppend = container.append.bind(container); | ||
| $appendNodesToHTML(editor, root, parentElementAppend, selection, domConfig); | ||
| return container; | ||
| }); | ||
| } | ||
| function $generateHtmlFromNodes(editor, selection = null) { | ||
| if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') { | ||
| { | ||
| 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.`); | ||
| } | ||
| } | ||
| return $generateDOMFromNodes(document.createElement('div'), selection, editor).innerHTML; | ||
| } | ||
| function $appendNodesToHTML(editor, currentNode, parentElementAppend, selection$1 = null, domConfig = lexical.$getEditorDOMRenderConfig(editor)) { | ||
| let shouldInclude = domConfig.$shouldInclude(currentNode, selection$1, editor); | ||
| const shouldExclude = domConfig.$shouldExclude(currentNode, selection$1, editor); | ||
| let target = currentNode; | ||
| if (selection$1 !== null && lexical.$isTextNode(currentNode)) { | ||
| target = selection.$sliceSelectedTextNodeContent(selection$1, currentNode, 'clone'); | ||
| } | ||
| const exportProps = domConfig.$exportDOM(target, editor); | ||
| const { | ||
| element, | ||
| after, | ||
| append, | ||
| $getChildNodes | ||
| } = exportProps; | ||
| if (!element) { | ||
| return false; | ||
| } | ||
| const fragment = document.createDocumentFragment(); | ||
| const children = $getChildNodes ? $getChildNodes() : lexical.$isElementNode(target) ? target.getChildren() : []; | ||
| const fragmentAppend = fragment.append.bind(fragment); | ||
| for (const childNode of children) { | ||
| const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragmentAppend, selection$1, domConfig); | ||
| if (!shouldInclude && shouldIncludeChild && domConfig.$extractWithChild(currentNode, childNode, selection$1, 'html', editor)) { | ||
| shouldInclude = true; | ||
| } | ||
| } | ||
| if (shouldInclude && !shouldExclude) { | ||
| if (lexical.isHTMLElement(element) || lexical.isDocumentFragment(element)) { | ||
| if (append) { | ||
| append(fragment); | ||
| } else { | ||
| element.append(fragment); | ||
| } | ||
| } | ||
| parentElementAppend(element); | ||
| if (after) { | ||
| const newElement = after.call(target, element); | ||
| if (newElement) { | ||
| if (lexical.isDocumentFragment(element)) { | ||
| element.replaceChildren(newElement); | ||
| } else { | ||
| element.replaceWith(newElement); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| parentElementAppend(fragment); | ||
| } | ||
| return shouldInclude; | ||
| } | ||
| function getConversionFunction(domNode, editor) { | ||
| const { | ||
| nodeName | ||
| } = domNode; | ||
| const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase()); | ||
| let currentConversion = null; | ||
| if (cachedConversions !== undefined) { | ||
| for (const cachedConversion of cachedConversions) { | ||
| const domConversion = cachedConversion(domNode); | ||
| if (domConversion !== null && (currentConversion === null || | ||
| // Given equal priority, prefer the last registered importer | ||
| // which is typically an application custom node or HTMLConfig['import'] | ||
| (currentConversion.priority || 0) <= (domConversion.priority || 0))) { | ||
| currentConversion = domConversion; | ||
| } | ||
| } | ||
| } | ||
| return currentConversion !== null ? currentConversion.conversion : null; | ||
| } | ||
| function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorLexicalNode, forChildMap = new Map(), parentLexicalNode) { | ||
| const lexicalNodes = []; | ||
| if (IGNORE_TAGS.has(node.nodeName)) { | ||
| return lexicalNodes; | ||
| } | ||
| let currentLexicalNode = null; | ||
| const transformFunction = getConversionFunction(node, editor); | ||
| const transformOutput = transformFunction ? transformFunction(node) : null; | ||
| let postTransform = null; | ||
| if (transformOutput !== null) { | ||
| postTransform = transformOutput.after; | ||
| const transformNodes = transformOutput.node; | ||
| currentLexicalNode = Array.isArray(transformNodes) ? transformNodes[transformNodes.length - 1] : transformNodes; | ||
| if (currentLexicalNode !== null) { | ||
| for (const [, forChildFunction] of forChildMap) { | ||
| currentLexicalNode = forChildFunction(currentLexicalNode, parentLexicalNode); | ||
| if (!currentLexicalNode) { | ||
| break; | ||
| } | ||
| } | ||
| if (currentLexicalNode) { | ||
| lexicalNodes.push(...(Array.isArray(transformNodes) ? transformNodes : [currentLexicalNode])); | ||
| } | ||
| } | ||
| if (transformOutput.forChild != null) { | ||
| forChildMap.set(node.nodeName, transformOutput.forChild); | ||
| } | ||
| } | ||
| // If the DOM node doesn't have a transformer, we don't know what | ||
| // to do with it but we still need to process any childNodes. | ||
| const children = node.childNodes; | ||
| let childLexicalNodes = []; | ||
| const hasBlockAncestorLexicalNodeForChildren = currentLexicalNode != null && lexical.$isRootOrShadowRoot(currentLexicalNode) ? false : currentLexicalNode != null && lexical.$isBlockElementNode(currentLexicalNode) || hasBlockAncestorLexicalNode; | ||
| for (let i = 0; i < children.length; i++) { | ||
| childLexicalNodes.push(...$createNodesFromDOM(children[i], editor, allArtificialNodes, hasBlockAncestorLexicalNodeForChildren, new Map(forChildMap), currentLexicalNode)); | ||
| } | ||
| if (postTransform != null) { | ||
| childLexicalNodes = postTransform(childLexicalNodes); | ||
| } | ||
| if (lexical.isBlockDomNode(node)) { | ||
| if (!hasBlockAncestorLexicalNodeForChildren) { | ||
| childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, lexical.$createParagraphNode); | ||
| } else { | ||
| childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => { | ||
| const artificialNode = new lexical.ArtificialNode__DO_NOT_USE(); | ||
| allArtificialNodes.push(artificialNode); | ||
| return artificialNode; | ||
| }); | ||
| } | ||
| } | ||
| if (currentLexicalNode == null) { | ||
| if (childLexicalNodes.length > 0) { | ||
| // If it hasn't been converted to a LexicalNode, we hoist its children | ||
| // up to the same level as it. | ||
| for (const childNode of childLexicalNodes) { | ||
| lexicalNodes.push(childNode); | ||
| } | ||
| } else { | ||
| if (lexical.isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) { | ||
| // Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes | ||
| lexicalNodes.push(lexical.$createLineBreakNode()); | ||
| } | ||
| } | ||
| } else { | ||
| if (lexical.$isElementNode(currentLexicalNode)) { | ||
| // If the current node is a ElementNode after conversion, | ||
| // we can append all the children to it. | ||
| currentLexicalNode.append(...childLexicalNodes); | ||
| } | ||
| } | ||
| return lexicalNodes; | ||
| } | ||
| function wrapContinuousInlines(domNode, nodes, createWrapperFn) { | ||
| const textAlign = domNode.style.textAlign; | ||
| const out = []; | ||
| let continuousInlines = []; | ||
| // wrap contiguous inline child nodes in para | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if (lexical.$isBlockElementNode(node)) { | ||
| if (textAlign && !node.getFormat()) { | ||
| node.setFormat(textAlign); | ||
| } | ||
| out.push(node); | ||
| } else { | ||
| continuousInlines.push(node); | ||
| if (i === nodes.length - 1 || i < nodes.length - 1 && lexical.$isBlockElementNode(nodes[i + 1])) { | ||
| const wrapper = createWrapperFn(); | ||
| wrapper.setFormat(textAlign); | ||
| wrapper.append(...continuousInlines); | ||
| out.push(wrapper); | ||
| continuousInlines = []; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function $unwrapArtificialNodes(allArtificialNodes) { | ||
| // Replace artificial node with its children, inserting a linebreak | ||
| // between adjacent artificial nodes | ||
| for (const node of allArtificialNodes) { | ||
| if (node.getParent() && node.getNextSibling() instanceof lexical.ArtificialNode__DO_NOT_USE) { | ||
| node.insertAfter(lexical.$createLineBreakNode()); | ||
| } | ||
| } | ||
| for (const node of allArtificialNodes) { | ||
| const parent = node.getParent(); | ||
| if (parent) { | ||
| parent.splice(node.getIndexWithinParent(), 1, node.getChildren()); | ||
| } | ||
| } | ||
| } | ||
| function isDomNodeBetweenTwoInlineNodes(node) { | ||
| if (node.nextSibling == null || node.previousSibling == null) { | ||
| return false; | ||
| } | ||
| return lexical.isInlineDomNode(node.nextSibling) && lexical.isInlineDomNode(node.previousSibling); | ||
| } | ||
| exports.$generateDOMFromNodes = $generateDOMFromNodes; | ||
| exports.$generateDOMFromRoot = $generateDOMFromRoot; | ||
| exports.$generateHtmlFromNodes = $generateHtmlFromNodes; | ||
| exports.$generateNodesFromDOM = $generateNodesFromDOM; | ||
| exports.$getRenderContextValue = $getRenderContextValue; | ||
| exports.$withRenderContext = $withRenderContext; | ||
| exports.DOMRenderExtension = DOMRenderExtension; | ||
| exports.RenderContextExport = RenderContextExport; | ||
| exports.RenderContextRoot = RenderContextRoot; | ||
| exports.contextUpdater = contextUpdater; | ||
| exports.contextValue = contextValue; | ||
| exports.createRenderState = createRenderState; | ||
| exports.domOverride = domOverride; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import { $sliceSelectedTextNodeContent } from '@lexical/selection'; | ||
| 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'; | ||
| import { getPeerDependencyFromEditor, getKnownTypesAndNodes } from '@lexical/extension'; | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| // Do not require this module directly! Use normal `invariant` calls. | ||
| function formatDevErrorMessage(message) { | ||
| throw new Error(message); | ||
| } | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| let activeContext; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * The LexicalEditor with context | ||
| */ | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param contextRecord The ContextRecord | ||
| * @param cfg The configuration | ||
| * @returns The value or defaultValue of cfg | ||
| */ | ||
| function getContextValue(contextRecord, cfg) { | ||
| const { | ||
| key | ||
| } = cfg; | ||
| return contextRecord && key in contextRecord ? contextRecord[key] : cfg.defaultValue; | ||
| } | ||
| function getEditorContext(editor) { | ||
| return activeContext && activeContext.editor === editor ? activeContext : undefined; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * @param sym The symbol for this ContextRecord (e.g. DOMRenderContextSymbol) | ||
| * @param editor The editor | ||
| * @returns The current context or undefined | ||
| */ | ||
| function getContextRecord(sym, editor) { | ||
| const editorContext = getEditorContext(editor); | ||
| return editorContext && editorContext[sym]; | ||
| } | ||
| function toPair(contextRecord, pairOrUpdater) { | ||
| if ('cfg' in pairOrUpdater) { | ||
| const { | ||
| cfg, | ||
| updater | ||
| } = pairOrUpdater; | ||
| return [cfg, updater(getContextValue(contextRecord, cfg))]; | ||
| } | ||
| return pairOrUpdater; | ||
| } | ||
| /** | ||
| * Construct a new context from a parent context and pairs | ||
| * | ||
| * @param pairs The pairs and updaters to build the context from | ||
| * @param parent The parent context | ||
| * @returns The new context | ||
| */ | ||
| function contextFromPairs(pairs, parent) { | ||
| let rval = parent; | ||
| for (const pairOrUpdater of pairs) { | ||
| const [k, v] = toPair(rval, pairOrUpdater); | ||
| const key = k.key; | ||
| if (rval === parent && getContextValue(rval, k) === v) { | ||
| continue; | ||
| } | ||
| const ctx = rval || createChildContext(parent); | ||
| ctx[key] = v; | ||
| rval = ctx; | ||
| } | ||
| return rval; | ||
| } | ||
| function createChildContext(parent) { | ||
| return Object.create(parent || null); | ||
| } | ||
| /** | ||
| * Create a context config pair that sets a value in the render context. | ||
| * @experimental | ||
| */ | ||
| function contextValue(cfg, value) { | ||
| return [cfg, value]; | ||
| } | ||
| /** | ||
| * Create a context config updater that transforms a value in the render context. | ||
| * @experimental | ||
| */ | ||
| function contextUpdater(cfg, updater) { | ||
| return { | ||
| cfg, | ||
| updater | ||
| }; | ||
| } | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function $withFullContext(sym, contextRecord, f, editor = $getEditor()) { | ||
| const prevDOMContext = activeContext; | ||
| const parentEditorContext = getEditorContext(editor); | ||
| try { | ||
| activeContext = { | ||
| ...parentEditorContext, | ||
| editor, | ||
| [sym]: contextRecord | ||
| }; | ||
| return f(); | ||
| } finally { | ||
| activeContext = prevDOMContext; | ||
| } | ||
| } | ||
| /** | ||
| * @internal | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function $withContext(sym, $defaults = () => undefined) { | ||
| return (cfg, editor = $getEditor()) => { | ||
| return f => { | ||
| const parentEditorContext = getEditorContext(editor); | ||
| const parentContextRecord = parentEditorContext && parentEditorContext[sym]; | ||
| const contextRecord = contextFromPairs(cfg, parentContextRecord || $defaults(editor)); | ||
| if (!contextRecord || contextRecord === parentContextRecord) { | ||
| return f(); | ||
| } | ||
| return $withFullContext(sym, contextRecord, f, editor); | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * @internal | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function createContextState(tag, name, getDefaultValue, isEqual) { | ||
| return Object.assign(createState(Symbol(name), { | ||
| isEqual, | ||
| parse: getDefaultValue | ||
| }), { | ||
| [tag]: true | ||
| }); | ||
| } | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| const DOMRenderExtensionName = '@lexical/html/DOM'; | ||
| const DOMRenderContextSymbol = Symbol.for('@lexical/html/DOMExportContext'); | ||
| const ALWAYS_TRUE = () => true; | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * Create a context state to be used during render. | ||
| * | ||
| * Note that to support the ValueOrUpdater pattern you can not use a | ||
| * function for V (but you may wrap it in an array or object). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function createRenderState(name, getDefaultValue, isEqual) { | ||
| return createContextState(DOMRenderContextSymbol, name, getDefaultValue, isEqual); | ||
| } | ||
| /** | ||
| * Render context state that is true if the export was initiated from the root of the document. | ||
| * @experimental | ||
| */ | ||
| const RenderContextRoot = createRenderState('root', Boolean); | ||
| /** | ||
| * Render context state that is true if this is an export operation ($generateHtmlFromNodes). | ||
| * @experimental | ||
| */ | ||
| const RenderContextExport = createRenderState('isExport', Boolean); | ||
| function getDefaultRenderContext(editor) { | ||
| const dep = getPeerDependencyFromEditor(editor, DOMRenderExtensionName); | ||
| return dep ? dep.output.defaults : undefined; | ||
| } | ||
| function getRenderContext(editor) { | ||
| return getContextRecord(DOMRenderContextSymbol, editor) || getDefaultRenderContext(editor); | ||
| } | ||
| /** | ||
| * Get a render context value during a DOM render or export operation. | ||
| * @experimental | ||
| */ | ||
| function $getRenderContextValue(cfg, editor = $getEditor()) { | ||
| return getContextValue(getRenderContext(editor), cfg); | ||
| } | ||
| /** | ||
| * Execute a callback within a render context with the given config pairs. | ||
| * @experimental | ||
| */ | ||
| const $withRenderContext = $withContext(DOMRenderContextSymbol, getDefaultRenderContext); | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * A convenience function for type inference when constructing DOM overrides for | ||
| * use with {@link DOMRenderExtension}. | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| function domOverride(nodes, config) { | ||
| return { | ||
| ...config, | ||
| nodes | ||
| }; | ||
| } | ||
| function buildTypeTree(editorConfig) { | ||
| const t = {}; | ||
| const { | ||
| nodes | ||
| } = getKnownTypesAndNodes(editorConfig); | ||
| for (const klass of nodes) { | ||
| const type = klass.getType(); | ||
| t[type] = { | ||
| klass, | ||
| types: {} | ||
| }; | ||
| } | ||
| for (const baseRec of Object.values(t)) { | ||
| if (baseRec) { | ||
| const baseType = baseRec.klass.getType(); | ||
| for (let { | ||
| klass | ||
| } = baseRec; $isLexicalNode(klass.prototype); klass = Object.getPrototypeOf(klass)) { | ||
| const { | ||
| ownNodeType | ||
| } = getStaticNodeConfig(klass); | ||
| const superRec = ownNodeType && t[ownNodeType]; | ||
| if (superRec) { | ||
| superRec.types[baseType] = true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return t; | ||
| } | ||
| function buildNodePredicate(klass) { | ||
| return node => node instanceof klass; | ||
| } | ||
| function getPredicate(typeTree, { | ||
| nodes | ||
| }) { | ||
| if (nodes === '*') { | ||
| return ALWAYS_TRUE; | ||
| } | ||
| let types = {}; | ||
| const predicates = []; | ||
| for (const klassOrPredicate of nodes) { | ||
| if ('getType' in klassOrPredicate) { | ||
| const type = klassOrPredicate.getType(); | ||
| if (types) { | ||
| const tree = typeTree[type]; | ||
| if (!(tree !== undefined)) { | ||
| formatDevErrorMessage(`Node class ${klassOrPredicate.name} with type ${type} not registered in editor`); | ||
| } | ||
| types = Object.assign(types, tree.types); | ||
| } | ||
| predicates.push(buildNodePredicate(klassOrPredicate)); | ||
| } else { | ||
| types = undefined; | ||
| predicates.push(klassOrPredicate); | ||
| } | ||
| } | ||
| if (types) { | ||
| return types; | ||
| } else if (predicates.length === 1) { | ||
| return predicates[0]; | ||
| } | ||
| return node => { | ||
| for (const predicate of predicates) { | ||
| if (predicate(node)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| } | ||
| function makePrerender() { | ||
| return { | ||
| $createDOM: [], | ||
| $decorateDOM: [], | ||
| $exportDOM: [], | ||
| $extractWithChild: [], | ||
| $getDOMSlot: [], | ||
| $shouldExclude: [], | ||
| $shouldInclude: [], | ||
| $updateDOM: [] | ||
| }; | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| function ignoreNext2(acc) { | ||
| return (node, _$next, editor) => acc(node, editor); | ||
| } | ||
| function ignoreNext3(acc) { | ||
| return (node, a, _$next, editor) => acc(node, a, editor); | ||
| } | ||
| function ignoreNext4(acc) { | ||
| return (node, a, b, _$next, editor) => acc(node, a, b, editor); | ||
| } | ||
| function ignoreNext5(acc) { | ||
| return (node, a, b, c, _$next, editor) => acc(node, a, b, c, editor); | ||
| } | ||
| function merge2($acc, $getOverride) { | ||
| return (node, editor) => { | ||
| const $next = () => $acc(node, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge3(acc, $getOverride) { | ||
| return (node, a, editor) => { | ||
| const $next = () => acc(node, a, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge4($acc, $getOverride) { | ||
| return (node, a, b, editor) => { | ||
| const $next = () => $acc(node, a, b, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, b, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function merge5(acc, $getOverride) { | ||
| return (node, a, b, c, editor) => { | ||
| const $next = () => acc(node, a, b, c, editor); | ||
| const $override = $getOverride(node); | ||
| return $override ? $override(node, a, b, c, $next, editor) : $next(); | ||
| }; | ||
| } | ||
| function sequence4($acc, $getOverride) { | ||
| return (node, a, b, editor) => { | ||
| $acc(node, a, b, editor); | ||
| const $override = $getOverride(node); | ||
| if ($override) { | ||
| $override(node, a, b, editor); | ||
| } | ||
| }; | ||
| } | ||
| function compilePrerenderKey(prerender, k, defaults, mergeFunction, ignoreNextFunction) { | ||
| let acc = defaults[k]; | ||
| for (const pair of prerender[k]) { | ||
| if (typeof pair[0] === 'function') { | ||
| const [$predicate, $override] = pair; | ||
| acc = mergeFunction(acc, node => $predicate(node) && $override || undefined); | ||
| } else { | ||
| const typeOverrides = pair[1]; | ||
| const compiled = {}; | ||
| for (const type in typeOverrides) { | ||
| const arr = typeOverrides[type]; | ||
| if (arr) { | ||
| compiled[type] = arr.reduce(($acc, $override) => mergeFunction($acc, () => $override), acc); | ||
| } | ||
| } | ||
| acc = mergeFunction(acc, node => { | ||
| const f = compiled[node.getType()]; | ||
| return f && ignoreNextFunction(f); | ||
| }); | ||
| } | ||
| } | ||
| defaults[k] = acc; | ||
| } | ||
| function addOverride(prerender, k, predicateOrTypes, override) { | ||
| if (!override) { | ||
| return; | ||
| } | ||
| const arr = prerender[k]; | ||
| if (typeof predicateOrTypes === 'function') { | ||
| arr.push([predicateOrTypes, override]); | ||
| } else { | ||
| const last = arr[arr.length - 1]; | ||
| let types; | ||
| if (last && last[0] === 'types') { | ||
| types = last[1]; | ||
| } else { | ||
| types = {}; | ||
| arr.push(['types', types]); | ||
| } | ||
| for (const type in predicateOrTypes) { | ||
| const typeArr = types[type] || []; | ||
| types[type] = typeArr; | ||
| typeArr.push(override); | ||
| } | ||
| } | ||
| } | ||
| function isWildcard(override) { | ||
| return override.nodes === '*'; | ||
| } | ||
| function sortedOverrides(overrides) { | ||
| const byWildcard = []; | ||
| const byPredicate = []; | ||
| const byNode = []; | ||
| for (const override of overrides) { | ||
| if (isWildcard(override)) { | ||
| byWildcard.push(override); | ||
| } else if (Array.isArray(override.nodes)) { | ||
| for (const klassOrPredicate of override.nodes) { | ||
| if ($isLexicalNode(klassOrPredicate.prototype)) { | ||
| byNode.push(override.nodes.length === 1 ? override : { | ||
| ...override, | ||
| nodes: [klassOrPredicate] | ||
| }); | ||
| } else { | ||
| byPredicate.push(override.nodes.length === 1 ? override : { | ||
| ...override, | ||
| nodes: [klassOrPredicate] | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const depths = new Map(); | ||
| const depthOf = klass => { | ||
| let depth = depths.get(klass); | ||
| if (depth === undefined) { | ||
| depth = 0; | ||
| for (let k = klass; $isLexicalNode(k.prototype); k = Object.getPrototypeOf(k)) { | ||
| depth++; | ||
| } | ||
| depths.set(klass, depth); | ||
| } | ||
| return depth; | ||
| }; | ||
| byNode.sort((a, b) => depthOf(a.nodes[0]) - depthOf(b.nodes[0])); | ||
| return [...byNode, ...byPredicate, ...byWildcard]; | ||
| } | ||
| function precompileDOMRenderConfigOverrides(editorConfig, overrides) { | ||
| const typeTree = buildTypeTree(editorConfig); | ||
| const prerender = makePrerender(); | ||
| for (const override of sortedOverrides(overrides)) { | ||
| const predicateOrTypes = getPredicate(typeTree, override); | ||
| for (const k_ in prerender) { | ||
| const k = k_; | ||
| addOverride(prerender, k, predicateOrTypes, override[k]); | ||
| } | ||
| } | ||
| return prerender; | ||
| } | ||
| function identity(v) { | ||
| return v; | ||
| } | ||
| function compileDOMRenderConfigOverrides(editorConfig, { | ||
| overrides | ||
| }) { | ||
| const prerender = precompileDOMRenderConfigOverrides(editorConfig, overrides); | ||
| const dom = { | ||
| ...DEFAULT_EDITOR_DOM_CONFIG, | ||
| ...editorConfig.dom | ||
| }; | ||
| compilePrerenderKey(prerender, '$createDOM', dom, merge2, ignoreNext2); | ||
| compilePrerenderKey(prerender, '$exportDOM', dom, merge2, ignoreNext2); | ||
| compilePrerenderKey(prerender, '$extractWithChild', dom, merge5, ignoreNext5); | ||
| compilePrerenderKey(prerender, '$getDOMSlot', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$shouldExclude', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$shouldInclude', dom, merge3, ignoreNext3); | ||
| compilePrerenderKey(prerender, '$updateDOM', dom, merge4, ignoreNext4); | ||
| compilePrerenderKey(prerender, '$decorateDOM', dom, sequence4, identity); | ||
| return dom; | ||
| } | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| /** | ||
| * @experimental | ||
| * | ||
| * An extension that allows overriding the render and export behavior for an | ||
| * editor. This is highly experimental and subject to change from one version | ||
| * to the next. | ||
| **/ | ||
| const DOMRenderExtension = defineExtension({ | ||
| build(editor, config, state) { | ||
| return { | ||
| defaults: contextFromPairs(config.contextDefaults, undefined) | ||
| }; | ||
| }, | ||
| config: { | ||
| contextDefaults: [], | ||
| overrides: [] | ||
| }, | ||
| html: { | ||
| // Define a RootNode export for $generateDOMFromRoot | ||
| export: new Map([[RootNode, () => { | ||
| const element = document.createElement('div'); | ||
| element.role = 'textbox'; | ||
| return { | ||
| element | ||
| }; | ||
| }]]) | ||
| }, | ||
| init(editorConfig, config) { | ||
| editorConfig.dom = compileDOMRenderConfigOverrides(editorConfig, config); | ||
| }, | ||
| mergeConfig(config, partial) { | ||
| const merged = shallowMergeConfig(config, partial); | ||
| for (const k of ['overrides', 'contextDefaults']) { | ||
| if (partial[k]) { | ||
| merged[k] = [...config[k], ...partial[k]]; | ||
| } | ||
| } | ||
| return merged; | ||
| }, | ||
| name: DOMRenderExtensionName | ||
| }); | ||
| function isStyleRule(rule) { | ||
| return rule.constructor.name === CSSStyleRule.name; | ||
| } | ||
| /** | ||
| * Inlines CSS rules from <style> tags onto matching elements as inline styles. | ||
| * This is needed because apps like Excel generate HTML where styles live in | ||
| * class-based <style> rules (e.g. `.xl65 { background: #FFFF00; color: blue; }`) | ||
| * rather than inline styles. Since Lexical's import converters read inline styles, | ||
| * we resolve stylesheet rules into inline styles before conversion. | ||
| * | ||
| * Mutates the DOM in-place. Original inline styles always take precedence over | ||
| * stylesheet rules (matching CSS specificity behavior). | ||
| */ | ||
| function inlineStylesFromStyleSheets(doc) { | ||
| if (doc.querySelector('style') === null) { | ||
| return; | ||
| } | ||
| const originalInlineStyles = new Map(); | ||
| function getOriginalInlineProps(el) { | ||
| let props = originalInlineStyles.get(el); | ||
| if (props === undefined) { | ||
| props = new Set(); | ||
| for (let i = 0; i < el.style.length; i++) { | ||
| props.add(el.style[i]); | ||
| } | ||
| originalInlineStyles.set(el, props); | ||
| } | ||
| return props; | ||
| } | ||
| try { | ||
| for (const sheet of Array.from(doc.styleSheets)) { | ||
| let rules; | ||
| try { | ||
| rules = sheet.cssRules; | ||
| } catch (_unused) { | ||
| continue; | ||
| } | ||
| for (const rule of Array.from(rules)) { | ||
| if (!isStyleRule(rule)) { | ||
| continue; | ||
| } | ||
| let elements; | ||
| try { | ||
| elements = doc.querySelectorAll(rule.selectorText); | ||
| } catch (_unused2) { | ||
| continue; | ||
| } | ||
| for (const el of Array.from(elements)) { | ||
| if (!isHTMLElement(el)) { | ||
| continue; | ||
| } | ||
| const originalProps = getOriginalInlineProps(el); | ||
| for (let i = 0; i < rule.style.length; i++) { | ||
| const prop = rule.style[i]; | ||
| if (!originalProps.has(prop)) { | ||
| el.style.setProperty(prop, rule.style.getPropertyValue(prop), rule.style.getPropertyPriority(prop)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } catch (_unused3) { | ||
| // styleSheets API not supported in this environment | ||
| } | ||
| } | ||
| const IGNORE_TAGS = new Set(['STYLE', 'SCRIPT']); | ||
| /** | ||
| * How you parse your html string to get a document is left up to you. In the browser you can use the native | ||
| * DOMParser API to generate a document (see clipboard.ts), but to use in a headless environment you can use JSDom | ||
| * or an equivalent library and pass in the document here. | ||
| */ | ||
| function $generateNodesFromDOM(editor, dom) { | ||
| if (isDOMDocumentNode(dom)) { | ||
| inlineStylesFromStyleSheets(dom); | ||
| } | ||
| const elements = isDOMDocumentNode(dom) ? dom.body.childNodes : dom.childNodes; | ||
| const lexicalNodes = []; | ||
| const allArtificialNodes = []; | ||
| for (const element of elements) { | ||
| if (!IGNORE_TAGS.has(element.nodeName)) { | ||
| const lexicalNode = $createNodesFromDOM(element, editor, allArtificialNodes, false); | ||
| if (lexicalNode !== null) { | ||
| for (const node of lexicalNode) { | ||
| lexicalNodes.push(node); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| $unwrapArtificialNodes(allArtificialNodes); | ||
| return lexicalNodes; | ||
| } | ||
| /** | ||
| * Generate DOM nodes from the editor state into the given container element, | ||
| * using the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| function $generateDOMFromNodes(container, selection = null, editor = $getEditor()) { | ||
| return $withRenderContext([contextValue(RenderContextExport, true)], editor)(() => { | ||
| const root = $getRoot(); | ||
| const domConfig = $getEditorDOMRenderConfig(editor); | ||
| const parentElementAppend = container.append.bind(container); | ||
| for (const topLevelNode of root.getChildren()) { | ||
| $appendNodesToHTML(editor, topLevelNode, parentElementAppend, selection, domConfig); | ||
| } | ||
| return container; | ||
| }); | ||
| } | ||
| /** | ||
| * Generate DOM nodes from a root node into the given container element, | ||
| * including the root node itself. Uses the editor's {@link EditorDOMRenderConfig}. | ||
| * @experimental | ||
| */ | ||
| function $generateDOMFromRoot(container, root = $getRoot()) { | ||
| const editor = $getEditor(); | ||
| return $withRenderContext([contextValue(RenderContextExport, true), contextValue(RenderContextRoot, true)], editor)(() => { | ||
| const selection = null; | ||
| const domConfig = $getEditorDOMRenderConfig(editor); | ||
| const parentElementAppend = container.append.bind(container); | ||
| $appendNodesToHTML(editor, root, parentElementAppend, selection, domConfig); | ||
| return container; | ||
| }); | ||
| } | ||
| function $generateHtmlFromNodes(editor, selection = null) { | ||
| if (typeof document === 'undefined' || typeof window === 'undefined' && typeof global.window === 'undefined') { | ||
| { | ||
| 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.`); | ||
| } | ||
| } | ||
| return $generateDOMFromNodes(document.createElement('div'), selection, editor).innerHTML; | ||
| } | ||
| function $appendNodesToHTML(editor, currentNode, parentElementAppend, selection = null, domConfig = $getEditorDOMRenderConfig(editor)) { | ||
| let shouldInclude = domConfig.$shouldInclude(currentNode, selection, editor); | ||
| const shouldExclude = domConfig.$shouldExclude(currentNode, selection, editor); | ||
| let target = currentNode; | ||
| if (selection !== null && $isTextNode(currentNode)) { | ||
| target = $sliceSelectedTextNodeContent(selection, currentNode, 'clone'); | ||
| } | ||
| const exportProps = domConfig.$exportDOM(target, editor); | ||
| const { | ||
| element, | ||
| after, | ||
| append, | ||
| $getChildNodes | ||
| } = exportProps; | ||
| if (!element) { | ||
| return false; | ||
| } | ||
| const fragment = document.createDocumentFragment(); | ||
| const children = $getChildNodes ? $getChildNodes() : $isElementNode(target) ? target.getChildren() : []; | ||
| const fragmentAppend = fragment.append.bind(fragment); | ||
| for (const childNode of children) { | ||
| const shouldIncludeChild = $appendNodesToHTML(editor, childNode, fragmentAppend, selection, domConfig); | ||
| if (!shouldInclude && shouldIncludeChild && domConfig.$extractWithChild(currentNode, childNode, selection, 'html', editor)) { | ||
| shouldInclude = true; | ||
| } | ||
| } | ||
| if (shouldInclude && !shouldExclude) { | ||
| if (isHTMLElement(element) || isDocumentFragment(element)) { | ||
| if (append) { | ||
| append(fragment); | ||
| } else { | ||
| element.append(fragment); | ||
| } | ||
| } | ||
| parentElementAppend(element); | ||
| if (after) { | ||
| const newElement = after.call(target, element); | ||
| if (newElement) { | ||
| if (isDocumentFragment(element)) { | ||
| element.replaceChildren(newElement); | ||
| } else { | ||
| element.replaceWith(newElement); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| parentElementAppend(fragment); | ||
| } | ||
| return shouldInclude; | ||
| } | ||
| function getConversionFunction(domNode, editor) { | ||
| const { | ||
| nodeName | ||
| } = domNode; | ||
| const cachedConversions = editor._htmlConversions.get(nodeName.toLowerCase()); | ||
| let currentConversion = null; | ||
| if (cachedConversions !== undefined) { | ||
| for (const cachedConversion of cachedConversions) { | ||
| const domConversion = cachedConversion(domNode); | ||
| if (domConversion !== null && (currentConversion === null || | ||
| // Given equal priority, prefer the last registered importer | ||
| // which is typically an application custom node or HTMLConfig['import'] | ||
| (currentConversion.priority || 0) <= (domConversion.priority || 0))) { | ||
| currentConversion = domConversion; | ||
| } | ||
| } | ||
| } | ||
| return currentConversion !== null ? currentConversion.conversion : null; | ||
| } | ||
| function $createNodesFromDOM(node, editor, allArtificialNodes, hasBlockAncestorLexicalNode, forChildMap = new Map(), parentLexicalNode) { | ||
| const lexicalNodes = []; | ||
| if (IGNORE_TAGS.has(node.nodeName)) { | ||
| return lexicalNodes; | ||
| } | ||
| let currentLexicalNode = null; | ||
| const transformFunction = getConversionFunction(node, editor); | ||
| const transformOutput = transformFunction ? transformFunction(node) : null; | ||
| let postTransform = null; | ||
| if (transformOutput !== null) { | ||
| postTransform = transformOutput.after; | ||
| const transformNodes = transformOutput.node; | ||
| currentLexicalNode = Array.isArray(transformNodes) ? transformNodes[transformNodes.length - 1] : transformNodes; | ||
| if (currentLexicalNode !== null) { | ||
| for (const [, forChildFunction] of forChildMap) { | ||
| currentLexicalNode = forChildFunction(currentLexicalNode, parentLexicalNode); | ||
| if (!currentLexicalNode) { | ||
| break; | ||
| } | ||
| } | ||
| if (currentLexicalNode) { | ||
| lexicalNodes.push(...(Array.isArray(transformNodes) ? transformNodes : [currentLexicalNode])); | ||
| } | ||
| } | ||
| if (transformOutput.forChild != null) { | ||
| forChildMap.set(node.nodeName, transformOutput.forChild); | ||
| } | ||
| } | ||
| // If the DOM node doesn't have a transformer, we don't know what | ||
| // to do with it but we still need to process any childNodes. | ||
| const children = node.childNodes; | ||
| let childLexicalNodes = []; | ||
| const hasBlockAncestorLexicalNodeForChildren = currentLexicalNode != null && $isRootOrShadowRoot(currentLexicalNode) ? false : currentLexicalNode != null && $isBlockElementNode(currentLexicalNode) || hasBlockAncestorLexicalNode; | ||
| for (let i = 0; i < children.length; i++) { | ||
| childLexicalNodes.push(...$createNodesFromDOM(children[i], editor, allArtificialNodes, hasBlockAncestorLexicalNodeForChildren, new Map(forChildMap), currentLexicalNode)); | ||
| } | ||
| if (postTransform != null) { | ||
| childLexicalNodes = postTransform(childLexicalNodes); | ||
| } | ||
| if (isBlockDomNode(node)) { | ||
| if (!hasBlockAncestorLexicalNodeForChildren) { | ||
| childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, $createParagraphNode); | ||
| } else { | ||
| childLexicalNodes = wrapContinuousInlines(node, childLexicalNodes, () => { | ||
| const artificialNode = new ArtificialNode__DO_NOT_USE(); | ||
| allArtificialNodes.push(artificialNode); | ||
| return artificialNode; | ||
| }); | ||
| } | ||
| } | ||
| if (currentLexicalNode == null) { | ||
| if (childLexicalNodes.length > 0) { | ||
| // If it hasn't been converted to a LexicalNode, we hoist its children | ||
| // up to the same level as it. | ||
| for (const childNode of childLexicalNodes) { | ||
| lexicalNodes.push(childNode); | ||
| } | ||
| } else { | ||
| if (isBlockDomNode(node) && isDomNodeBetweenTwoInlineNodes(node)) { | ||
| // Empty block dom node that hasnt been converted, we replace it with a linebreak if its between inline nodes | ||
| lexicalNodes.push($createLineBreakNode()); | ||
| } | ||
| } | ||
| } else { | ||
| if ($isElementNode(currentLexicalNode)) { | ||
| // If the current node is a ElementNode after conversion, | ||
| // we can append all the children to it. | ||
| currentLexicalNode.append(...childLexicalNodes); | ||
| } | ||
| } | ||
| return lexicalNodes; | ||
| } | ||
| function wrapContinuousInlines(domNode, nodes, createWrapperFn) { | ||
| const textAlign = domNode.style.textAlign; | ||
| const out = []; | ||
| let continuousInlines = []; | ||
| // wrap contiguous inline child nodes in para | ||
| for (let i = 0; i < nodes.length; i++) { | ||
| const node = nodes[i]; | ||
| if ($isBlockElementNode(node)) { | ||
| if (textAlign && !node.getFormat()) { | ||
| node.setFormat(textAlign); | ||
| } | ||
| out.push(node); | ||
| } else { | ||
| continuousInlines.push(node); | ||
| if (i === nodes.length - 1 || i < nodes.length - 1 && $isBlockElementNode(nodes[i + 1])) { | ||
| const wrapper = createWrapperFn(); | ||
| wrapper.setFormat(textAlign); | ||
| wrapper.append(...continuousInlines); | ||
| out.push(wrapper); | ||
| continuousInlines = []; | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function $unwrapArtificialNodes(allArtificialNodes) { | ||
| // Replace artificial node with its children, inserting a linebreak | ||
| // between adjacent artificial nodes | ||
| for (const node of allArtificialNodes) { | ||
| if (node.getParent() && node.getNextSibling() instanceof ArtificialNode__DO_NOT_USE) { | ||
| node.insertAfter($createLineBreakNode()); | ||
| } | ||
| } | ||
| for (const node of allArtificialNodes) { | ||
| const parent = node.getParent(); | ||
| if (parent) { | ||
| parent.splice(node.getIndexWithinParent(), 1, node.getChildren()); | ||
| } | ||
| } | ||
| } | ||
| function isDomNodeBetweenTwoInlineNodes(node) { | ||
| if (node.nextSibling == null || node.previousSibling == null) { | ||
| return false; | ||
| } | ||
| return isInlineDomNode(node.nextSibling) && isInlineDomNode(node.previousSibling); | ||
| } | ||
| export { $generateDOMFromNodes, $generateDOMFromRoot, $generateHtmlFromNodes, $generateNodesFromDOM, $getRenderContextValue, $withRenderContext, DOMRenderExtension, RenderContextExport, RenderContextRoot, contextUpdater, contextValue, createRenderState, domOverride }; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| 'use strict' | ||
| const LexicalHtml = process.env.NODE_ENV !== 'production' ? require('./LexicalHtml.dev.js') : require('./LexicalHtml.prod.js'); | ||
| module.exports = LexicalHtml; |
Sorry, the diff of this file is not supported yet
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import * as modDev from './LexicalHtml.dev.mjs'; | ||
| import * as modProd from './LexicalHtml.prod.mjs'; | ||
| const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd; | ||
| export const $generateDOMFromNodes = mod.$generateDOMFromNodes; | ||
| export const $generateDOMFromRoot = mod.$generateDOMFromRoot; | ||
| export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes; | ||
| export const $generateNodesFromDOM = mod.$generateNodesFromDOM; | ||
| export const $getRenderContextValue = mod.$getRenderContextValue; | ||
| export const $withRenderContext = mod.$withRenderContext; | ||
| export const DOMRenderExtension = mod.DOMRenderExtension; | ||
| export const RenderContextExport = mod.RenderContextExport; | ||
| export const RenderContextRoot = mod.RenderContextRoot; | ||
| export const contextUpdater = mod.contextUpdater; | ||
| export const contextValue = mod.contextValue; | ||
| export const createRenderState = mod.createRenderState; | ||
| export const domOverride = mod.domOverride; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| const mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalHtml.dev.mjs') : import('./LexicalHtml.prod.mjs')); | ||
| export const $generateDOMFromNodes = mod.$generateDOMFromNodes; | ||
| export const $generateDOMFromRoot = mod.$generateDOMFromRoot; | ||
| export const $generateHtmlFromNodes = mod.$generateHtmlFromNodes; | ||
| export const $generateNodesFromDOM = mod.$generateNodesFromDOM; | ||
| export const $getRenderContextValue = mod.$getRenderContextValue; | ||
| export const $withRenderContext = mod.$withRenderContext; | ||
| export const DOMRenderExtension = mod.DOMRenderExtension; | ||
| export const RenderContextExport = mod.RenderContextExport; | ||
| export const RenderContextRoot = mod.RenderContextRoot; | ||
| export const contextUpdater = mod.contextUpdater; | ||
| export const contextValue = mod.contextValue; | ||
| export const createRenderState = mod.createRenderState; | ||
| export const domOverride = mod.domOverride; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| "use strict";var e=require("@lexical/selection"),t=require("lexical"),n=require("@lexical/extension");function o(e,...t){const n=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",e);for(const e of t)o.append("v",e);throw n.search=o.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}let r;function s(e,t){const{key:n}=t;return e&&n in e?e[n]:t.defaultValue}function i(e){return r&&r.editor===e?r:void 0}function c(e,t){if("cfg"in t){const{cfg:n,updater:o}=t;return[n,o(s(e,n))]}return t}function l(e,t){let n=t;for(const o of e){const[e,r]=c(n,o),i=e.key;if(n===t&&s(n,e)===r)continue;const l=n||u(t);l[i]=r,n=l}return n}function u(e){return Object.create(e||null)}function f(e,t){return[e,t]}const d="@lexical/html/DOM",a=Symbol.for("@lexical/html/DOMExportContext"),p=()=>!0;function g(e,n,o){return function(e,n,o,r){return Object.assign(t.createState(Symbol(n),{isEqual:r,parse:o}),{[e]:!0})}(a,e,n,o)}const h=g("root",Boolean),m=g("isExport",Boolean);function y(e){const t=n.getPeerDependencyFromEditor(e,d);return t?t.output.defaults:void 0}function x(e){return function(e,t){const n=i(t);return n&&n[e]}(a,e)||y(e)}const $=function(e,n=()=>{}){return(o,s=t.$getEditor())=>c=>{const u=i(s),f=u&&u[e],d=l(o,f||n(s));return d&&d!==f?function(e,n,o,s=t.$getEditor()){const c=r,l=i(s);try{return r={...l,editor:s,[e]:n},o()}finally{r=c}}(e,d,c,s):c()}}(a,y);function D(e){return t=>t instanceof e}function N(e,{nodes:t}){if("*"===t)return p;let n={};const r=[];for(const s of t)if("getType"in s){const t=s.getType();if(n){const r=e[t];void 0===r&&o(339,s.name,t),n=Object.assign(n,r.types)}r.push(D(s))}else n=void 0,r.push(s);return n||(1===r.length?r[0]:e=>{for(const t of r)if(t(e))return!0;return!1})}function O(e){return(t,n,o)=>e(t,o)}function E(e){return(t,n,o,r)=>e(t,n,r)}function M(e){return(t,n,o,r,s)=>e(t,n,o,s)}function S(e){return(t,n,o,r,s,i)=>e(t,n,o,r,i)}function v(e,t){return(n,o)=>{const r=()=>e(n,o),s=t(n);return s?s(n,r,o):r()}}function C(e,t){return(n,o,r)=>{const s=()=>e(n,o,r),i=t(n);return i?i(n,o,s,r):s()}}function w(e,t){return(n,o,r,s)=>{const i=()=>e(n,o,r,s),c=t(n);return c?c(n,o,r,i,s):i()}}function R(e,t){return(n,o,r,s,i)=>{const c=()=>e(n,o,r,s,i),l=t(n);return l?l(n,o,r,s,c,i):c()}}function b(e,t){return(n,o,r,s)=>{e(n,o,r,s);const i=t(n);i&&i(n,o,r,s)}}function T(e,t,n,o,r){let s=n[t];for(const n of e[t])if("function"==typeof n[0]){const[e,t]=n;s=o(s,n=>e(n)&&t||void 0)}else{const e=n[1],t={};for(const n in e){const r=e[n];r&&(t[n]=r.reduce((e,t)=>o(e,()=>t),s))}s=o(s,e=>{const n=t[e.getType()];return n&&r(n)})}n[t]=s}function A(e,t,n,o){if(!o)return;const r=e[t];if("function"==typeof n)r.push([n,o]);else{const e=r[r.length-1];let t;e&&"types"===e[0]?t=e[1]:(t={},r.push(["types",t]));for(const e in n){const n=t[e]||[];t[e]=n,n.push(o)}}}function L(e){return"*"===e.nodes}function k(e,o){const r=function(e){const o={},{nodes:r}=n.getKnownTypesAndNodes(e);for(const e of r)o[e.getType()]={klass:e,types:{}};for(const e of Object.values(o))if(e){const n=e.klass.getType();for(let{klass:r}=e;t.$isLexicalNode(r.prototype);r=Object.getPrototypeOf(r)){const{ownNodeType:e}=t.getStaticNodeConfig(r),s=e&&o[e];s&&(s.types[n]=!0)}}return o}(e),s={$createDOM:[],$decorateDOM:[],$exportDOM:[],$extractWithChild:[],$getDOMSlot:[],$shouldExclude:[],$shouldInclude:[],$updateDOM:[]};for(const e of function(e){const n=[],o=[],r=[];for(const s of e)if(L(s))n.push(s);else if(Array.isArray(s.nodes))for(const e of s.nodes)t.$isLexicalNode(e.prototype)?r.push(1===s.nodes.length?s:{...s,nodes:[e]}):o.push(1===s.nodes.length?s:{...s,nodes:[e]});const s=new Map,i=e=>{let n=s.get(e);if(void 0===n){n=0;for(let o=e;t.$isLexicalNode(o.prototype);o=Object.getPrototypeOf(o))n++;s.set(e,n)}return n};return r.sort((e,t)=>i(e.nodes[0])-i(t.nodes[0])),[...r,...o,...n]}(o)){const t=N(r,e);for(const n in s){A(s,n,t,e[n])}}return s}function F(e){return e}const P=t.defineExtension({build:(e,t,n)=>({defaults:l(t.contextDefaults,void 0)}),config:{contextDefaults:[],overrides:[]},html:{export:new Map([[t.RootNode,()=>{const e=document.createElement("div");return e.role="textbox",{element:e}}]])},init(e,n){e.dom=function(e,{overrides:n}){const o=k(e,n),r={...t.DEFAULT_EDITOR_DOM_CONFIG,...e.dom};return T(o,"$createDOM",r,v,O),T(o,"$exportDOM",r,v,O),T(o,"$extractWithChild",r,R,S),T(o,"$getDOMSlot",r,C,E),T(o,"$shouldExclude",r,C,E),T(o,"$shouldInclude",r,C,E),T(o,"$updateDOM",r,w,M),T(o,"$decorateDOM",r,b,F),r}(e,n)},mergeConfig(e,n){const o=t.shallowMergeConfig(e,n);for(const t of["overrides","contextDefaults"])n[t]&&(o[t]=[...e[t],...n[t]]);return o},name:d});function _(e){return e.constructor.name===CSSStyleRule.name}const B=new Set(["STYLE","SCRIPT"]);function I(e,n=null,o=t.$getEditor()){return $([f(m,!0)],o)(()=>{const r=t.$getRoot(),s=t.$getEditorDOMRenderConfig(o),i=e.append.bind(e);for(const e of r.getChildren())j(o,e,i,n,s);return e})}function j(n,o,r,s=null,i=t.$getEditorDOMRenderConfig(n)){let c=i.$shouldInclude(o,s,n);const l=i.$shouldExclude(o,s,n);let u=o;null!==s&&t.$isTextNode(o)&&(u=e.$sliceSelectedTextNodeContent(s,o,"clone"));const f=i.$exportDOM(u,n),{element:d,after:a,append:p,$getChildNodes:g}=f;if(!d)return!1;const h=document.createDocumentFragment(),m=g?g():t.$isElementNode(u)?u.getChildren():[],y=h.append.bind(h);for(const e of m){const t=j(n,e,y,s,i);!c&&t&&i.$extractWithChild(o,e,s,"html",n)&&(c=!0)}if(c&&!l){if((t.isHTMLElement(d)||t.isDocumentFragment(d))&&(p?p(h):d.append(h)),r(d),a){const e=a.call(u,d);e&&(t.isDocumentFragment(d)?d.replaceChildren(e):d.replaceWith(e))}}else r(h);return c}function q(e,n,o,r,s=new Map,i){const c=[];if(B.has(e.nodeName))return c;let l=null;const u=function(e,t){const{nodeName:n}=e,o=t._htmlConversions.get(n.toLowerCase());let r=null;if(void 0!==o)for(const t of o){const n=t(e);null!==n&&(null===r||(r.priority||0)<=(n.priority||0))&&(r=n)}return null!==r?r.conversion:null}(e,n),f=u?u(e):null;let d=null;if(null!==f){d=f.after;const t=f.node;if(l=Array.isArray(t)?t[t.length-1]:t,null!==l){for(const[,e]of s)if(l=e(l,i),!l)break;l&&c.push(...Array.isArray(t)?t:[l])}null!=f.forChild&&s.set(e.nodeName,f.forChild)}const a=e.childNodes;let p=[];const g=(null==l||!t.$isRootOrShadowRoot(l))&&(null!=l&&t.$isBlockElementNode(l)||r);for(let e=0;e<a.length;e++)p.push(...q(a[e],n,o,g,new Map(s),l));if(null!=d&&(p=d(p)),t.isBlockDomNode(e)&&(p=U(e,p,g?()=>{const e=new t.ArtificialNode__DO_NOT_USE;return o.push(e),e}:t.$createParagraphNode)),null==l)if(p.length>0)for(const e of p)c.push(e);else t.isBlockDomNode(e)&&function(e){if(null==e.nextSibling||null==e.previousSibling)return!1;return t.isInlineDomNode(e.nextSibling)&&t.isInlineDomNode(e.previousSibling)}(e)&&c.push(t.$createLineBreakNode());else t.$isElementNode(l)&&l.append(...p);return c}function U(e,n,o){const r=e.style.textAlign,s=[];let i=[];for(let e=0;e<n.length;e++){const c=n[e];if(t.$isBlockElementNode(c))r&&!c.getFormat()&&c.setFormat(r),s.push(c);else if(i.push(c),e===n.length-1||e<n.length-1&&t.$isBlockElementNode(n[e+1])){const e=o();e.setFormat(r),e.append(...i),s.push(e),i=[]}}return s}exports.$generateDOMFromNodes=I,exports.$generateDOMFromRoot=function(e,n=t.$getRoot()){const o=t.$getEditor();return $([f(m,!0),f(h,!0)],o)(()=>{const r=t.$getEditorDOMRenderConfig(o),s=e.append.bind(e);return j(o,n,s,null,r),e})},exports.$generateHtmlFromNodes=function(e,t=null){return("undefined"==typeof document||"undefined"==typeof window&&void 0===global.window)&&o(338),I(document.createElement("div"),t,e).innerHTML},exports.$generateNodesFromDOM=function(e,n){t.isDOMDocumentNode(n)&&function(e){if(null===e.querySelector("style"))return;const n=new Map;function o(e){let t=n.get(e);if(void 0===t){t=new Set;for(let n=0;n<e.style.length;n++)t.add(e.style[n]);n.set(e,t)}return t}try{for(const n of Array.from(e.styleSheets)){let r;try{r=n.cssRules}catch(e){continue}for(const n of Array.from(r)){if(!_(n))continue;let r;try{r=e.querySelectorAll(n.selectorText)}catch(e){continue}for(const e of Array.from(r)){if(!t.isHTMLElement(e))continue;const r=o(e);for(let t=0;t<n.style.length;t++){const o=n.style[t];r.has(o)||e.style.setProperty(o,n.style.getPropertyValue(o),n.style.getPropertyPriority(o))}}}}}catch(e){}}(n);const o=t.isDOMDocumentNode(n)?n.body.childNodes:n.childNodes,r=[],s=[];for(const t of o)if(!B.has(t.nodeName)){const n=q(t,e,s,!1);if(null!==n)for(const e of n)r.push(e)}return function(e){for(const n of e)n.getParent()&&n.getNextSibling()instanceof t.ArtificialNode__DO_NOT_USE&&n.insertAfter(t.$createLineBreakNode());for(const t of e){const e=t.getParent();e&&e.splice(t.getIndexWithinParent(),1,t.getChildren())}}(s),r},exports.$getRenderContextValue=function(e,n=t.$getEditor()){return s(x(n),e)},exports.$withRenderContext=$,exports.DOMRenderExtension=P,exports.RenderContextExport=m,exports.RenderContextRoot=h,exports.contextUpdater=function(e,t){return{cfg:e,updater:t}},exports.contextValue=f,exports.createRenderState=g,exports.domOverride=function(e,t){return{...t,nodes:e}}; |
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import{$sliceSelectedTextNodeContent as t}from"@lexical/selection";import{$getEditor as n,createState as e,DEFAULT_EDITOR_DOM_CONFIG as o,$isLexicalNode as r,getStaticNodeConfig as c,defineExtension as s,shallowMergeConfig as u,RootNode as i,$getRoot as l,$getEditorDOMRenderConfig as f,isDOMDocumentNode as d,$isTextNode as a,$isElementNode as p,isHTMLElement as h,isDocumentFragment as y,$isRootOrShadowRoot as g,$isBlockElementNode as m,isBlockDomNode as x,$createLineBreakNode as v,ArtificialNode__DO_NOT_USE as $,isInlineDomNode as S,$createParagraphNode as b}from"lexical";import{getPeerDependencyFromEditor as O,getKnownTypesAndNodes as M}from"@lexical/extension";function w(t,...n){const e=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const t of n)o.append("v",t);throw e.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${e.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}let D;function C(t,n){const{key:e}=n;return t&&e in t?t[e]:n.defaultValue}function A(t){return D&&D.editor===t?D:void 0}function P(t,n){if("cfg"in n){const{cfg:e,updater:o}=n;return[e,o(C(t,e))]}return n}function E(t,n){let e=n;for(const o of t){const[t,r]=P(e,o),c=t.key;if(e===n&&C(e,t)===r)continue;const s=e||N(n);s[c]=r,e=s}return e}function N(t){return Object.create(t||null)}function T(t,n){return[t,n]}function j(t,n){return{cfg:t,updater:n}}const k="@lexical/html/DOM",L=Symbol.for("@lexical/html/DOMExportContext"),I=()=>!0;function R(t,n,o){return function(t,n,o,r){return Object.assign(e(Symbol(n),{isEqual:r,parse:o}),{[t]:!0})}(L,t,n,o)}const W=R("root",Boolean),F=R("isExport",Boolean);function q(t){const n=O(t,k);return n?n.output.defaults:void 0}function B(t){return function(t,n){const e=A(n);return e&&e[t]}(L,t)||q(t)}function U(t,e=n()){return C(B(e),t)}const V=function(t,e=()=>{}){return(o,r=n())=>c=>{const s=A(r),u=s&&s[t],i=E(o,u||e(r));return i&&i!==u?function(t,e,o,r=n()){const c=D,s=A(r);try{return D={...s,editor:r,[t]:e},o()}finally{D=c}}(t,i,c,r):c()}}(L,q);function H(t,n){return{...n,nodes:t}}function Y(t){return n=>n instanceof t}function _(t,{nodes:n}){if("*"===n)return I;let e={};const o=[];for(const r of n)if("getType"in r){const n=r.getType();if(e){const o=t[n];void 0===o&&w(339,r.name,n),e=Object.assign(e,o.types)}o.push(Y(r))}else e=void 0,o.push(r);return e||(1===o.length?o[0]:t=>{for(const n of o)if(n(t))return!0;return!1})}function z(t){return(n,e,o)=>t(n,o)}function G(t){return(n,e,o,r)=>t(n,e,r)}function J(t){return(n,e,o,r,c)=>t(n,e,o,c)}function K(t){return(n,e,o,r,c,s)=>t(n,e,o,r,s)}function Q(t,n){return(e,o)=>{const r=()=>t(e,o),c=n(e);return c?c(e,r,o):r()}}function X(t,n){return(e,o,r)=>{const c=()=>t(e,o,r),s=n(e);return s?s(e,o,c,r):c()}}function Z(t,n){return(e,o,r,c)=>{const s=()=>t(e,o,r,c),u=n(e);return u?u(e,o,r,s,c):s()}}function tt(t,n){return(e,o,r,c,s)=>{const u=()=>t(e,o,r,c,s),i=n(e);return i?i(e,o,r,c,u,s):u()}}function nt(t,n){return(e,o,r,c)=>{t(e,o,r,c);const s=n(e);s&&s(e,o,r,c)}}function et(t,n,e,o,r){let c=e[n];for(const e of t[n])if("function"==typeof e[0]){const[t,n]=e;c=o(c,e=>t(e)&&n||void 0)}else{const t=e[1],n={};for(const e in t){const r=t[e];r&&(n[e]=r.reduce((t,n)=>o(t,()=>n),c))}c=o(c,t=>{const e=n[t.getType()];return e&&r(e)})}e[n]=c}function ot(t,n,e,o){if(!o)return;const r=t[n];if("function"==typeof e)r.push([e,o]);else{const t=r[r.length-1];let n;t&&"types"===t[0]?n=t[1]:(n={},r.push(["types",n]));for(const t in e){const e=n[t]||[];n[t]=e,e.push(o)}}}function rt(t){return"*"===t.nodes}function ct(t,n){const e=function(t){const n={},{nodes:e}=M(t);for(const t of e)n[t.getType()]={klass:t,types:{}};for(const t of Object.values(n))if(t){const e=t.klass.getType();for(let{klass:o}=t;r(o.prototype);o=Object.getPrototypeOf(o)){const{ownNodeType:t}=c(o),r=t&&n[t];r&&(r.types[e]=!0)}}return n}(t),o={$createDOM:[],$decorateDOM:[],$exportDOM:[],$extractWithChild:[],$getDOMSlot:[],$shouldExclude:[],$shouldInclude:[],$updateDOM:[]};for(const t of function(t){const n=[],e=[],o=[];for(const c of t)if(rt(c))n.push(c);else if(Array.isArray(c.nodes))for(const t of c.nodes)r(t.prototype)?o.push(1===c.nodes.length?c:{...c,nodes:[t]}):e.push(1===c.nodes.length?c:{...c,nodes:[t]});const c=new Map,s=t=>{let n=c.get(t);if(void 0===n){n=0;for(let e=t;r(e.prototype);e=Object.getPrototypeOf(e))n++;c.set(t,n)}return n};return o.sort((t,n)=>s(t.nodes[0])-s(n.nodes[0])),[...o,...e,...n]}(n)){const n=_(e,t);for(const e in o){ot(o,e,n,t[e])}}return o}function st(t){return t}const ut=s({build:(t,n,e)=>({defaults:E(n.contextDefaults,void 0)}),config:{contextDefaults:[],overrides:[]},html:{export:new Map([[i,()=>{const t=document.createElement("div");return t.role="textbox",{element:t}}]])},init(t,n){t.dom=function(t,{overrides:n}){const e=ct(t,n),r={...o,...t.dom};return et(e,"$createDOM",r,Q,z),et(e,"$exportDOM",r,Q,z),et(e,"$extractWithChild",r,tt,K),et(e,"$getDOMSlot",r,X,G),et(e,"$shouldExclude",r,X,G),et(e,"$shouldInclude",r,X,G),et(e,"$updateDOM",r,Z,J),et(e,"$decorateDOM",r,nt,st),r}(t,n)},mergeConfig(t,n){const e=u(t,n);for(const o of["overrides","contextDefaults"])n[o]&&(e[o]=[...t[o],...n[o]]);return e},name:k});function it(t){return t.constructor.name===CSSStyleRule.name}const lt=new Set(["STYLE","SCRIPT"]);function ft(t,n){d(n)&&function(t){if(null===t.querySelector("style"))return;const n=new Map;function e(t){let e=n.get(t);if(void 0===e){e=new Set;for(let n=0;n<t.style.length;n++)e.add(t.style[n]);n.set(t,e)}return e}try{for(const n of Array.from(t.styleSheets)){let o;try{o=n.cssRules}catch(t){continue}for(const n of Array.from(o)){if(!it(n))continue;let o;try{o=t.querySelectorAll(n.selectorText)}catch(t){continue}for(const t of Array.from(o)){if(!h(t))continue;const o=e(t);for(let e=0;e<n.style.length;e++){const r=n.style[e];o.has(r)||t.style.setProperty(r,n.style.getPropertyValue(r),n.style.getPropertyPriority(r))}}}}}catch(t){}}(n);const e=d(n)?n.body.childNodes:n.childNodes,o=[],r=[];for(const n of e)if(!lt.has(n.nodeName)){const e=yt(n,t,r,!1);if(null!==e)for(const t of e)o.push(t)}return function(t){for(const n of t)n.getParent()&&n.getNextSibling()instanceof $&&n.insertAfter(v());for(const n of t){const t=n.getParent();t&&t.splice(n.getIndexWithinParent(),1,n.getChildren())}}(r),o}function dt(t,e=null,o=n()){return V([T(F,!0)],o)(()=>{const n=l(),r=f(o),c=t.append.bind(t);for(const t of n.getChildren())ht(o,t,c,e,r);return t})}function at(t,e=l()){const o=n();return V([T(F,!0),T(W,!0)],o)(()=>{const n=f(o),r=t.append.bind(t);return ht(o,e,r,null,n),t})}function pt(t,n=null){return("undefined"==typeof document||"undefined"==typeof window&&void 0===global.window)&&w(338),dt(document.createElement("div"),n,t).innerHTML}function ht(n,e,o,r=null,c=f(n)){let s=c.$shouldInclude(e,r,n);const u=c.$shouldExclude(e,r,n);let i=e;null!==r&&a(e)&&(i=t(r,e,"clone"));const l=c.$exportDOM(i,n),{element:d,after:g,append:m,$getChildNodes:x}=l;if(!d)return!1;const v=document.createDocumentFragment(),$=x?x():p(i)?i.getChildren():[],S=v.append.bind(v);for(const t of $){const o=ht(n,t,S,r,c);!s&&o&&c.$extractWithChild(e,t,r,"html",n)&&(s=!0)}if(s&&!u){if((h(d)||y(d))&&(m?m(v):d.append(v)),o(d),g){const t=g.call(i,d);t&&(y(d)?d.replaceChildren(t):d.replaceWith(t))}}else o(v);return s}function yt(t,n,e,o,r=new Map,c){const s=[];if(lt.has(t.nodeName))return s;let u=null;const i=function(t,n){const{nodeName:e}=t,o=n._htmlConversions.get(e.toLowerCase());let r=null;if(void 0!==o)for(const n of o){const e=n(t);null!==e&&(null===r||(r.priority||0)<=(e.priority||0))&&(r=e)}return null!==r?r.conversion:null}(t,n),l=i?i(t):null;let f=null;if(null!==l){f=l.after;const n=l.node;if(u=Array.isArray(n)?n[n.length-1]:n,null!==u){for(const[,t]of r)if(u=t(u,c),!u)break;u&&s.push(...Array.isArray(n)?n:[u])}null!=l.forChild&&r.set(t.nodeName,l.forChild)}const d=t.childNodes;let a=[];const h=(null==u||!g(u))&&(null!=u&&m(u)||o);for(let t=0;t<d.length;t++)a.push(...yt(d[t],n,e,h,new Map(r),u));if(null!=f&&(a=f(a)),x(t)&&(a=gt(t,a,h?()=>{const t=new $;return e.push(t),t}:b)),null==u)if(a.length>0)for(const t of a)s.push(t);else x(t)&&function(t){if(null==t.nextSibling||null==t.previousSibling)return!1;return S(t.nextSibling)&&S(t.previousSibling)}(t)&&s.push(v());else p(u)&&u.append(...a);return s}function gt(t,n,e){const o=t.style.textAlign,r=[];let c=[];for(let t=0;t<n.length;t++){const s=n[t];if(m(s))o&&!s.getFormat()&&s.setFormat(o),r.push(s);else if(c.push(s),t===n.length-1||t<n.length-1&&m(n[t+1])){const t=e();t.setFormat(o),t.append(...c),r.push(t),c=[]}}return r}export{dt as $generateDOMFromNodes,at as $generateDOMFromRoot,pt as $generateHtmlFromNodes,ft as $generateNodesFromDOM,U as $getRenderContextValue,V as $withRenderContext,ut as DOMRenderExtension,F as RenderContextExport,W as RenderContextRoot,j as contextUpdater,T as contextValue,R as createRenderState,H as domOverride}; |
| import { LexicalEditor } from 'lexical'; | ||
| import { AnyRenderStateConfigPairOrUpdater, RenderStateConfig } from './types'; | ||
| /** | ||
| * Create a context state to be used during render. | ||
| * | ||
| * Note that to support the ValueOrUpdater pattern you can not use a | ||
| * function for V (but you may wrap it in an array or object). | ||
| * | ||
| * @experimental | ||
| * @__NO_SIDE_EFFECTS__ | ||
| */ | ||
| export declare function createRenderState<V>(name: string, getDefaultValue: () => V, isEqual?: (a: V, b: V) => boolean): RenderStateConfig<V>; | ||
| /** | ||
| * Render context state that is true if the export was initiated from the root of the document. | ||
| * @experimental | ||
| */ | ||
| export declare const RenderContextRoot: RenderStateConfig<boolean>; | ||
| /** | ||
| * Render context state that is true if this is an export operation ($generateHtmlFromNodes). | ||
| * @experimental | ||
| */ | ||
| export declare const RenderContextExport: RenderStateConfig<boolean>; | ||
| /** | ||
| * Get a render context value during a DOM render or export operation. | ||
| * @experimental | ||
| */ | ||
| export declare function $getRenderContextValue<V>(cfg: RenderStateConfig<V>, editor?: LexicalEditor): V; | ||
| /** | ||
| * Execute a callback within a render context with the given config pairs. | ||
| * @experimental | ||
| */ | ||
| export declare const $withRenderContext: (cfg: readonly AnyRenderStateConfigPairOrUpdater[], editor?: LexicalEditor) => <T>(f: () => T) => T; |
-293
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| * | ||
| */ | ||
| import type { DOMRenderContextSymbol } from './constants'; | ||
| import type { BaseSelection, DOMExportOutput, ElementDOMSlot, Klass, LexicalEditor, LexicalNode, StateConfig } from 'lexical'; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any ContextSymbol for {@link ContextConfig} (currently only {@link DOMRenderContextSymbol}) | ||
| */ | ||
| export type AnyContextSymbol = typeof DOMRenderContextSymbol; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Context with a phantom type for its purpose (such as {@link DOMRenderContextSymbol}). | ||
| * | ||
| * A ContextRecord is a data structure used in the export and import pipelines | ||
| * to allow for information to be passed throughout the chain without explicit | ||
| * argument passing, e.g. to specify whether the intended use case for HTML | ||
| * export is for serialization or for clipboard copy. | ||
| */ | ||
| export type ContextRecord<_K extends symbol> = Record<string | symbol, unknown>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * A data structure much like StateConfig (they share implementation details) | ||
| * but for managing context during an export or import pipeline rather than | ||
| * individual node state. | ||
| */ | ||
| export type ContextConfig<Sym extends symbol, V> = StateConfig<symbol, V> & { | ||
| readonly [K in Sym]?: true; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Update the context at `cfg` with updater, constructed with {@link contextUpdater} | ||
| */ | ||
| export type ContextConfigUpdater<Ctx extends AnyContextSymbol, V> = { | ||
| readonly cfg: ContextConfig<Ctx, V>; | ||
| /** | ||
| * @param prev The current or default value | ||
| * @returns The new value | ||
| */ | ||
| readonly updater: (prev: V) => V; | ||
| }; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Set the the context at `cfg` to a specific value, constructed with {@link contextValue} | ||
| */ | ||
| export type ContextConfigPair<Ctx extends AnyContextSymbol, V> = readonly [ | ||
| ContextConfig<Ctx, V>, | ||
| V | ||
| ]; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Set or update a context value, constructed with {@link contextValue} or {@link contextUpdater} | ||
| */ | ||
| export type ContextPairOrUpdater<Ctx extends AnyContextSymbol, V> = ContextConfigPair<Ctx, V> | ContextConfigUpdater<Ctx, V>; | ||
| /** @experimental */ | ||
| export type AnyContextConfigPairOrUpdater<Ctx extends AnyContextSymbol> = ContextPairOrUpdater<Ctx, any>; | ||
| /** @experimental */ | ||
| export interface DOMRenderExtensionOutput { | ||
| /** @internal */ | ||
| defaults: undefined | ContextRecord<typeof DOMRenderContextSymbol>; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Context configuration for render context, created with {@link createRenderState} | ||
| */ | ||
| export type RenderStateConfig<V> = ContextConfig<typeof DOMRenderContextSymbol, V>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any setter or updater for {@link RenderStateConfig} | ||
| */ | ||
| export type AnyRenderStateConfigPairOrUpdater = AnyContextConfigPairOrUpdater<typeof DOMRenderContextSymbol>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Any {@link RenderStateConfig} | ||
| */ | ||
| export type AnyRenderStateConfig = RenderStateConfig<any>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Configuration for {@link DOMRenderExtension} | ||
| */ | ||
| export interface DOMRenderConfig { | ||
| /** | ||
| * {@link DOMRenderMatch} overrides to customize node behavior, | ||
| * the final priority of these will be based on the following criteria: | ||
| * | ||
| * - Wildcards (`'*'`) have highest priority | ||
| * - Predicates (`$isParagraphNode`) have next priority | ||
| * - Subclasses have higher priority (e.g. `ParagraphNode` before `ElementNode`) | ||
| * - Extensions closer to the root have higher priority | ||
| * - Extensions depended on later have higher priority | ||
| * - Overrides defined later have higher priority | ||
| */ | ||
| overrides: AnyDOMRenderMatch[]; | ||
| /** | ||
| * Default context to provide in all exports, the configurations are created | ||
| * with {@link createRenderState} and should be created at the module-level. | ||
| * | ||
| * Only specify these if overriding the default value globally, since each | ||
| * configuration has a built-in default value that will be used if not | ||
| * already present in the context. | ||
| */ | ||
| contextDefaults: AnyRenderStateConfigPairOrUpdater[]; | ||
| } | ||
| /** | ||
| * @experimental | ||
| * Any {@link DOMRenderMatch} | ||
| */ | ||
| export type AnyDOMRenderMatch = DOMRenderMatch<any>; | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Match a node (and any subclass of that node) by its LexicalNode class, | ||
| * or with a guard (e.g. `ElementNode` or `$isElementNode`). | ||
| * | ||
| * Note that using the class compiles to significantly more efficient code | ||
| * than using a guard. | ||
| */ | ||
| export type NodeMatch<T extends LexicalNode> = Klass<T> | ((node: LexicalNode) => node is T); | ||
| /** | ||
| * @experimental | ||
| * | ||
| * Used to define overrides for the render and export | ||
| * behavior for nodes matching the `nodes` predicate. | ||
| * | ||
| * All of these overrides are in a middleware style where you may use the | ||
| * result of `$next()` to enhance the result of the default implementation | ||
| * (or a lower priority override) by calling it and manipulating the result, | ||
| * or you may choose not to call `$next()` to entirely replace the behavior. | ||
| * | ||
| * It is not permitted to update the lexical editor state during any of | ||
| * these calls, you should only be doing read-only operations. | ||
| */ | ||
| export interface DOMRenderMatch<T extends LexicalNode> { | ||
| /** | ||
| * '*' for all nodes, or an array of `NodeClass | $isNodeGuard` to match | ||
| * nodes more specifically. Using classes is more efficient, but will | ||
| * also target subclasses. | ||
| */ | ||
| readonly nodes: '*' | readonly NodeMatch<T>[]; | ||
| /** | ||
| * Control where an ElementNode's children are inserted into the DOM, | ||
| * this is useful to add a wrapping node or accessory nodes before or | ||
| * after the children. The root of the node returned by createDOM must | ||
| * still be exactly one HTMLElement. | ||
| * | ||
| * Generally you will call `$next()` to get an ElementDOMSlot and then use | ||
| * its methods to create a new one. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param dom The rendered HTMLElement | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns The `ElementDOMSlot` for this node | ||
| */ | ||
| $getDOMSlot?: <N extends LexicalNode>(node: N, dom: HTMLElement, $next: () => ElementDOMSlot<HTMLElement>, editor: LexicalEditor) => ElementDOMSlot<HTMLElement>; | ||
| /** | ||
| * Called during the reconciliation process to determine which nodes | ||
| * to insert into the DOM for this Lexical Node. This is also the default | ||
| * implementation of `$exportDOM` for most nodes. | ||
| * | ||
| * This method must return exactly one `HTMLElement`. | ||
| * | ||
| * Nested elements are not supported except with `DecoratorNode` | ||
| * (which have unmanaged contents) or `ElementNode` using an appropriate | ||
| * `$getDOMSlot` return value. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns The HTMLElement for this node to be rendered in the editor | ||
| */ | ||
| $createDOM?: (node: T, $next: () => HTMLElement, editor: LexicalEditor) => HTMLElement; | ||
| /** | ||
| * Called when a node changes and should update the DOM | ||
| * in whatever way is necessary to make it align with any changes that might | ||
| * have happened during the update. | ||
| * | ||
| * Returning `true` here will cause lexical to unmount and recreate the DOM | ||
| * node (by calling `$createDOM`). You would need to do this if the element | ||
| * tag changes, for instance. | ||
| * | ||
| * @param nextNode The current version of this node | ||
| * @param prevNode The previous version of this node | ||
| * @param dom The previously rendered HTMLElement for this node | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns `false` if no update needed or was performed in-place, `true` if `$createDOM` should be called to re-create the node | ||
| */ | ||
| $updateDOM?: (nextNode: T, prevNode: T, dom: HTMLElement, $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Called after a node is created or updated and should make any in-place | ||
| * updates to the DOM in whatever way is necessary to make it align with | ||
| * any changes that might have happened during the `$createDOM` or | ||
| * `$updateDOM`. This also runs after any children have been reconciled. | ||
| * | ||
| * Use this when you have code that you would need to duplicate in both | ||
| * methods, or if there is a need to ensure that the children are also | ||
| * reconciled before performing this in-place update. | ||
| * | ||
| * Unlike other overrides, all applicable `$decorateDOM` functions are | ||
| * called unconditionally. There is no `$next` argument, because there | ||
| * are no known use cases for avoiding the next implementation and due | ||
| * to the void return value it would be error-prone and add boilerplate | ||
| * to require calling it. | ||
| * | ||
| * The ordering here is equivalent to an implicit `$next` call *first*. | ||
| * | ||
| * @param nextNode The current version of this node | ||
| * @param prevNode The previous version of this node if `$updateDOM` returned `false`, or `null` if `$createDOM` was just called | ||
| * @param dom The previously rendered `HTMLElement` for this node | ||
| * @param editor The editor | ||
| */ | ||
| $decorateDOM?: (nextNode: T, prevNode: null | T, dom: HTMLElement, editor: LexicalEditor) => void; | ||
| /** | ||
| * Controls how the this node is serialized to HTML. This is important for | ||
| * copy and paste between Lexical and non-Lexical editors, or Lexical | ||
| * editors with different namespaces, in which case the primary transfer | ||
| * format is HTML. It's also important if you're serializing to HTML for | ||
| * any other reason via {@link @lexical/html!$generateHtmlFromNodes}. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param $next Call the next implementation | ||
| * @param editor The editor | ||
| * @returns A {@link DOMExportOutput} structure that defines how the node should be exported to HTML | ||
| */ | ||
| $exportDOM?: (node: T, $next: () => DOMExportOutput, editor: LexicalEditor) => DOMExportOutput; | ||
| /** | ||
| * Equivalent to `ElementNode.excludeFromCopy`, if it returns `true` this | ||
| * lexical node will not be exported to DOM (but if it's an `ElementNode` | ||
| * its children may still be inserted in its place). | ||
| * | ||
| * Has higher precedence than `$shouldInclude` and `$extractWithChild`. | ||
| * | ||
| * @param node The LexicalNode | ||
| * @param selection The current selection | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns true to exclude this node, false otherwise | ||
| */ | ||
| $shouldExclude?: (node: T, selection: null | BaseSelection, $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Return `true` if this node should be included in the export, typically based | ||
| * on the current selection (all nodes by default are included when there | ||
| * is no selection). | ||
| * | ||
| * The default implementation is equivalent to | ||
| * `selection ? node.isSelected(selection) : true`. | ||
| * | ||
| * This has lower precedence than `$extractWithChild` and `$shouldExclude`. | ||
| * | ||
| * @param node The current node | ||
| * @param selection The current selection | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns `true` if this node should be included in the export, `false` otherwise | ||
| */ | ||
| $shouldInclude?: (node: T, selection: null | BaseSelection, $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| /** | ||
| * Return `true` if this node should be included in the export based on | ||
| * `childNode`, even if it would not otherwise be included based on its | ||
| * `$shouldInclude` result. | ||
| * | ||
| * Typically used to ensure that required wrapping nodes are always | ||
| * present with its children, e.g. a ListNode when some of its ListItemNode | ||
| * children are selected. | ||
| * | ||
| * This has higher precedence than `$extractWithChild` and lower precedence | ||
| * than `$shouldExclude`. | ||
| * | ||
| * @param node The lexical node | ||
| * @param childNode A child of this lexical node | ||
| * @param selection The current selection | ||
| * @param destination Currently always `'html'` | ||
| * @param $next The next implementation | ||
| * @param editor The editor | ||
| * @returns true if this | ||
| */ | ||
| $extractWithChild?: (node: T, childNode: LexicalNode, selection: null | BaseSelection, destination: 'clone' | 'html', $next: () => boolean, editor: LexicalEditor) => boolean; | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
517360
373.26%59
210.53%13336
458.93%5
25%6
50%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated