18 SerializedTableCellNode,
19 TableCellHeaderStates,
21 } from "@lexical/table";
22 import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
24 export type SerializedCustomTableCellNode = Spread<{
25 styles: Record<string, string>,
26 }, SerializedTableCellNode>
28 export class CustomTableCellNode extends TableCellNode {
29 __styles: Map<string, string> = new Map;
31 static getType(): string {
32 return 'custom-table-cell';
35 static clone(node: CustomTableCellNode): CustomTableCellNode {
36 const cellNode = new CustomTableCellNode(
42 cellNode.__rowSpan = node.__rowSpan;
43 cellNode.__styles = new Map(node.__styles);
47 getStyles(): Map<string, string> {
48 const self = this.getLatest();
49 return new Map(self.__styles);
52 setStyles(styles: Map<string, string>): void {
53 const self = this.getWritable();
54 self.__styles = new Map(styles);
57 updateTag(tag: string): void {
58 const isHeader = tag.toLowerCase() === 'th';
59 const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
60 const self = this.getWritable();
61 self.__headerState = state;
64 createDOM(config: EditorConfig): HTMLElement {
65 const element = super.createDOM(config);
67 for (const [name, value] of this.__styles.entries()) {
68 element.style.setProperty(name, value);
74 updateDOM(prevNode: CustomTableCellNode): boolean {
75 return super.updateDOM(prevNode)
76 || this.__styles !== prevNode.__styles;
79 static importDOM(): DOMConversionMap | null {
81 td: (node: Node) => ({
82 conversion: $convertCustomTableCellNodeElement,
85 th: (node: Node) => ({
86 conversion: $convertCustomTableCellNodeElement,
92 exportDOM(editor: LexicalEditor): DOMExportOutput {
93 const element = this.createDOM(editor._config);
99 static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode {
100 const node = $createCustomTableCellNode(
101 serializedNode.headerState,
102 serializedNode.colSpan,
103 serializedNode.width,
106 node.setStyles(new Map<string, string>(Object.entries(serializedNode.styles)));
111 exportJSON(): SerializedCustomTableCellNode {
113 ...super.exportJSON(),
114 type: 'custom-table-cell',
115 styles: Object.fromEntries(this.__styles),
120 function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput {
121 const output = $convertTableCellNodeElement(domNode);
123 if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
124 const styleMap = new Map<string, string>();
125 const styleNames = Array.from(domNode.style);
126 for (const style of styleNames) {
127 styleMap.set(style, domNode.style.getPropertyValue(style));
129 output.node.setStyles(styleMap);
136 * Function taken from:
137 * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289
138 * Copyright (c) Meta Platforms, Inc. and affiliates.
140 * Modified since copy.
142 export function $convertTableCellNodeElement(
144 ): DOMConversionOutput {
145 const domNode_ = domNode as HTMLTableCellElement;
146 const nodeName = domNode.nodeName.toLowerCase();
148 let width: number | undefined = undefined;
151 const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
152 if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
153 width = parseFloat(domNode_.style.width);
156 const tableCellNode = $createTableCellNode(
158 ? TableCellHeaderStates.ROW
159 : TableCellHeaderStates.NO_STATUS,
164 tableCellNode.__rowSpan = domNode_.rowSpan;
166 const style = domNode_.style;
167 const textDecoration = style.textDecoration.split(' ');
168 const hasBoldFontWeight =
169 style.fontWeight === '700' || style.fontWeight === 'bold';
170 const hasLinethroughTextDecoration = textDecoration.includes('line-through');
171 const hasItalicFontStyle = style.fontStyle === 'italic';
172 const hasUnderlineTextDecoration = textDecoration.includes('underline');
174 after: (childLexicalNodes) => {
175 if (childLexicalNodes.length === 0) {
176 childLexicalNodes.push($createParagraphNode());
178 return childLexicalNodes;
180 forChild: (lexicalNode, parentLexicalNode) => {
181 if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
182 const paragraphNode = $createParagraphNode();
184 $isLineBreakNode(lexicalNode) &&
185 lexicalNode.getTextContent() === '\n'
189 if ($isTextNode(lexicalNode)) {
190 if (hasBoldFontWeight) {
191 lexicalNode.toggleFormat('bold');
193 if (hasLinethroughTextDecoration) {
194 lexicalNode.toggleFormat('strikethrough');
196 if (hasItalicFontStyle) {
197 lexicalNode.toggleFormat('italic');
199 if (hasUnderlineTextDecoration) {
200 lexicalNode.toggleFormat('underline');
203 paragraphNode.append(lexicalNode);
204 return paragraphNode;
214 export function $createCustomTableCellNode(
215 headerState: TableCellHeaderState,
218 ): CustomTableCellNode {
219 return new CustomTableCellNode(headerState, colSpan, width);
222 export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode {
223 return node instanceof CustomTableCellNode;