Description
Currently there is now an importDOM API for an LexicalNode that allows you to define the import of a specific tag as a key-value relationship, where the key is the name of the tag, and the value is a function that returns the import function and its priority
There's no way to specify import handling for any node. The import definition in the current implementation of importDOM must always be in the node class, and DOMConversionMap always expects a specific tag name as a key, making it particularly difficult to intercept non-HTML tags (for example, if a user implements a WYSIWYG editor compatible with custom markup)
Workaround for unknown elements
Theoretically it is possible to process unknown elements, given that the editor already has conversion functions registered for known tags.
To do this, you need to use a local copy of the getConversionFunction function and use it in DOM preprocessing before sending it to $generateNodesFromDOM. The purpose of preprocessing is to ensure that each element has a corresponding conversion function, and if not, to wrap the node in a special tag that will later be processed during the import of special node like FallbackNode
This approach is quite fragile, especially if a DOM element is mutated during import. For example, if you need to add style display: inline for custom inline node #8391
Workaround for any existing elements
If you need to intercept the import of any existing element, for example to process global attributes as id and add NodeState to the node. You can loop through each class from RegisteredNodes and reassign a new static importDOM method based on the original one
Code example
const registeredNodes =
typeof editorConfig.nodes === 'function'
? editorConfig.nodes()
: editorConfig.nodes;
if (!registeredNodes) {
return;
}
for (const klass of registeredNodes) {
// skip replacement config
if ('replace' in klass) return;
const importMap: DOMConversionMap = {};
// Wrap all node importers with a function that sets id
// This doesn't work for classes where `importDOM` is declared via `config()`
for (const [tag, fn] of Object.entries(klass.importDOM?.() || {})) {
importMap[tag] = importNode => {
const importer = fn(importNode);
if (!importer) {
return null;
}
return {
...importer,
conversion: element => {
const output = importer.conversion(element);
if (element.id && output?.node && !Array.isArray(output.node)) {
$setState(output.node, idState, element.id);
}
return output;
},
};
};
}
klass.importDOM = () => importMap;
}
But the problem with this approach is that it doesn't work if importDOM is declared as a config property https://lexical.dev/docs/api/modules/lexical#importdom-6
Import context problem
Another issue related to imports is the context in which the import occurs. For example, it's important to consider that imports may occur while pasting HTML from the clipboard. In this case, it's best to skip elements for which the conversion function returns null, especially if the importDOM contains strict schema validation and needs to be relaxed when pasting HTML from the clipboard
Example of import with schema validation
import { z, ZodError } from 'zod';
const $UnorderedListSchema = z.preprocess(
(attrs: NamedNodeMap) =>
Object.fromEntries(Array.from(attrs).map((a) => [a.name, a.value])),
z.strictObject({
marker: z.enum(['disc', 'circle', 'square', 'none']).default('disc'),
})
);
// class ExtendedListNode extends ListNode
static importDOM(): DOMConversionMap {
return {
// Validate here if you want other conversion functions to work after returning null
ul: (domNode) => {
return {
conversion: (element) => {
try {
const { marker } = $UnorderedListSchema.parse(element.attributes);
return {
node: $createExtendedList('bullet', marker),
};
} catch (e) {
if (e instanceof ZodError) {
console.warn('ExtendedListNode\n', z.prettifyError(e));
// no fallback conversions
return null;
}
throw e;
}
},
};
},
};
}
Relates
Impact
This may be useful if the user is implementing a WYSIWYG editor in which it is important to preserve the original markup or show a fallback element. And also for setting global states in nodes
Description
Currently there is now an
importDOMAPI for an LexicalNode that allows you to define the import of a specific tag as a key-value relationship, where the key is the name of the tag, and the value is a function that returns the import function and its priorityThere's no way to specify import handling for any node. The import definition in the current implementation of
importDOMmust always be in the node class, andDOMConversionMapalways expects a specific tag name as a key, making it particularly difficult to intercept non-HTML tags (for example, if a user implements a WYSIWYG editor compatible with custom markup)Workaround for unknown elements
Theoretically it is possible to process unknown elements, given that the editor already has conversion functions registered for known tags.
To do this, you need to use a local copy of the
getConversionFunctionfunction and use it in DOM preprocessing before sending it to$generateNodesFromDOM. The purpose of preprocessing is to ensure that each element has a corresponding conversion function, and if not, to wrap the node in a special tag that will later be processed during the import of special node like FallbackNodeThis approach is quite fragile, especially if a DOM element is mutated during import. For example, if you need to add style
display: inlinefor custom inline node #8391Workaround for any existing elements
If you need to intercept the import of any existing element, for example to process global attributes as
idand add NodeState to the node. You can loop through each class fromRegisteredNodesand reassign a new static importDOM method based on the original oneCode example
But the problem with this approach is that it doesn't work if importDOM is declared as a config property https://lexical.dev/docs/api/modules/lexical#importdom-6
Import context problem
Another issue related to imports is the context in which the import occurs. For example, it's important to consider that imports may occur while pasting HTML from the clipboard. In this case, it's best to skip elements for which the conversion function returns
null, especially if theimportDOMcontains strict schema validation and needs to be relaxed when pasting HTML from the clipboardExample of import with schema validation
Relates
Impact
This may be useful if the user is implementing a WYSIWYG editor in which it is important to preserve the original markup or show a fallback element. And also for setting global states in nodes