18 SerializedTableCellNode,
19 TableCellHeaderStates,
21 } from "@lexical/table";
22 import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
23 import {extractStyleMapFromElement, StyleMap} from "../utils/dom";
24 import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common";
26 export type SerializedCustomTableCellNode = Spread<{
27 styles: Record<string, string>;
28 alignment: CommonBlockAlignment;
29 }, SerializedTableCellNode>
31 export class CustomTableCellNode extends TableCellNode {
32 __styles: StyleMap = new Map;
33 __alignment: CommonBlockAlignment = '';
35 static getType(): string {
36 return 'custom-table-cell';
39 static clone(node: CustomTableCellNode): CustomTableCellNode {
40 const cellNode = new CustomTableCellNode(
46 cellNode.__rowSpan = node.__rowSpan;
47 cellNode.__styles = new Map(node.__styles);
48 cellNode.__alignment = node.__alignment;
53 const self = this.getWritable();
54 self.__width = undefined;
57 getStyles(): StyleMap {
58 const self = this.getLatest();
59 return new Map(self.__styles);
62 setStyles(styles: StyleMap): void {
63 const self = this.getWritable();
64 self.__styles = new Map(styles);
67 setAlignment(alignment: CommonBlockAlignment) {
68 const self = this.getWritable();
69 self.__alignment = alignment;
72 getAlignment(): CommonBlockAlignment {
73 const self = this.getLatest();
74 return self.__alignment;
77 updateTag(tag: string): void {
78 const isHeader = tag.toLowerCase() === 'th';
79 const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS;
80 const self = this.getWritable();
81 self.__headerState = state;
84 createDOM(config: EditorConfig): HTMLElement {
85 const element = super.createDOM(config);
87 for (const [name, value] of this.__styles.entries()) {
88 element.style.setProperty(name, value);
91 if (this.__alignment) {
92 element.classList.add('align-' + this.__alignment);
98 updateDOM(prevNode: CustomTableCellNode): boolean {
99 return super.updateDOM(prevNode)
100 || this.__styles !== prevNode.__styles
101 || this.__alignment !== prevNode.__alignment;
104 static importDOM(): DOMConversionMap | null {
106 td: (node: Node) => ({
107 conversion: $convertCustomTableCellNodeElement,
110 th: (node: Node) => ({
111 conversion: $convertCustomTableCellNodeElement,
117 exportDOM(editor: LexicalEditor): DOMExportOutput {
118 const element = this.createDOM(editor._config);
124 static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode {
125 const node = $createCustomTableCellNode(
126 serializedNode.headerState,
127 serializedNode.colSpan,
128 serializedNode.width,
131 node.setStyles(new Map(Object.entries(serializedNode.styles)));
132 node.setAlignment(serializedNode.alignment);
137 exportJSON(): SerializedCustomTableCellNode {
139 ...super.exportJSON(),
140 type: 'custom-table-cell',
141 styles: Object.fromEntries(this.__styles),
142 alignment: this.__alignment,
147 function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput {
148 const output = $convertTableCellNodeElement(domNode);
150 if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
151 output.node.setStyles(extractStyleMapFromElement(domNode));
152 output.node.setAlignment(extractAlignmentFromElement(domNode));
159 * Function taken from:
160 * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289
161 * Copyright (c) Meta Platforms, Inc. and affiliates.
163 * Modified since copy.
165 export function $convertTableCellNodeElement(
167 ): DOMConversionOutput {
168 const domNode_ = domNode as HTMLTableCellElement;
169 const nodeName = domNode.nodeName.toLowerCase();
171 let width: number | undefined = undefined;
174 const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
175 if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
176 width = parseFloat(domNode_.style.width);
179 const tableCellNode = $createTableCellNode(
181 ? TableCellHeaderStates.ROW
182 : TableCellHeaderStates.NO_STATUS,
187 tableCellNode.__rowSpan = domNode_.rowSpan;
189 const style = domNode_.style;
190 const textDecoration = style.textDecoration.split(' ');
191 const hasBoldFontWeight =
192 style.fontWeight === '700' || style.fontWeight === 'bold';
193 const hasLinethroughTextDecoration = textDecoration.includes('line-through');
194 const hasItalicFontStyle = style.fontStyle === 'italic';
195 const hasUnderlineTextDecoration = textDecoration.includes('underline');
197 after: (childLexicalNodes) => {
198 if (childLexicalNodes.length === 0) {
199 childLexicalNodes.push($createParagraphNode());
201 return childLexicalNodes;
203 forChild: (lexicalNode, parentLexicalNode) => {
204 if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
205 const paragraphNode = $createParagraphNode();
207 $isLineBreakNode(lexicalNode) &&
208 lexicalNode.getTextContent() === '\n'
212 if ($isTextNode(lexicalNode)) {
213 if (hasBoldFontWeight) {
214 lexicalNode.toggleFormat('bold');
216 if (hasLinethroughTextDecoration) {
217 lexicalNode.toggleFormat('strikethrough');
219 if (hasItalicFontStyle) {
220 lexicalNode.toggleFormat('italic');
222 if (hasUnderlineTextDecoration) {
223 lexicalNode.toggleFormat('underline');
226 paragraphNode.append(lexicalNode);
227 return paragraphNode;
237 export function $createCustomTableCellNode(
238 headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
241 ): CustomTableCellNode {
242 return new CustomTableCellNode(headerState, colSpan, width);
245 export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode {
246 return node instanceof CustomTableCellNode;