18 SerializedTableCellNode,
19 TableCellHeaderStates,
21 } from "@lexical/table";
22 import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
23 import {extractStyleMapFromElement, StyleMap} from "../utils/dom";
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);
49 const self = this.getWritable();
50 self.__width = undefined;
53 getStyles(): StyleMap {
54 const self = this.getLatest();
55 return new Map(self.__styles);
58 setStyles(styles: StyleMap): void {
59 const self = this.getWritable();
60 self.__styles = new Map(styles);
63 updateTag(tag: string): void {
64 const isHeader = tag.toLowerCase() === 'th';
65 const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
66 const self = this.getWritable();
67 self.__headerState = state;
70 createDOM(config: EditorConfig): HTMLElement {
71 const element = super.createDOM(config);
73 for (const [name, value] of this.__styles.entries()) {
74 element.style.setProperty(name, value);
80 updateDOM(prevNode: CustomTableCellNode): boolean {
81 return super.updateDOM(prevNode)
82 || this.__styles !== prevNode.__styles;
85 static importDOM(): DOMConversionMap | null {
87 td: (node: Node) => ({
88 conversion: $convertCustomTableCellNodeElement,
91 th: (node: Node) => ({
92 conversion: $convertCustomTableCellNodeElement,
98 exportDOM(editor: LexicalEditor): DOMExportOutput {
99 const element = this.createDOM(editor._config);
105 static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode {
106 const node = $createCustomTableCellNode(
107 serializedNode.headerState,
108 serializedNode.colSpan,
109 serializedNode.width,
112 node.setStyles(new Map(Object.entries(serializedNode.styles)));
117 exportJSON(): SerializedCustomTableCellNode {
119 ...super.exportJSON(),
120 type: 'custom-table-cell',
121 styles: Object.fromEntries(this.__styles),
126 function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput {
127 const output = $convertTableCellNodeElement(domNode);
129 if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
130 output.node.setStyles(extractStyleMapFromElement(domNode));
137 * Function taken from:
138 * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289
139 * Copyright (c) Meta Platforms, Inc. and affiliates.
141 * Modified since copy.
143 export function $convertTableCellNodeElement(
145 ): DOMConversionOutput {
146 const domNode_ = domNode as HTMLTableCellElement;
147 const nodeName = domNode.nodeName.toLowerCase();
149 let width: number | undefined = undefined;
152 const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
153 if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
154 width = parseFloat(domNode_.style.width);
157 const tableCellNode = $createTableCellNode(
159 ? TableCellHeaderStates.ROW
160 : TableCellHeaderStates.NO_STATUS,
165 tableCellNode.__rowSpan = domNode_.rowSpan;
167 const style = domNode_.style;
168 const textDecoration = style.textDecoration.split(' ');
169 const hasBoldFontWeight =
170 style.fontWeight === '700' || style.fontWeight === 'bold';
171 const hasLinethroughTextDecoration = textDecoration.includes('line-through');
172 const hasItalicFontStyle = style.fontStyle === 'italic';
173 const hasUnderlineTextDecoration = textDecoration.includes('underline');
175 after: (childLexicalNodes) => {
176 if (childLexicalNodes.length === 0) {
177 childLexicalNodes.push($createParagraphNode());
179 return childLexicalNodes;
181 forChild: (lexicalNode, parentLexicalNode) => {
182 if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
183 const paragraphNode = $createParagraphNode();
185 $isLineBreakNode(lexicalNode) &&
186 lexicalNode.getTextContent() === '\n'
190 if ($isTextNode(lexicalNode)) {
191 if (hasBoldFontWeight) {
192 lexicalNode.toggleFormat('bold');
194 if (hasLinethroughTextDecoration) {
195 lexicalNode.toggleFormat('strikethrough');
197 if (hasItalicFontStyle) {
198 lexicalNode.toggleFormat('italic');
200 if (hasUnderlineTextDecoration) {
201 lexicalNode.toggleFormat('underline');
204 paragraphNode.append(lexicalNode);
205 return paragraphNode;
215 export function $createCustomTableCellNode(
216 headerState: TableCellHeaderState,
219 ): CustomTableCellNode {
220 return new CustomTableCellNode(headerState, colSpan, width);
223 export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode {
224 return node instanceof CustomTableCellNode;