Lexical: Added support for table caption nodes

Needs linking up to the table form still.
This commit is contained in:
Dan Brown 2025-01-22 12:54:13 +00:00
parent 04cca77ae6
commit 8a66365d48
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
5 changed files with 101 additions and 8 deletions

View File

@ -1165,6 +1165,16 @@ export class LexicalNode {
markDirty(): void {
this.getWritable();
}
/**
* Insert the DOM of this node into that of the parent.
* Allows this node to implement custom DOM attachment logic.
* Boolean result indicates if the insertion was handled by the function.
* A true return value prevents default insertion logic from taking place.
*/
insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
return false;
}
}
function errorOnTypeKlassMismatch(

View File

@ -171,16 +171,21 @@ function $createNode(
}
if (parentDOM !== null) {
if (insertDOM != null) {
parentDOM.insertBefore(dom, insertDOM);
} else {
// @ts-expect-error: internal field
const possibleLineBreak = parentDOM.__lexicalLineBreak;
if (possibleLineBreak != null) {
parentDOM.insertBefore(dom, possibleLineBreak);
const inserted = node?.insertDOMIntoParent(dom, parentDOM);
if (!inserted) {
if (insertDOM != null) {
parentDOM.insertBefore(dom, insertDOM);
} else {
parentDOM.appendChild(dom);
// @ts-expect-error: internal field
const possibleLineBreak = parentDOM.__lexicalLineBreak;
if (possibleLineBreak != null) {
parentDOM.insertBefore(dom, possibleLineBreak);
} else {
parentDOM.appendChild(dom);
}
}
}
}

View File

@ -0,0 +1,74 @@
import {
DOMConversionMap,
DOMExportOutput,
EditorConfig,
ElementNode,
LexicalEditor,
LexicalNode,
SerializedElementNode
} from "lexical";
export class CaptionNode extends ElementNode {
static getType(): string {
return 'caption';
}
static clone(node: CaptionNode): CaptionNode {
return new CaptionNode(node.__key);
}
createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
return document.createElement('caption');
}
updateDOM(_prevNode: unknown, _dom: HTMLElement, _config: EditorConfig): boolean {
return false;
}
isParentRequired(): true {
return true;
}
canBeEmpty(): boolean {
return false;
}
exportJSON(): SerializedElementNode {
return {
...super.exportJSON(),
type: 'caption',
version: 1,
};
}
insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean {
parentDOM.insertBefore(nodeDOM, parentDOM.firstChild);
return true;
}
static importJSON(serializedNode: SerializedElementNode): CaptionNode {
return $createCaptionNode();
}
static importDOM(): DOMConversionMap | null {
return {
caption: (node: Node) => ({
conversion(domNode: Node) {
return {
node: $createCaptionNode(),
}
},
priority: 0,
}),
};
}
}
export function $createCaptionNode(): CaptionNode {
return new CaptionNode();
}
export function $isCaptionNode(node: LexicalNode | null | undefined): node is CaptionNode {
return node instanceof CaptionNode;
}

View File

@ -139,6 +139,8 @@ export class TableNode extends CommonBlockNode {
for (const child of Array.from(tableElement.children)) {
if (child.nodeName === 'TR') {
tBody.append(child);
} else if (child.nodeName === 'CAPTION') {
newElement.insertBefore(child, newElement.firstChild);
} else {
newElement.append(child);
}

View File

@ -18,6 +18,7 @@ import {EditorUiContext} from "./ui/framework/core";
import {MediaNode} from "@lexical/rich-text/LexicalMediaNode";
import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode";
import {CaptionNode} from "@lexical/table/LexicalCaptionNode";
/**
* Load the nodes for lexical.
@ -32,6 +33,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
TableNode,
TableRowNode,
TableCellNode,
CaptionNode,
ImageNode, // TODO - Alignment
HorizontalRuleNode,
DetailsNode,