18 SerializedTableCellNode,
19 TableCellHeaderStates,
21 } from "@lexical/table";
22 import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
23 import {createStyleMapFromDomStyles, StyleMap} from "../utils/styles";
25 export type SerializedCustomTableCellNode = Spread<{
26 styles: Record<string, string>,
27 }, SerializedTableCellNode>
29 export class CustomTableCellNode extends TableCellNode {
30 __styles: StyleMap = new Map;
32 static getType(): string {
33 return 'custom-table-cell';
36 static clone(node: CustomTableCellNode): CustomTableCellNode {
37 const cellNode = new CustomTableCellNode(
43 cellNode.__rowSpan = node.__rowSpan;
44 cellNode.__styles = new Map(node.__styles);
48 getStyles(): StyleMap {
49 const self = this.getLatest();
50 return new Map(self.__styles);
53 setStyles(styles: StyleMap): void {
54 const self = this.getWritable();
55 self.__styles = new Map(styles);
58 updateTag(tag: string): void {
59 const isHeader = tag.toLowerCase() === 'th';
60 const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
61 const self = this.getWritable();
62 self.__headerState = state;
65 createDOM(config: EditorConfig): HTMLElement {
66 const element = super.createDOM(config);
68 for (const [name, value] of this.__styles.entries()) {
69 element.style.setProperty(name, value);
75 updateDOM(prevNode: CustomTableCellNode): boolean {
76 return super.updateDOM(prevNode)
77 || this.__styles !== prevNode.__styles;
80 static importDOM(): DOMConversionMap | null {
82 td: (node: Node) => ({
83 conversion: $convertCustomTableCellNodeElement,
86 th: (node: Node) => ({
87 conversion: $convertCustomTableCellNodeElement,
93 exportDOM(editor: LexicalEditor): DOMExportOutput {
94 const element = this.createDOM(editor._config);
100 static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode {
101 const node = $createCustomTableCellNode(
102 serializedNode.headerState,
103 serializedNode.colSpan,
104 serializedNode.width,
107 node.setStyles(new Map(Object.entries(serializedNode.styles)));
112 exportJSON(): SerializedCustomTableCellNode {
114 ...super.exportJSON(),
115 type: 'custom-table-cell',
116 styles: Object.fromEntries(this.__styles),
121 function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput {
122 const output = $convertTableCellNodeElement(domNode);
124 if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
125 output.node.setStyles(createStyleMapFromDomStyles(domNode.style));
132 * Function taken from:
133 * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289
134 * Copyright (c) Meta Platforms, Inc. and affiliates.
136 * Modified since copy.
138 export function $convertTableCellNodeElement(
140 ): DOMConversionOutput {
141 const domNode_ = domNode as HTMLTableCellElement;
142 const nodeName = domNode.nodeName.toLowerCase();
144 let width: number | undefined = undefined;
147 const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
148 if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
149 width = parseFloat(domNode_.style.width);
152 const tableCellNode = $createTableCellNode(
154 ? TableCellHeaderStates.ROW
155 : TableCellHeaderStates.NO_STATUS,
160 tableCellNode.__rowSpan = domNode_.rowSpan;
162 const style = domNode_.style;
163 const textDecoration = style.textDecoration.split(' ');
164 const hasBoldFontWeight =
165 style.fontWeight === '700' || style.fontWeight === 'bold';
166 const hasLinethroughTextDecoration = textDecoration.includes('line-through');
167 const hasItalicFontStyle = style.fontStyle === 'italic';
168 const hasUnderlineTextDecoration = textDecoration.includes('underline');
170 after: (childLexicalNodes) => {
171 if (childLexicalNodes.length === 0) {
172 childLexicalNodes.push($createParagraphNode());
174 return childLexicalNodes;
176 forChild: (lexicalNode, parentLexicalNode) => {
177 if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
178 const paragraphNode = $createParagraphNode();
180 $isLineBreakNode(lexicalNode) &&
181 lexicalNode.getTextContent() === '\n'
185 if ($isTextNode(lexicalNode)) {
186 if (hasBoldFontWeight) {
187 lexicalNode.toggleFormat('bold');
189 if (hasLinethroughTextDecoration) {
190 lexicalNode.toggleFormat('strikethrough');
192 if (hasItalicFontStyle) {
193 lexicalNode.toggleFormat('italic');
195 if (hasUnderlineTextDecoration) {
196 lexicalNode.toggleFormat('underline');
199 paragraphNode.append(lexicalNode);
200 return paragraphNode;
210 export function $createCustomTableCellNode(
211 headerState: TableCellHeaderState,
214 ): CustomTableCellNode {
215 return new CustomTableCellNode(headerState, colSpan, width);
218 export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode {
219 return node instanceof CustomTableCellNode;