Lexical: Merged custom table node code
This commit is contained in:
		
							parent
							
								
									ebd4604f21
								
							
						
					
					
						commit
						57d8449660
					
				|  | @ -28,7 +28,8 @@ import { | ||||||
|   ElementNode, |   ElementNode, | ||||||
| } from 'lexical'; | } from 'lexical'; | ||||||
| 
 | 
 | ||||||
| import {COLUMN_WIDTH, PIXEL_VALUE_REG_EXP} from './constants'; | import {extractStyleMapFromElement, StyleMap} from "../../utils/dom"; | ||||||
|  | import {CommonBlockAlignment, extractAlignmentFromElement} from "../../nodes/_common"; | ||||||
| 
 | 
 | ||||||
| export const TableCellHeaderStates = { | export const TableCellHeaderStates = { | ||||||
|   BOTH: 3, |   BOTH: 3, | ||||||
|  | @ -47,6 +48,8 @@ export type SerializedTableCellNode = Spread< | ||||||
|     headerState: TableCellHeaderState; |     headerState: TableCellHeaderState; | ||||||
|     width?: number; |     width?: number; | ||||||
|     backgroundColor?: null | string; |     backgroundColor?: null | string; | ||||||
|  |     styles: Record<string, string>; | ||||||
|  |     alignment: CommonBlockAlignment; | ||||||
|   }, |   }, | ||||||
|   SerializedElementNode |   SerializedElementNode | ||||||
| >; | >; | ||||||
|  | @ -63,6 +66,10 @@ export class TableCellNode extends ElementNode { | ||||||
|   __width?: number; |   __width?: number; | ||||||
|   /** @internal */ |   /** @internal */ | ||||||
|   __backgroundColor: null | string; |   __backgroundColor: null | string; | ||||||
|  |   /** @internal */ | ||||||
|  |   __styles: StyleMap = new Map; | ||||||
|  |   /** @internal */ | ||||||
|  |   __alignment: CommonBlockAlignment = ''; | ||||||
| 
 | 
 | ||||||
|   static getType(): string { |   static getType(): string { | ||||||
|     return 'tablecell'; |     return 'tablecell'; | ||||||
|  | @ -77,6 +84,8 @@ export class TableCellNode extends ElementNode { | ||||||
|     ); |     ); | ||||||
|     cellNode.__rowSpan = node.__rowSpan; |     cellNode.__rowSpan = node.__rowSpan; | ||||||
|     cellNode.__backgroundColor = node.__backgroundColor; |     cellNode.__backgroundColor = node.__backgroundColor; | ||||||
|  |     cellNode.__styles = new Map(node.__styles); | ||||||
|  |     cellNode.__alignment = node.__alignment; | ||||||
|     return cellNode; |     return cellNode; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -94,16 +103,20 @@ export class TableCellNode extends ElementNode { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static importJSON(serializedNode: SerializedTableCellNode): TableCellNode { |   static importJSON(serializedNode: SerializedTableCellNode): TableCellNode { | ||||||
|     const colSpan = serializedNode.colSpan || 1; |     const node = $createTableCellNode( | ||||||
|     const rowSpan = serializedNode.rowSpan || 1; |  | ||||||
|     const cellNode = $createTableCellNode( |  | ||||||
|         serializedNode.headerState, |         serializedNode.headerState, | ||||||
|       colSpan, |         serializedNode.colSpan, | ||||||
|       serializedNode.width || undefined, |         serializedNode.width, | ||||||
|     ); |     ); | ||||||
|     cellNode.__rowSpan = rowSpan; | 
 | ||||||
|     cellNode.__backgroundColor = serializedNode.backgroundColor || null; |     if (serializedNode.rowSpan) { | ||||||
|     return cellNode; |         node.setRowSpan(serializedNode.rowSpan); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     node.setStyles(new Map(Object.entries(serializedNode.styles))); | ||||||
|  |     node.setAlignment(serializedNode.alignment); | ||||||
|  | 
 | ||||||
|  |     return node; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|  | @ -144,34 +157,19 @@ export class TableCellNode extends ElementNode { | ||||||
|       this.hasHeader() && config.theme.tableCellHeader, |       this.hasHeader() && config.theme.tableCellHeader, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     for (const [name, value] of this.__styles.entries()) { | ||||||
|  |       element.style.setProperty(name, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (this.__alignment) { | ||||||
|  |       element.classList.add('align-' + this.__alignment); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return element; |     return element; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exportDOM(editor: LexicalEditor): DOMExportOutput { |   exportDOM(editor: LexicalEditor): DOMExportOutput { | ||||||
|     const {element} = super.exportDOM(editor); |     const {element} = super.exportDOM(editor); | ||||||
| 
 |  | ||||||
|     if (element) { |  | ||||||
|       const element_ = element as HTMLTableCellElement; |  | ||||||
|       element_.style.border = '1px solid black'; |  | ||||||
|       if (this.__colSpan > 1) { |  | ||||||
|         element_.colSpan = this.__colSpan; |  | ||||||
|       } |  | ||||||
|       if (this.__rowSpan > 1) { |  | ||||||
|         element_.rowSpan = this.__rowSpan; |  | ||||||
|       } |  | ||||||
|       element_.style.width = `${this.getWidth() || COLUMN_WIDTH}px`; |  | ||||||
| 
 |  | ||||||
|       element_.style.verticalAlign = 'top'; |  | ||||||
|       element_.style.textAlign = 'start'; |  | ||||||
| 
 |  | ||||||
|       const backgroundColor = this.getBackgroundColor(); |  | ||||||
|       if (backgroundColor !== null) { |  | ||||||
|         element_.style.backgroundColor = backgroundColor; |  | ||||||
|       } else if (this.hasHeader()) { |  | ||||||
|         element_.style.backgroundColor = '#f2f3f5'; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return { |     return { | ||||||
|       element, |       element, | ||||||
|     }; |     }; | ||||||
|  | @ -186,6 +184,8 @@ export class TableCellNode extends ElementNode { | ||||||
|       rowSpan: this.__rowSpan, |       rowSpan: this.__rowSpan, | ||||||
|       type: 'tablecell', |       type: 'tablecell', | ||||||
|       width: this.getWidth(), |       width: this.getWidth(), | ||||||
|  |       styles: Object.fromEntries(this.__styles), | ||||||
|  |       alignment: this.__alignment, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -231,6 +231,38 @@ export class TableCellNode extends ElementNode { | ||||||
|     return this.getLatest().__width; |     return this.getLatest().__width; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   clearWidth(): void { | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__width = undefined; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getStyles(): StyleMap { | ||||||
|  |     const self = this.getLatest(); | ||||||
|  |     return new Map(self.__styles); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setStyles(styles: StyleMap): void { | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__styles = new Map(styles); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setAlignment(alignment: CommonBlockAlignment) { | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__alignment = alignment; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getAlignment(): CommonBlockAlignment { | ||||||
|  |     const self = this.getLatest(); | ||||||
|  |     return self.__alignment; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateTag(tag: string): void { | ||||||
|  |     const isHeader = tag.toLowerCase() === 'th'; | ||||||
|  |     const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS; | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__headerState = state; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getBackgroundColor(): null | string { |   getBackgroundColor(): null | string { | ||||||
|     return this.getLatest().__backgroundColor; |     return this.getLatest().__backgroundColor; | ||||||
|   } |   } | ||||||
|  | @ -265,7 +297,9 @@ export class TableCellNode extends ElementNode { | ||||||
|       prevNode.__width !== this.__width || |       prevNode.__width !== this.__width || | ||||||
|       prevNode.__colSpan !== this.__colSpan || |       prevNode.__colSpan !== this.__colSpan || | ||||||
|       prevNode.__rowSpan !== this.__rowSpan || |       prevNode.__rowSpan !== this.__rowSpan || | ||||||
|       prevNode.__backgroundColor !== this.__backgroundColor |       prevNode.__backgroundColor !== this.__backgroundColor || | ||||||
|  |       prevNode.__styles !== this.__styles || | ||||||
|  |       prevNode.__alignment !== this.__alignment | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -294,6 +328,8 @@ export function $convertTableCellNodeElement( | ||||||
| 
 | 
 | ||||||
|   let width: number | undefined = undefined; |   let width: number | undefined = undefined; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |   const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/; | ||||||
|   if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) { |   if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) { | ||||||
|     width = parseFloat(domNode_.style.width); |     width = parseFloat(domNode_.style.width); | ||||||
|   } |   } | ||||||
|  | @ -307,10 +343,6 @@ export function $convertTableCellNodeElement( | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   tableCellNode.__rowSpan = domNode_.rowSpan; |   tableCellNode.__rowSpan = domNode_.rowSpan; | ||||||
|   const backgroundColor = domNode_.style.backgroundColor; |  | ||||||
|   if (backgroundColor !== '') { |  | ||||||
|     tableCellNode.__backgroundColor = backgroundColor; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   const style = domNode_.style; |   const style = domNode_.style; | ||||||
|   const textDecoration = style.textDecoration.split(' '); |   const textDecoration = style.textDecoration.split(' '); | ||||||
|  | @ -319,6 +351,12 @@ export function $convertTableCellNodeElement( | ||||||
|   const hasLinethroughTextDecoration = textDecoration.includes('line-through'); |   const hasLinethroughTextDecoration = textDecoration.includes('line-through'); | ||||||
|   const hasItalicFontStyle = style.fontStyle === 'italic'; |   const hasItalicFontStyle = style.fontStyle === 'italic'; | ||||||
|   const hasUnderlineTextDecoration = textDecoration.includes('underline'); |   const hasUnderlineTextDecoration = textDecoration.includes('underline'); | ||||||
|  | 
 | ||||||
|  |   if (domNode instanceof HTMLElement) { | ||||||
|  |     tableCellNode.setStyles(extractStyleMapFromElement(domNode)); | ||||||
|  |     tableCellNode.setAlignment(extractAlignmentFromElement(domNode)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   return { |   return { | ||||||
|     after: (childLexicalNodes) => { |     after: (childLexicalNodes) => { | ||||||
|       if (childLexicalNodes.length === 0) { |       if (childLexicalNodes.length === 0) { | ||||||
|  | @ -360,7 +398,7 @@ export function $convertTableCellNodeElement( | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $createTableCellNode( | export function $createTableCellNode( | ||||||
|   headerState: TableCellHeaderState, |   headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS, | ||||||
|   colSpan = 1, |   colSpan = 1, | ||||||
|   width?: number, |   width?: number, | ||||||
| ): TableCellNode { | ): TableCellNode { | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import type {TableCellNode} from './LexicalTableCellNode'; | import type {TableCellNode} from './LexicalTableCellNode'; | ||||||
| import type { | import { | ||||||
|   DOMConversionMap, |   DOMConversionMap, | ||||||
|   DOMConversionOutput, |   DOMConversionOutput, | ||||||
|   DOMExportOutput, |   DOMExportOutput, | ||||||
|  | @ -15,7 +15,7 @@ import type { | ||||||
|   LexicalEditor, |   LexicalEditor, | ||||||
|   LexicalNode, |   LexicalNode, | ||||||
|   NodeKey, |   NodeKey, | ||||||
|   SerializedElementNode, |   SerializedElementNode, Spread, | ||||||
| } from 'lexical'; | } from 'lexical'; | ||||||
| 
 | 
 | ||||||
| import {addClassNamesToElement, isHTMLElement} from '@lexical/utils'; | import {addClassNamesToElement, isHTMLElement} from '@lexical/utils'; | ||||||
|  | @ -27,19 +27,36 @@ import { | ||||||
| 
 | 
 | ||||||
| import {$isTableCellNode} from './LexicalTableCellNode'; | import {$isTableCellNode} from './LexicalTableCellNode'; | ||||||
| import {TableDOMCell, TableDOMTable} from './LexicalTableObserver'; | import {TableDOMCell, TableDOMTable} from './LexicalTableObserver'; | ||||||
| import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode'; |  | ||||||
| import {getTable} from './LexicalTableSelectionHelpers'; | import {getTable} from './LexicalTableSelectionHelpers'; | ||||||
|  | import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; | ||||||
|  | import { | ||||||
|  |   commonPropertiesDifferent, deserializeCommonBlockNode, | ||||||
|  |   SerializedCommonBlockNode, setCommonBlockPropsFromElement, | ||||||
|  |   updateElementWithCommonBlockProps | ||||||
|  | } from "../../nodes/_common"; | ||||||
|  | import {el, extractStyleMapFromElement, StyleMap} from "../../utils/dom"; | ||||||
|  | import {getTableColumnWidths} from "../../utils/tables"; | ||||||
| 
 | 
 | ||||||
| export type SerializedTableNode = SerializedElementNode; | export type SerializedTableNode = Spread<{ | ||||||
|  |   colWidths: string[]; | ||||||
|  |   styles: Record<string, string>, | ||||||
|  | }, SerializedCommonBlockNode> | ||||||
| 
 | 
 | ||||||
| /** @noInheritDoc */ | /** @noInheritDoc */ | ||||||
| export class TableNode extends ElementNode { | export class TableNode extends CommonBlockNode { | ||||||
|  |   __colWidths: string[] = []; | ||||||
|  |   __styles: StyleMap = new Map; | ||||||
|  | 
 | ||||||
|   static getType(): string { |   static getType(): string { | ||||||
|     return 'table'; |     return 'table'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static clone(node: TableNode): TableNode { |   static clone(node: TableNode): TableNode { | ||||||
|     return new TableNode(node.__key); |     const newNode = new TableNode(node.__key); | ||||||
|  |     copyCommonBlockProperties(node, newNode); | ||||||
|  |     newNode.__colWidths = node.__colWidths; | ||||||
|  |     newNode.__styles = new Map(node.__styles); | ||||||
|  |     return newNode; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static importDOM(): DOMConversionMap | null { |   static importDOM(): DOMConversionMap | null { | ||||||
|  | @ -52,18 +69,24 @@ export class TableNode extends ElementNode { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static importJSON(_serializedNode: SerializedTableNode): TableNode { |   static importJSON(_serializedNode: SerializedTableNode): TableNode { | ||||||
|     return $createTableNode(); |     const node = $createTableNode(); | ||||||
|  |     deserializeCommonBlockNode(_serializedNode, node); | ||||||
|  |     node.setColWidths(_serializedNode.colWidths); | ||||||
|  |     node.setStyles(new Map(Object.entries(_serializedNode.styles))); | ||||||
|  |     return node; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor(key?: NodeKey) { |   constructor(key?: NodeKey) { | ||||||
|     super(key); |     super(key); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exportJSON(): SerializedElementNode { |   exportJSON(): SerializedTableNode { | ||||||
|     return { |     return { | ||||||
|       ...super.exportJSON(), |       ...super.exportJSON(), | ||||||
|       type: 'table', |       type: 'table', | ||||||
|       version: 1, |       version: 1, | ||||||
|  |       colWidths: this.__colWidths, | ||||||
|  |       styles: Object.fromEntries(this.__styles), | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -72,11 +95,33 @@ export class TableNode extends ElementNode { | ||||||
| 
 | 
 | ||||||
|     addClassNamesToElement(tableElement, config.theme.table); |     addClassNamesToElement(tableElement, config.theme.table); | ||||||
| 
 | 
 | ||||||
|  |     updateElementWithCommonBlockProps(tableElement, this); | ||||||
|  | 
 | ||||||
|  |     const colWidths = this.getColWidths(); | ||||||
|  |     if (colWidths.length > 0) { | ||||||
|  |       const colgroup = el('colgroup'); | ||||||
|  |       for (const width of colWidths) { | ||||||
|  |         const col = el('col'); | ||||||
|  |         if (width) { | ||||||
|  |           col.style.width = width; | ||||||
|  |         } | ||||||
|  |         colgroup.append(col); | ||||||
|  |       } | ||||||
|  |       tableElement.append(colgroup); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (const [name, value] of this.__styles.entries()) { | ||||||
|  |       tableElement.style.setProperty(name, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return tableElement; |     return tableElement; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updateDOM(): boolean { |   updateDOM(_prevNode: TableNode): boolean { | ||||||
|     return false; |     return commonPropertiesDifferent(_prevNode, this) | ||||||
|  |       || this.__colWidths.join(':') !== _prevNode.__colWidths.join(':') | ||||||
|  |       || this.__styles.size !== _prevNode.__styles.size | ||||||
|  |       || (Array.from(this.__styles.values()).join(':') !== (Array.from(_prevNode.__styles.values()).join(':'))); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exportDOM(editor: LexicalEditor): DOMExportOutput { |   exportDOM(editor: LexicalEditor): DOMExportOutput { | ||||||
|  | @ -115,6 +160,26 @@ export class TableNode extends ElementNode { | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   setColWidths(widths: string[]) { | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__colWidths = widths; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getColWidths(): string[] { | ||||||
|  |     const self = this.getLatest(); | ||||||
|  |     return self.__colWidths; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getStyles(): StyleMap { | ||||||
|  |     const self = this.getLatest(); | ||||||
|  |     return new Map(self.__styles); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setStyles(styles: StyleMap): void { | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__styles = new Map(styles); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getCordsFromCellNode( |   getCordsFromCellNode( | ||||||
|     tableCellNode: TableCellNode, |     tableCellNode: TableCellNode, | ||||||
|     table: TableDOMTable, |     table: TableDOMTable, | ||||||
|  | @ -239,8 +304,15 @@ export function $getElementForTableNode( | ||||||
|   return getTable(tableElement); |   return getTable(tableElement); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $convertTableElement(_domNode: Node): DOMConversionOutput { | export function $convertTableElement(element: HTMLElement): DOMConversionOutput { | ||||||
|   return {node: $createTableNode()}; |   const node = $createTableNode(); | ||||||
|  |   setCommonBlockPropsFromElement(element, node); | ||||||
|  | 
 | ||||||
|  |   const colWidths = getTableColumnWidths(element as HTMLTableElement); | ||||||
|  |   node.setColWidths(colWidths); | ||||||
|  |   node.setStyles(extractStyleMapFromElement(element)); | ||||||
|  | 
 | ||||||
|  |   return {node}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $createTableNode(): TableNode { | export function $createTableNode(): TableNode { | ||||||
|  |  | ||||||
|  | @ -20,11 +20,12 @@ import { | ||||||
|   SerializedElementNode, |   SerializedElementNode, | ||||||
| } from 'lexical'; | } from 'lexical'; | ||||||
| 
 | 
 | ||||||
| import {PIXEL_VALUE_REG_EXP} from './constants'; | import {extractStyleMapFromElement, sizeToPixels, StyleMap} from "../../utils/dom"; | ||||||
| 
 | 
 | ||||||
| export type SerializedTableRowNode = Spread< | export type SerializedTableRowNode = Spread< | ||||||
|   { |   { | ||||||
|     height?: number; |     styles: Record<string, string>, | ||||||
|  |     height?: number, | ||||||
|   }, |   }, | ||||||
|   SerializedElementNode |   SerializedElementNode | ||||||
| >; | >; | ||||||
|  | @ -33,13 +34,17 @@ export type SerializedTableRowNode = Spread< | ||||||
| export class TableRowNode extends ElementNode { | export class TableRowNode extends ElementNode { | ||||||
|   /** @internal */ |   /** @internal */ | ||||||
|   __height?: number; |   __height?: number; | ||||||
|  |   /** @internal */ | ||||||
|  |   __styles: StyleMap = new Map(); | ||||||
| 
 | 
 | ||||||
|   static getType(): string { |   static getType(): string { | ||||||
|     return 'tablerow'; |     return 'tablerow'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static clone(node: TableRowNode): TableRowNode { |   static clone(node: TableRowNode): TableRowNode { | ||||||
|     return new TableRowNode(node.__height, node.__key); |     const newNode = new TableRowNode(node.__key); | ||||||
|  |     newNode.__styles = new Map(node.__styles); | ||||||
|  |     return newNode; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static importDOM(): DOMConversionMap | null { |   static importDOM(): DOMConversionMap | null { | ||||||
|  | @ -52,20 +57,24 @@ export class TableRowNode extends ElementNode { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static importJSON(serializedNode: SerializedTableRowNode): TableRowNode { |   static importJSON(serializedNode: SerializedTableRowNode): TableRowNode { | ||||||
|     return $createTableRowNode(serializedNode.height); |     const node = $createTableRowNode(); | ||||||
|  | 
 | ||||||
|  |     node.setStyles(new Map(Object.entries(serializedNode.styles))); | ||||||
|  | 
 | ||||||
|  |     return node; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor(height?: number, key?: NodeKey) { |   constructor(key?: NodeKey) { | ||||||
|     super(key); |     super(key); | ||||||
|     this.__height = height; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exportJSON(): SerializedTableRowNode { |   exportJSON(): SerializedTableRowNode { | ||||||
|     return { |     return { | ||||||
|       ...super.exportJSON(), |       ...super.exportJSON(), | ||||||
|       ...(this.getHeight() && {height: this.getHeight()}), |  | ||||||
|       type: 'tablerow', |       type: 'tablerow', | ||||||
|       version: 1, |       version: 1, | ||||||
|  |       styles: Object.fromEntries(this.__styles), | ||||||
|  |       height: this.__height || 0, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -76,6 +85,10 @@ export class TableRowNode extends ElementNode { | ||||||
|       element.style.height = `${this.__height}px`; |       element.style.height = `${this.__height}px`; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     for (const [name, value] of this.__styles.entries()) { | ||||||
|  |       element.style.setProperty(name, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     addClassNamesToElement(element, config.theme.tableRow); |     addClassNamesToElement(element, config.theme.tableRow); | ||||||
| 
 | 
 | ||||||
|     return element; |     return element; | ||||||
|  | @ -85,6 +98,16 @@ export class TableRowNode extends ElementNode { | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   getStyles(): StyleMap { | ||||||
|  |     const self = this.getLatest(); | ||||||
|  |     return new Map(self.__styles); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setStyles(styles: StyleMap): void { | ||||||
|  |     const self = this.getWritable(); | ||||||
|  |     self.__styles = new Map(styles); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setHeight(height: number): number | null | undefined { |   setHeight(height: number): number | null | undefined { | ||||||
|     const self = this.getWritable(); |     const self = this.getWritable(); | ||||||
|     self.__height = height; |     self.__height = height; | ||||||
|  | @ -96,7 +119,8 @@ export class TableRowNode extends ElementNode { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updateDOM(prevNode: TableRowNode): boolean { |   updateDOM(prevNode: TableRowNode): boolean { | ||||||
|     return prevNode.__height !== this.__height; |     return prevNode.__height !== this.__height | ||||||
|  |         || prevNode.__styles !== this.__styles; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   canBeEmpty(): false { |   canBeEmpty(): false { | ||||||
|  | @ -109,18 +133,21 @@ export class TableRowNode extends ElementNode { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $convertTableRowElement(domNode: Node): DOMConversionOutput { | export function $convertTableRowElement(domNode: Node): DOMConversionOutput { | ||||||
|   const domNode_ = domNode as HTMLTableCellElement; |   const rowNode = $createTableRowNode(); | ||||||
|   let height: number | undefined = undefined; |   const domNode_ = domNode as HTMLElement; | ||||||
| 
 | 
 | ||||||
|   if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) { |   const height = sizeToPixels(domNode_.style.height); | ||||||
|     height = parseFloat(domNode_.style.height); |   rowNode.setHeight(height); | ||||||
|  | 
 | ||||||
|  |   if (domNode instanceof HTMLElement) { | ||||||
|  |     rowNode.setStyles(extractStyleMapFromElement(domNode)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return {node: $createTableRowNode(height)}; |   return {node: rowNode}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $createTableRowNode(height?: number): TableRowNode { | export function $createTableRowNode(): TableRowNode { | ||||||
|   return $applyNodeReplacement(new TableRowNode(height)); |   return $applyNodeReplacement(new TableRowNode()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $isTableRowNode( | export function $isTableRowNode( | ||||||
|  |  | ||||||
|  | @ -438,59 +438,6 @@ export function applyTableHandlers( | ||||||
|     ), |     ), | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   tableObserver.listenersToRemove.add( |  | ||||||
|     editor.registerCommand<ElementFormatType>( |  | ||||||
|       FORMAT_ELEMENT_COMMAND, |  | ||||||
|       (formatType) => { |  | ||||||
|         const selection = $getSelection(); |  | ||||||
|         if ( |  | ||||||
|           !$isTableSelection(selection) || |  | ||||||
|           !$isSelectionInTable(selection, tableNode) |  | ||||||
|         ) { |  | ||||||
|           return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const anchorNode = selection.anchor.getNode(); |  | ||||||
|         const focusNode = selection.focus.getNode(); |  | ||||||
|         if (!$isTableCellNode(anchorNode) || !$isTableCellNode(focusNode)) { |  | ||||||
|           return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const [tableMap, anchorCell, focusCell] = $computeTableMap( |  | ||||||
|           tableNode, |  | ||||||
|           anchorNode, |  | ||||||
|           focusNode, |  | ||||||
|         ); |  | ||||||
|         const maxRow = Math.max(anchorCell.startRow, focusCell.startRow); |  | ||||||
|         const maxColumn = Math.max( |  | ||||||
|           anchorCell.startColumn, |  | ||||||
|           focusCell.startColumn, |  | ||||||
|         ); |  | ||||||
|         const minRow = Math.min(anchorCell.startRow, focusCell.startRow); |  | ||||||
|         const minColumn = Math.min( |  | ||||||
|           anchorCell.startColumn, |  | ||||||
|           focusCell.startColumn, |  | ||||||
|         ); |  | ||||||
|         for (let i = minRow; i <= maxRow; i++) { |  | ||||||
|           for (let j = minColumn; j <= maxColumn; j++) { |  | ||||||
|             const cell = tableMap[i][j].cell; |  | ||||||
|             cell.setFormat(formatType); |  | ||||||
| 
 |  | ||||||
|             const cellChildren = cell.getChildren(); |  | ||||||
|             for (let k = 0; k < cellChildren.length; k++) { |  | ||||||
|               const child = cellChildren[k]; |  | ||||||
|               if ($isElementNode(child) && !child.isInline()) { |  | ||||||
|                 child.setFormat(formatType); |  | ||||||
|               } |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|       }, |  | ||||||
|       COMMAND_PRIORITY_CRITICAL, |  | ||||||
|     ), |  | ||||||
|   ); |  | ||||||
| 
 |  | ||||||
|   tableObserver.listenersToRemove.add( |   tableObserver.listenersToRemove.add( | ||||||
|     editor.registerCommand( |     editor.registerCommand( | ||||||
|       CONTROLLED_TEXT_INSERTION_COMMAND, |       CONTROLLED_TEXT_INSERTION_COMMAND, | ||||||
|  |  | ||||||
|  | @ -39,10 +39,9 @@ describe('LexicalTableRowNode tests', () => { | ||||||
|           `<tr class="${editorConfig.theme.tableRow}"></tr>`, |           `<tr class="${editorConfig.theme.tableRow}"></tr>`, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         const rowHeight = 36; |         const rowWithCustomHeightNode = $createTableRowNode(); | ||||||
|         const rowWithCustomHeightNode = $createTableRowNode(36); |  | ||||||
|         expect(rowWithCustomHeightNode.createDOM(editorConfig).outerHTML).toBe( |         expect(rowWithCustomHeightNode.createDOM(editorConfig).outerHTML).toBe( | ||||||
|           `<tr style="height: ${rowHeight}px;" class="${editorConfig.theme.tableRow}"></tr>`, |           `<tr class="${editorConfig.theme.tableRow}"></tr>`, | ||||||
|         ); |         ); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -1,247 +0,0 @@ | ||||||
| import { |  | ||||||
|     $createParagraphNode, |  | ||||||
|     $isElementNode, |  | ||||||
|     $isLineBreakNode, |  | ||||||
|     $isTextNode, |  | ||||||
|     DOMConversionMap, |  | ||||||
|     DOMConversionOutput, |  | ||||||
|     DOMExportOutput, |  | ||||||
|     EditorConfig, |  | ||||||
|     LexicalEditor, |  | ||||||
|     LexicalNode, |  | ||||||
|     Spread |  | ||||||
| } from "lexical"; |  | ||||||
| 
 |  | ||||||
| import { |  | ||||||
|     $createTableCellNode, |  | ||||||
|     $isTableCellNode, |  | ||||||
|     SerializedTableCellNode, |  | ||||||
|     TableCellHeaderStates, |  | ||||||
|     TableCellNode |  | ||||||
| } from "@lexical/table"; |  | ||||||
| import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode"; |  | ||||||
| import {extractStyleMapFromElement, StyleMap} from "../utils/dom"; |  | ||||||
| import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common"; |  | ||||||
| 
 |  | ||||||
| export type SerializedCustomTableCellNode = Spread<{ |  | ||||||
|     styles: Record<string, string>; |  | ||||||
|     alignment: CommonBlockAlignment; |  | ||||||
| }, SerializedTableCellNode> |  | ||||||
| 
 |  | ||||||
| export class CustomTableCellNode extends TableCellNode { |  | ||||||
|     __styles: StyleMap = new Map; |  | ||||||
|     __alignment: CommonBlockAlignment = ''; |  | ||||||
| 
 |  | ||||||
|     static getType(): string { |  | ||||||
|         return 'custom-table-cell'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static clone(node: CustomTableCellNode): CustomTableCellNode { |  | ||||||
|         const cellNode = new CustomTableCellNode( |  | ||||||
|             node.__headerState, |  | ||||||
|             node.__colSpan, |  | ||||||
|             node.__width, |  | ||||||
|             node.__key, |  | ||||||
|         ); |  | ||||||
|         cellNode.__rowSpan = node.__rowSpan; |  | ||||||
|         cellNode.__styles = new Map(node.__styles); |  | ||||||
|         cellNode.__alignment = node.__alignment; |  | ||||||
|         return cellNode; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     clearWidth(): void { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__width = undefined; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getStyles(): StyleMap { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return new Map(self.__styles); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setStyles(styles: StyleMap): void { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__styles = new Map(styles); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setAlignment(alignment: CommonBlockAlignment) { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__alignment = alignment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getAlignment(): CommonBlockAlignment { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return self.__alignment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     updateTag(tag: string): void { |  | ||||||
|         const isHeader = tag.toLowerCase() === 'th'; |  | ||||||
|         const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS; |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__headerState = state; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     createDOM(config: EditorConfig): HTMLElement { |  | ||||||
|         const element = super.createDOM(config); |  | ||||||
| 
 |  | ||||||
|         for (const [name, value] of this.__styles.entries()) { |  | ||||||
|             element.style.setProperty(name, value); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (this.__alignment) { |  | ||||||
|             element.classList.add('align-' + this.__alignment); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return element; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     updateDOM(prevNode: CustomTableCellNode): boolean { |  | ||||||
|         return super.updateDOM(prevNode) |  | ||||||
|             || this.__styles !== prevNode.__styles |  | ||||||
|             || this.__alignment !== prevNode.__alignment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static importDOM(): DOMConversionMap | null { |  | ||||||
|         return { |  | ||||||
|             td: (node: Node) => ({ |  | ||||||
|                 conversion: $convertCustomTableCellNodeElement, |  | ||||||
|                 priority: 0, |  | ||||||
|             }), |  | ||||||
|             th: (node: Node) => ({ |  | ||||||
|                 conversion: $convertCustomTableCellNodeElement, |  | ||||||
|                 priority: 0, |  | ||||||
|             }), |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     exportDOM(editor: LexicalEditor): DOMExportOutput { |  | ||||||
|         const element = this.createDOM(editor._config); |  | ||||||
|         return { |  | ||||||
|             element |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode { |  | ||||||
|         const node = $createCustomTableCellNode( |  | ||||||
|             serializedNode.headerState, |  | ||||||
|             serializedNode.colSpan, |  | ||||||
|             serializedNode.width, |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         node.setStyles(new Map(Object.entries(serializedNode.styles))); |  | ||||||
|         node.setAlignment(serializedNode.alignment); |  | ||||||
| 
 |  | ||||||
|         return node; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     exportJSON(): SerializedCustomTableCellNode { |  | ||||||
|         return { |  | ||||||
|             ...super.exportJSON(), |  | ||||||
|             type: 'custom-table-cell', |  | ||||||
|             styles: Object.fromEntries(this.__styles), |  | ||||||
|             alignment: this.__alignment, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput { |  | ||||||
|     const output =  $convertTableCellNodeElement(domNode); |  | ||||||
| 
 |  | ||||||
|     if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) { |  | ||||||
|         output.node.setStyles(extractStyleMapFromElement(domNode)); |  | ||||||
|         output.node.setAlignment(extractAlignmentFromElement(domNode)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return output; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Function taken from: |  | ||||||
|  * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289
 |  | ||||||
|  * Copyright (c) Meta Platforms, Inc. and affiliates. |  | ||||||
|  * MIT LICENSE |  | ||||||
|  * Modified since copy. |  | ||||||
|  */ |  | ||||||
| export function $convertTableCellNodeElement( |  | ||||||
|     domNode: Node, |  | ||||||
| ): DOMConversionOutput { |  | ||||||
|     const domNode_ = domNode as HTMLTableCellElement; |  | ||||||
|     const nodeName = domNode.nodeName.toLowerCase(); |  | ||||||
| 
 |  | ||||||
|     let width: number | undefined = undefined; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/; |  | ||||||
|     if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) { |  | ||||||
|         width = parseFloat(domNode_.style.width); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const tableCellNode = $createTableCellNode( |  | ||||||
|         nodeName === 'th' |  | ||||||
|             ? TableCellHeaderStates.ROW |  | ||||||
|             : TableCellHeaderStates.NO_STATUS, |  | ||||||
|         domNode_.colSpan, |  | ||||||
|         width, |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     tableCellNode.__rowSpan = domNode_.rowSpan; |  | ||||||
| 
 |  | ||||||
|     const style = domNode_.style; |  | ||||||
|     const textDecoration = style.textDecoration.split(' '); |  | ||||||
|     const hasBoldFontWeight = |  | ||||||
|         style.fontWeight === '700' || style.fontWeight === 'bold'; |  | ||||||
|     const hasLinethroughTextDecoration = textDecoration.includes('line-through'); |  | ||||||
|     const hasItalicFontStyle = style.fontStyle === 'italic'; |  | ||||||
|     const hasUnderlineTextDecoration = textDecoration.includes('underline'); |  | ||||||
|     return { |  | ||||||
|         after: (childLexicalNodes) => { |  | ||||||
|             if (childLexicalNodes.length === 0) { |  | ||||||
|                 childLexicalNodes.push($createParagraphNode()); |  | ||||||
|             } |  | ||||||
|             return childLexicalNodes; |  | ||||||
|         }, |  | ||||||
|         forChild: (lexicalNode, parentLexicalNode) => { |  | ||||||
|             if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) { |  | ||||||
|                 const paragraphNode = $createParagraphNode(); |  | ||||||
|                 if ( |  | ||||||
|                     $isLineBreakNode(lexicalNode) && |  | ||||||
|                     lexicalNode.getTextContent() === '\n' |  | ||||||
|                 ) { |  | ||||||
|                     return null; |  | ||||||
|                 } |  | ||||||
|                 if ($isTextNode(lexicalNode)) { |  | ||||||
|                     if (hasBoldFontWeight) { |  | ||||||
|                         lexicalNode.toggleFormat('bold'); |  | ||||||
|                     } |  | ||||||
|                     if (hasLinethroughTextDecoration) { |  | ||||||
|                         lexicalNode.toggleFormat('strikethrough'); |  | ||||||
|                     } |  | ||||||
|                     if (hasItalicFontStyle) { |  | ||||||
|                         lexicalNode.toggleFormat('italic'); |  | ||||||
|                     } |  | ||||||
|                     if (hasUnderlineTextDecoration) { |  | ||||||
|                         lexicalNode.toggleFormat('underline'); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 paragraphNode.append(lexicalNode); |  | ||||||
|                 return paragraphNode; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return lexicalNode; |  | ||||||
|         }, |  | ||||||
|         node: tableCellNode, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| export function $createCustomTableCellNode( |  | ||||||
|     headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS, |  | ||||||
|     colSpan = 1, |  | ||||||
|     width?: number, |  | ||||||
| ): CustomTableCellNode { |  | ||||||
|     return new CustomTableCellNode(headerState, colSpan, width); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode { |  | ||||||
|     return node instanceof CustomTableCellNode; |  | ||||||
| } |  | ||||||
|  | @ -1,106 +0,0 @@ | ||||||
| import { |  | ||||||
|     DOMConversionMap, |  | ||||||
|     DOMConversionOutput, |  | ||||||
|     EditorConfig, |  | ||||||
|     LexicalNode, |  | ||||||
|     Spread |  | ||||||
| } from "lexical"; |  | ||||||
| 
 |  | ||||||
| import { |  | ||||||
|     SerializedTableRowNode, |  | ||||||
|     TableRowNode |  | ||||||
| } from "@lexical/table"; |  | ||||||
| import {NodeKey} from "lexical/LexicalNode"; |  | ||||||
| import {extractStyleMapFromElement, StyleMap} from "../utils/dom"; |  | ||||||
| 
 |  | ||||||
| export type SerializedCustomTableRowNode = Spread<{ |  | ||||||
|     styles: Record<string, string>, |  | ||||||
| }, SerializedTableRowNode> |  | ||||||
| 
 |  | ||||||
| export class CustomTableRowNode extends TableRowNode { |  | ||||||
|     __styles: StyleMap = new Map(); |  | ||||||
| 
 |  | ||||||
|     constructor(key?: NodeKey) { |  | ||||||
|         super(0, key); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static getType(): string { |  | ||||||
|         return 'custom-table-row'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static clone(node: CustomTableRowNode): CustomTableRowNode { |  | ||||||
|         const cellNode = new CustomTableRowNode(node.__key); |  | ||||||
| 
 |  | ||||||
|         cellNode.__styles = new Map(node.__styles); |  | ||||||
|         return cellNode; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getStyles(): StyleMap { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return new Map(self.__styles); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setStyles(styles: StyleMap): void { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__styles = new Map(styles); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     createDOM(config: EditorConfig): HTMLElement { |  | ||||||
|         const element = super.createDOM(config); |  | ||||||
| 
 |  | ||||||
|         for (const [name, value] of this.__styles.entries()) { |  | ||||||
|             element.style.setProperty(name, value); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return element; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     updateDOM(prevNode: CustomTableRowNode): boolean { |  | ||||||
|         return super.updateDOM(prevNode) |  | ||||||
|             || this.__styles !== prevNode.__styles; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static importDOM(): DOMConversionMap | null { |  | ||||||
|         return { |  | ||||||
|             tr: (node: Node) => ({ |  | ||||||
|                 conversion: $convertTableRowElement, |  | ||||||
|                 priority: 0, |  | ||||||
|             }), |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static importJSON(serializedNode: SerializedCustomTableRowNode): CustomTableRowNode { |  | ||||||
|         const node = $createCustomTableRowNode(); |  | ||||||
| 
 |  | ||||||
|         node.setStyles(new Map(Object.entries(serializedNode.styles))); |  | ||||||
| 
 |  | ||||||
|         return node; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     exportJSON(): SerializedCustomTableRowNode { |  | ||||||
|         return { |  | ||||||
|             ...super.exportJSON(), |  | ||||||
|             height: 0, |  | ||||||
|             type: 'custom-table-row', |  | ||||||
|             styles: Object.fromEntries(this.__styles), |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function $convertTableRowElement(domNode: Node): DOMConversionOutput { |  | ||||||
|     const rowNode = $createCustomTableRowNode(); |  | ||||||
| 
 |  | ||||||
|     if (domNode instanceof HTMLElement) { |  | ||||||
|         rowNode.setStyles(extractStyleMapFromElement(domNode)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return {node: rowNode}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function $createCustomTableRowNode(): CustomTableRowNode { |  | ||||||
|     return new CustomTableRowNode(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function $isCustomTableRowNode(node: LexicalNode | null | undefined): node is CustomTableRowNode { |  | ||||||
|     return node instanceof CustomTableRowNode; |  | ||||||
| } |  | ||||||
|  | @ -1,166 +0,0 @@ | ||||||
| import {SerializedTableNode, TableNode} from "@lexical/table"; |  | ||||||
| import {DOMConversion, DOMConversionMap, DOMConversionOutput, LexicalNode, Spread} from "lexical"; |  | ||||||
| import {EditorConfig} from "lexical/LexicalEditor"; |  | ||||||
| 
 |  | ||||||
| import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom"; |  | ||||||
| import {getTableColumnWidths} from "../utils/tables"; |  | ||||||
| import { |  | ||||||
|     CommonBlockAlignment, deserializeCommonBlockNode, |  | ||||||
|     SerializedCommonBlockNode, |  | ||||||
|     setCommonBlockPropsFromElement, |  | ||||||
|     updateElementWithCommonBlockProps |  | ||||||
| } from "./_common"; |  | ||||||
| 
 |  | ||||||
| export type SerializedCustomTableNode = Spread<Spread<{ |  | ||||||
|     colWidths: string[]; |  | ||||||
|     styles: Record<string, string>, |  | ||||||
| }, SerializedTableNode>, SerializedCommonBlockNode> |  | ||||||
| 
 |  | ||||||
| export class CustomTableNode extends TableNode { |  | ||||||
|     __id: string = ''; |  | ||||||
|     __colWidths: string[] = []; |  | ||||||
|     __styles: StyleMap = new Map; |  | ||||||
|     __alignment: CommonBlockAlignment = ''; |  | ||||||
|     __inset: number = 0; |  | ||||||
| 
 |  | ||||||
|     static getType() { |  | ||||||
|         return 'custom-table'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setId(id: string) { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__id = id; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getId(): string { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return self.__id; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setAlignment(alignment: CommonBlockAlignment) { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__alignment = alignment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getAlignment(): CommonBlockAlignment { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return self.__alignment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setInset(size: number) { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__inset = size; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getInset(): number { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return self.__inset; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setColWidths(widths: string[]) { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__colWidths = widths; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getColWidths(): string[] { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return self.__colWidths; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     getStyles(): StyleMap { |  | ||||||
|         const self = this.getLatest(); |  | ||||||
|         return new Map(self.__styles); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     setStyles(styles: StyleMap): void { |  | ||||||
|         const self = this.getWritable(); |  | ||||||
|         self.__styles = new Map(styles); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static clone(node: CustomTableNode) { |  | ||||||
|         const newNode = new CustomTableNode(node.__key); |  | ||||||
|         newNode.__id = node.__id; |  | ||||||
|         newNode.__colWidths = node.__colWidths; |  | ||||||
|         newNode.__styles = new Map(node.__styles); |  | ||||||
|         newNode.__alignment = node.__alignment; |  | ||||||
|         newNode.__inset = node.__inset; |  | ||||||
|         return newNode; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     createDOM(config: EditorConfig): HTMLElement { |  | ||||||
|         const dom = super.createDOM(config); |  | ||||||
|         updateElementWithCommonBlockProps(dom, this); |  | ||||||
| 
 |  | ||||||
|         const colWidths = this.getColWidths(); |  | ||||||
|         if (colWidths.length > 0) { |  | ||||||
|             const colgroup = el('colgroup'); |  | ||||||
|             for (const width of colWidths) { |  | ||||||
|                 const col = el('col'); |  | ||||||
|                 if (width) { |  | ||||||
|                     col.style.width = width; |  | ||||||
|                 } |  | ||||||
|                 colgroup.append(col); |  | ||||||
|             } |  | ||||||
|             dom.append(colgroup); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for (const [name, value] of this.__styles.entries()) { |  | ||||||
|             dom.style.setProperty(name, value); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return dom; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     updateDOM(): boolean { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     exportJSON(): SerializedCustomTableNode { |  | ||||||
|         return { |  | ||||||
|             ...super.exportJSON(), |  | ||||||
|             type: 'custom-table', |  | ||||||
|             version: 1, |  | ||||||
|             id: this.__id, |  | ||||||
|             colWidths: this.__colWidths, |  | ||||||
|             styles: Object.fromEntries(this.__styles), |  | ||||||
|             alignment: this.__alignment, |  | ||||||
|             inset: this.__inset, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static importJSON(serializedNode: SerializedCustomTableNode): CustomTableNode { |  | ||||||
|         const node = $createCustomTableNode(); |  | ||||||
|         deserializeCommonBlockNode(serializedNode, node); |  | ||||||
|         node.setColWidths(serializedNode.colWidths); |  | ||||||
|         node.setStyles(new Map(Object.entries(serializedNode.styles))); |  | ||||||
|         return node; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static importDOM(): DOMConversionMap|null { |  | ||||||
|         return { |  | ||||||
|             table(node: HTMLElement): DOMConversion|null { |  | ||||||
|                 return { |  | ||||||
|                     conversion: (element: HTMLElement): DOMConversionOutput|null => { |  | ||||||
|                         const node = $createCustomTableNode(); |  | ||||||
|                         setCommonBlockPropsFromElement(element, node); |  | ||||||
| 
 |  | ||||||
|                         const colWidths = getTableColumnWidths(element as HTMLTableElement); |  | ||||||
|                         node.setColWidths(colWidths); |  | ||||||
|                         node.setStyles(extractStyleMapFromElement(element)); |  | ||||||
| 
 |  | ||||||
|                         return {node}; |  | ||||||
|                     }, |  | ||||||
|                     priority: 1, |  | ||||||
|                 }; |  | ||||||
|             }, |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function $createCustomTableNode(): CustomTableNode { |  | ||||||
|     return new CustomTableNode(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function $isCustomTableNode(node: LexicalNode | null | undefined): node is CustomTableNode { |  | ||||||
|     return node instanceof CustomTableNode; |  | ||||||
| } |  | ||||||
|  | @ -11,14 +11,11 @@ import {ImageNode} from "./image"; | ||||||
| import {DetailsNode, SummaryNode} from "./details"; | import {DetailsNode, SummaryNode} from "./details"; | ||||||
| import {ListItemNode, ListNode} from "@lexical/list"; | import {ListItemNode, ListNode} from "@lexical/list"; | ||||||
| import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; | import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; | ||||||
| import {CustomTableNode} from "./custom-table"; |  | ||||||
| import {HorizontalRuleNode} from "./horizontal-rule"; | import {HorizontalRuleNode} from "./horizontal-rule"; | ||||||
| import {CodeBlockNode} from "./code-block"; | import {CodeBlockNode} from "./code-block"; | ||||||
| import {DiagramNode} from "./diagram"; | import {DiagramNode} from "./diagram"; | ||||||
| import {EditorUiContext} from "../ui/framework/core"; | import {EditorUiContext} from "../ui/framework/core"; | ||||||
| import {MediaNode} from "./media"; | import {MediaNode} from "./media"; | ||||||
| import {CustomTableCellNode} from "./custom-table-cell"; |  | ||||||
| import {CustomTableRowNode} from "./custom-table-row"; |  | ||||||
| import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; | import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; | ||||||
| import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; | import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; | ||||||
| 
 | 
 | ||||||
|  | @ -32,9 +29,9 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | | ||||||
|         QuoteNode, |         QuoteNode, | ||||||
|         ListNode, |         ListNode, | ||||||
|         ListItemNode, |         ListItemNode, | ||||||
|         CustomTableNode, |         TableNode, | ||||||
|         CustomTableRowNode, |         TableRowNode, | ||||||
|         CustomTableCellNode, |         TableCellNode, | ||||||
|         ImageNode, // TODO - Alignment
 |         ImageNode, // TODO - Alignment
 | ||||||
|         HorizontalRuleNode, |         HorizontalRuleNode, | ||||||
|         DetailsNode, SummaryNode, |         DetailsNode, SummaryNode, | ||||||
|  | @ -43,30 +40,6 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | | ||||||
|         MediaNode, // TODO - Alignment
 |         MediaNode, // TODO - Alignment
 | ||||||
|         ParagraphNode, |         ParagraphNode, | ||||||
|         LinkNode, |         LinkNode, | ||||||
|         { |  | ||||||
|             replace: TableNode, |  | ||||||
|             with(node: TableNode) { |  | ||||||
|                 return new CustomTableNode(); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             replace: TableRowNode, |  | ||||||
|             with(node: TableRowNode) { |  | ||||||
|                 return new CustomTableRowNode(); |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             replace: TableCellNode, |  | ||||||
|             with: (node: TableCellNode) => { |  | ||||||
|                 const cell = new CustomTableCellNode( |  | ||||||
|                     node.__headerState, |  | ||||||
|                     node.__colSpan, |  | ||||||
|                     node.__width, |  | ||||||
|                 ); |  | ||||||
|                 cell.__rowSpan = node.__rowSpan; |  | ||||||
|                 return cell; |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     ]; |     ]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,17 +9,15 @@ import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg"; | ||||||
| import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg"; | import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg"; | ||||||
| import {EditorUiContext} from "../../framework/core"; | import {EditorUiContext} from "../../framework/core"; | ||||||
| import {$getSelection, BaseSelection} from "lexical"; | import {$getSelection, BaseSelection} from "lexical"; | ||||||
| import {$isCustomTableNode} from "../../../nodes/custom-table"; |  | ||||||
| import { | import { | ||||||
|     $deleteTableColumn__EXPERIMENTAL, |     $deleteTableColumn__EXPERIMENTAL, | ||||||
|     $deleteTableRow__EXPERIMENTAL, |     $deleteTableRow__EXPERIMENTAL, | ||||||
|     $insertTableColumn__EXPERIMENTAL, |     $insertTableColumn__EXPERIMENTAL, | ||||||
|     $insertTableRow__EXPERIMENTAL, |     $insertTableRow__EXPERIMENTAL, $isTableCellNode, | ||||||
|     $isTableNode, $isTableSelection, $unmergeCell, TableCellNode, |     $isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode, | ||||||
| } from "@lexical/table"; | } from "@lexical/table"; | ||||||
| import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection"; | import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection"; | ||||||
| import {$getParentOfType} from "../../../utils/nodes"; | import {$getParentOfType} from "../../../utils/nodes"; | ||||||
| import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell"; |  | ||||||
| import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables"; | import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables"; | ||||||
| import { | import { | ||||||
|     $clearTableFormatting, |     $clearTableFormatting, | ||||||
|  | @ -27,7 +25,6 @@ import { | ||||||
|     $getTableRowsFromSelection, |     $getTableRowsFromSelection, | ||||||
|     $mergeTableCellsInSelection |     $mergeTableCellsInSelection | ||||||
| } from "../../../utils/tables"; | } from "../../../utils/tables"; | ||||||
| import {$isCustomTableRowNode} from "../../../nodes/custom-table-row"; |  | ||||||
| import { | import { | ||||||
|     $copySelectedColumnsToClipboard, |     $copySelectedColumnsToClipboard, | ||||||
|     $copySelectedRowsToClipboard, |     $copySelectedRowsToClipboard, | ||||||
|  | @ -41,7 +38,7 @@ import { | ||||||
| } from "../../../utils/table-copy-paste"; | } from "../../../utils/table-copy-paste"; | ||||||
| 
 | 
 | ||||||
| const neverActive = (): boolean => false; | const neverActive = (): boolean => false; | ||||||
| const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode); | const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isTableCellNode); | ||||||
| 
 | 
 | ||||||
| export const table: EditorBasicButtonDefinition = { | export const table: EditorBasicButtonDefinition = { | ||||||
|     label: 'Table', |     label: 'Table', | ||||||
|  | @ -54,7 +51,7 @@ export const tableProperties: EditorButtonDefinition = { | ||||||
|     action(context: EditorUiContext) { |     action(context: EditorUiContext) { | ||||||
|         context.editor.getEditorState().read(() => { |         context.editor.getEditorState().read(() => { | ||||||
|             const table = $getTableFromSelection($getSelection()); |             const table = $getTableFromSelection($getSelection()); | ||||||
|             if ($isCustomTableNode(table)) { |             if ($isTableNode(table)) { | ||||||
|                 $showTablePropertiesForm(table, context); |                 $showTablePropertiesForm(table, context); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | @ -68,13 +65,13 @@ export const clearTableFormatting: EditorButtonDefinition = { | ||||||
|     format: 'long', |     format: 'long', | ||||||
|     action(context: EditorUiContext) { |     action(context: EditorUiContext) { | ||||||
|         context.editor.update(() => { |         context.editor.update(() => { | ||||||
|             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); |             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); | ||||||
|             if (!$isCustomTableCellNode(cell)) { |             if (!$isTableCellNode(cell)) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const table = $getParentOfType(cell, $isTableNode); |             const table = $getParentOfType(cell, $isTableNode); | ||||||
|             if ($isCustomTableNode(table)) { |             if ($isTableNode(table)) { | ||||||
|                 $clearTableFormatting(table); |                 $clearTableFormatting(table); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | @ -88,13 +85,13 @@ export const resizeTableToContents: EditorButtonDefinition = { | ||||||
|     format: 'long', |     format: 'long', | ||||||
|     action(context: EditorUiContext) { |     action(context: EditorUiContext) { | ||||||
|         context.editor.update(() => { |         context.editor.update(() => { | ||||||
|             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); |             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); | ||||||
|             if (!$isCustomTableCellNode(cell)) { |             if (!$isTableCellNode(cell)) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const table = $getParentOfType(cell, $isCustomTableNode); |             const table = $getParentOfType(cell, $isTableNode); | ||||||
|             if ($isCustomTableNode(table)) { |             if ($isTableNode(table)) { | ||||||
|                 $clearTableSizes(table); |                 $clearTableSizes(table); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | @ -108,7 +105,7 @@ export const deleteTable: EditorButtonDefinition = { | ||||||
|     icon: deleteIcon, |     icon: deleteIcon, | ||||||
|     action(context: EditorUiContext) { |     action(context: EditorUiContext) { | ||||||
|         context.editor.update(() => { |         context.editor.update(() => { | ||||||
|             const table = $getNodeFromSelection($getSelection(), $isCustomTableNode); |             const table = $getNodeFromSelection($getSelection(), $isTableNode); | ||||||
|             if (table) { |             if (table) { | ||||||
|                 table.remove(); |                 table.remove(); | ||||||
|             } |             } | ||||||
|  | @ -169,7 +166,7 @@ export const rowProperties: EditorButtonDefinition = { | ||||||
|     action(context: EditorUiContext) { |     action(context: EditorUiContext) { | ||||||
|         context.editor.getEditorState().read(() => { |         context.editor.getEditorState().read(() => { | ||||||
|             const rows = $getTableRowsFromSelection($getSelection()); |             const rows = $getTableRowsFromSelection($getSelection()); | ||||||
|             if ($isCustomTableRowNode(rows[0])) { |             if ($isTableRowNode(rows[0])) { | ||||||
|                 $showRowPropertiesForm(rows[0], context); |                 $showRowPropertiesForm(rows[0], context); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | @ -350,8 +347,8 @@ export const cellProperties: EditorButtonDefinition = { | ||||||
|     format: 'long', |     format: 'long', | ||||||
|     action(context: EditorUiContext) { |     action(context: EditorUiContext) { | ||||||
|         context.editor.getEditorState().read(() => { |         context.editor.getEditorState().read(() => { | ||||||
|             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); |             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); | ||||||
|             if ($isCustomTableCellNode(cell)) { |             if ($isTableCellNode(cell)) { | ||||||
|                 $showCellPropertiesForm(cell, context); |                 $showCellPropertiesForm(cell, context); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | @ -387,7 +384,7 @@ export const splitCell: EditorButtonDefinition = { | ||||||
|     }, |     }, | ||||||
|     isActive: neverActive, |     isActive: neverActive, | ||||||
|     isDisabled(selection) { |     isDisabled(selection) { | ||||||
|         const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null; |         const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode|null; | ||||||
|         if (cell) { |         if (cell) { | ||||||
|             const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1; |             const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1; | ||||||
|             return !merged; |             return !merged; | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ import { | ||||||
|     EditorSelectFormFieldDefinition |     EditorSelectFormFieldDefinition | ||||||
| } from "../../framework/forms"; | } from "../../framework/forms"; | ||||||
| import {EditorUiContext} from "../../framework/core"; | import {EditorUiContext} from "../../framework/core"; | ||||||
| import {CustomTableCellNode} from "../../../nodes/custom-table-cell"; |  | ||||||
| import {EditorFormModal} from "../../framework/modals"; | import {EditorFormModal} from "../../framework/modals"; | ||||||
| import {$getSelection, ElementFormatType} from "lexical"; | import {$getSelection, ElementFormatType} from "lexical"; | ||||||
| import { | import { | ||||||
|  | @ -16,8 +15,8 @@ import { | ||||||
|     $setTableCellColumnWidth |     $setTableCellColumnWidth | ||||||
| } from "../../../utils/tables"; | } from "../../../utils/tables"; | ||||||
| import {formatSizeValue} from "../../../utils/dom"; | import {formatSizeValue} from "../../../utils/dom"; | ||||||
| import {CustomTableRowNode} from "../../../nodes/custom-table-row"; | import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; | ||||||
| import {CustomTableNode} from "../../../nodes/custom-table"; | import {CommonBlockAlignment} from "../../../nodes/_common"; | ||||||
| 
 | 
 | ||||||
| const borderStyleInput: EditorSelectFormFieldDefinition = { | const borderStyleInput: EditorSelectFormFieldDefinition = { | ||||||
|     label: 'Border style', |     label: 'Border style', | ||||||
|  | @ -62,14 +61,14 @@ const alignmentInput: EditorSelectFormFieldDefinition = { | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function $showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal { | export function $showCellPropertiesForm(cell: TableCellNode, context: EditorUiContext): EditorFormModal { | ||||||
|     const styles = cell.getStyles(); |     const styles = cell.getStyles(); | ||||||
|     const modalForm = context.manager.createModal('cell_properties'); |     const modalForm = context.manager.createModal('cell_properties'); | ||||||
|     modalForm.show({ |     modalForm.show({ | ||||||
|         width: $getTableCellColumnWidth(context.editor, cell), |         width: $getTableCellColumnWidth(context.editor, cell), | ||||||
|         height: styles.get('height') || '', |         height: styles.get('height') || '', | ||||||
|         type: cell.getTag(), |         type: cell.getTag(), | ||||||
|         h_align: cell.getFormatType(), |         h_align: cell.getAlignment(), | ||||||
|         v_align: styles.get('vertical-align') || '', |         v_align: styles.get('vertical-align') || '', | ||||||
|         border_width: styles.get('border-width') || '', |         border_width: styles.get('border-width') || '', | ||||||
|         border_style: styles.get('border-style') || '', |         border_style: styles.get('border-style') || '', | ||||||
|  | @ -89,7 +88,7 @@ export const cellProperties: EditorFormDefinition = { | ||||||
| 
 | 
 | ||||||
|                 $setTableCellColumnWidth(cell, width); |                 $setTableCellColumnWidth(cell, width); | ||||||
|                 cell.updateTag(formData.get('type')?.toString() || ''); |                 cell.updateTag(formData.get('type')?.toString() || ''); | ||||||
|                 cell.setFormat((formData.get('h_align')?.toString() || '') as ElementFormatType); |                 cell.setAlignment((formData.get('h_align')?.toString() || '') as CommonBlockAlignment); | ||||||
| 
 | 
 | ||||||
|                 const styles = cell.getStyles(); |                 const styles = cell.getStyles(); | ||||||
|                 styles.set('height', formatSizeValue(formData.get('height')?.toString() || '')); |                 styles.set('height', formatSizeValue(formData.get('height')?.toString() || '')); | ||||||
|  | @ -172,7 +171,7 @@ export const cellProperties: EditorFormDefinition = { | ||||||
|     ], |     ], | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function $showRowPropertiesForm(row: CustomTableRowNode, context: EditorUiContext): EditorFormModal { | export function $showRowPropertiesForm(row: TableRowNode, context: EditorUiContext): EditorFormModal { | ||||||
|     const styles = row.getStyles(); |     const styles = row.getStyles(); | ||||||
|     const modalForm = context.manager.createModal('row_properties'); |     const modalForm = context.manager.createModal('row_properties'); | ||||||
|     modalForm.show({ |     modalForm.show({ | ||||||
|  | @ -216,7 +215,7 @@ export const rowProperties: EditorFormDefinition = { | ||||||
|     ], |     ], | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function $showTablePropertiesForm(table: CustomTableNode, context: EditorUiContext): EditorFormModal { | export function $showTablePropertiesForm(table: TableNode, context: EditorUiContext): EditorFormModal { | ||||||
|     const styles = table.getStyles(); |     const styles = table.getStyles(); | ||||||
|     const modalForm = context.manager.createModal('table_properties'); |     const modalForm = context.manager.createModal('table_properties'); | ||||||
|     modalForm.show({ |     modalForm.show({ | ||||||
|  | @ -229,7 +228,7 @@ export function $showTablePropertiesForm(table: CustomTableNode, context: Editor | ||||||
|         border_color: styles.get('border-color') || '', |         border_color: styles.get('border-color') || '', | ||||||
|         background_color: styles.get('background-color') || '', |         background_color: styles.get('background-color') || '', | ||||||
|         // caption: '', TODO
 |         // caption: '', TODO
 | ||||||
|         align: table.getFormatType(), |         align: table.getAlignment(), | ||||||
|     }); |     }); | ||||||
|     return modalForm; |     return modalForm; | ||||||
| } | } | ||||||
|  | @ -253,12 +252,12 @@ export const tableProperties: EditorFormDefinition = { | ||||||
|             styles.set('background-color', formData.get('background_color')?.toString() || ''); |             styles.set('background-color', formData.get('background_color')?.toString() || ''); | ||||||
|             table.setStyles(styles); |             table.setStyles(styles); | ||||||
| 
 | 
 | ||||||
|             table.setFormat(formData.get('align') as ElementFormatType); |             table.setAlignment(formData.get('align') as CommonBlockAlignment); | ||||||
| 
 | 
 | ||||||
|             const cellPadding = (formData.get('cell_padding')?.toString() || ''); |             const cellPadding = (formData.get('cell_padding')?.toString() || ''); | ||||||
|             if (cellPadding) { |             if (cellPadding) { | ||||||
|                 const cellPaddingFormatted = formatSizeValue(cellPadding); |                 const cellPaddingFormatted = formatSizeValue(cellPadding); | ||||||
|                 $forEachTableCell(table, (cell: CustomTableCellNode) => { |                 $forEachTableCell(table, (cell: TableCellNode) => { | ||||||
|                     const styles = cell.getStyles(); |                     const styles = cell.getStyles(); | ||||||
|                     styles.set('padding', cellPaddingFormatted); |                     styles.set('padding', cellPaddingFormatted); | ||||||
|                     cell.setStyles(styles); |                     cell.setStyles(styles); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import {EditorUiElement} from "../core"; | import {EditorUiElement} from "../core"; | ||||||
| import {$createTableNodeWithDimensions} from "@lexical/table"; | import {$createTableNodeWithDimensions} from "@lexical/table"; | ||||||
| import {CustomTableNode} from "../../../nodes/custom-table"; |  | ||||||
| import {$insertNewBlockNodeAtSelection} from "../../../utils/selection"; | import {$insertNewBlockNodeAtSelection} from "../../../utils/selection"; | ||||||
| import {el} from "../../../utils/dom"; | import {el} from "../../../utils/dom"; | ||||||
| 
 | 
 | ||||||
|  | @ -78,7 +77,7 @@ export class EditorTableCreator extends EditorUiElement { | ||||||
|         const colWidths = Array(columns).fill(targetColWidth + 'px'); |         const colWidths = Array(columns).fill(targetColWidth + 'px'); | ||||||
| 
 | 
 | ||||||
|         this.getContext().editor.update(() => { |         this.getContext().editor.update(() => { | ||||||
|             const table = $createTableNodeWithDimensions(rows, columns, false) as CustomTableNode; |             const table = $createTableNodeWithDimensions(rows, columns, false); | ||||||
|             table.setColWidths(colWidths); |             table.setColWidths(colWidths); | ||||||
|             $insertNewBlockNodeAtSelection(table); |             $insertNewBlockNodeAtSelection(table); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical"; | import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical"; | ||||||
| import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker"; | import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker"; | ||||||
| import {CustomTableNode} from "../../../nodes/custom-table"; | import {TableNode, TableRowNode} from "@lexical/table"; | ||||||
| import {TableRowNode} from "@lexical/table"; |  | ||||||
| import {el} from "../../../utils/dom"; | import {el} from "../../../utils/dom"; | ||||||
| import {$getTableColumnWidth, $setTableColumnWidth} from "../../../utils/tables"; | import {$getTableColumnWidth, $setTableColumnWidth} from "../../../utils/tables"; | ||||||
| 
 | 
 | ||||||
|  | @ -148,7 +147,7 @@ class TableResizer { | ||||||
| 
 | 
 | ||||||
|                     _this.editor.update(() => { |                     _this.editor.update(() => { | ||||||
|                         const table = $getNearestNodeFromDOMNode(parentTable); |                         const table = $getNearestNodeFromDOMNode(parentTable); | ||||||
|                         if (table instanceof CustomTableNode) { |                         if (table instanceof TableNode) { | ||||||
|                             const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex); |                             const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex); | ||||||
|                             const newWidth = Math.max(originalWidth + change, 10); |                             const newWidth = Math.max(originalWidth + change, 10); | ||||||
|                             $setTableColumnWidth(table, cellIndex, newWidth); |                             $setTableColumnWidth(table, cellIndex, newWidth); | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import {$getNodeByKey, LexicalEditor} from "lexical"; | import {$getNodeByKey, LexicalEditor} from "lexical"; | ||||||
| import {NodeKey} from "lexical/LexicalNode"; | import {NodeKey} from "lexical/LexicalNode"; | ||||||
| import { | import { | ||||||
|  |     $isTableNode, | ||||||
|     applyTableHandlers, |     applyTableHandlers, | ||||||
|     HTMLTableElementWithWithTableSelectionState, |     HTMLTableElementWithWithTableSelectionState, | ||||||
|     TableNode, |     TableNode, | ||||||
|     TableObserver |     TableObserver | ||||||
| } from "@lexical/table"; | } from "@lexical/table"; | ||||||
| import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table"; |  | ||||||
| 
 | 
 | ||||||
| // File adapted from logic in:
 | // File adapted from logic in:
 | ||||||
| // https://github.com/facebook/lexical/blob/f373759a7849f473d34960a6bf4e34b2a011e762/packages/lexical-react/src/LexicalTablePlugin.ts#L49
 | // https://github.com/facebook/lexical/blob/f373759a7849f473d34960a6bf4e34b2a011e762/packages/lexical-react/src/LexicalTablePlugin.ts#L49
 | ||||||
|  | @ -25,12 +25,12 @@ class TableSelectionHandler { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected init() { |     protected init() { | ||||||
|         this.unregisterMutationListener = this.editor.registerMutationListener(CustomTableNode, (mutations) => { |         this.unregisterMutationListener = this.editor.registerMutationListener(TableNode, (mutations) => { | ||||||
|             for (const [nodeKey, mutation] of mutations) { |             for (const [nodeKey, mutation] of mutations) { | ||||||
|                 if (mutation === 'created') { |                 if (mutation === 'created') { | ||||||
|                     this.editor.getEditorState().read(() => { |                     this.editor.getEditorState().read(() => { | ||||||
|                         const tableNode = $getNodeByKey<CustomTableNode>(nodeKey); |                         const tableNode = $getNodeByKey<TableNode>(nodeKey); | ||||||
|                         if ($isCustomTableNode(tableNode)) { |                         if ($isTableNode(tableNode)) { | ||||||
|                             this.initializeTableNode(tableNode); |                             this.initializeTableNode(tableNode); | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|  | @ -1,24 +1,28 @@ | ||||||
| import {NodeClipboard} from "./node-clipboard"; | import {NodeClipboard} from "./node-clipboard"; | ||||||
| import {CustomTableRowNode} from "../nodes/custom-table-row"; |  | ||||||
| import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables"; | import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables"; | ||||||
| import {$getSelection, BaseSelection, LexicalEditor} from "lexical"; | import {$getSelection, BaseSelection, LexicalEditor} from "lexical"; | ||||||
| import {$createCustomTableCellNode, $isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; |  | ||||||
| import {CustomTableNode} from "../nodes/custom-table"; |  | ||||||
| import {TableMap} from "./table-map"; | import {TableMap} from "./table-map"; | ||||||
| import {$isTableSelection} from "@lexical/table"; | import { | ||||||
|  |     $createTableCellNode, | ||||||
|  |     $isTableCellNode, | ||||||
|  |     $isTableSelection, | ||||||
|  |     TableCellNode, | ||||||
|  |     TableNode, | ||||||
|  |     TableRowNode | ||||||
|  | } from "@lexical/table"; | ||||||
| import {$getNodeFromSelection} from "./selection"; | import {$getNodeFromSelection} from "./selection"; | ||||||
| 
 | 
 | ||||||
| const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>(); | const rowClipboard: NodeClipboard<TableRowNode> = new NodeClipboard<TableRowNode>(); | ||||||
| 
 | 
 | ||||||
| export function isRowClipboardEmpty(): boolean { | export function isRowClipboardEmpty(): boolean { | ||||||
|     return rowClipboard.size() === 0; |     return rowClipboard.size() === 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function validateRowsToCopy(rows: CustomTableRowNode[]): void { | export function validateRowsToCopy(rows: TableRowNode[]): void { | ||||||
|     let commonRowSize: number|null = null; |     let commonRowSize: number|null = null; | ||||||
| 
 | 
 | ||||||
|     for (const row of rows) { |     for (const row of rows) { | ||||||
|         const cells = row.getChildren().filter(n => $isCustomTableCellNode(n)); |         const cells = row.getChildren().filter(n => $isTableCellNode(n)); | ||||||
|         let rowSize = 0; |         let rowSize = 0; | ||||||
|         for (const cell of cells) { |         for (const cell of cells) { | ||||||
|             rowSize += cell.getColSpan() || 1; |             rowSize += cell.getColSpan() || 1; | ||||||
|  | @ -35,10 +39,10 @@ export function validateRowsToCopy(rows: CustomTableRowNode[]): void { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: CustomTableNode): void { | export function validateRowsToPaste(rows: TableRowNode[], targetTable: TableNode): void { | ||||||
|     const tableColCount = (new TableMap(targetTable)).columnCount; |     const tableColCount = (new TableMap(targetTable)).columnCount; | ||||||
|     for (const row of rows) { |     for (const row of rows) { | ||||||
|         const cells = row.getChildren().filter(n => $isCustomTableCellNode(n)); |         const cells = row.getChildren().filter(n => $isTableCellNode(n)); | ||||||
|         let rowSize = 0; |         let rowSize = 0; | ||||||
|         for (const cell of cells) { |         for (const cell of cells) { | ||||||
|             rowSize += cell.getColSpan() || 1; |             rowSize += cell.getColSpan() || 1; | ||||||
|  | @ -49,7 +53,7 @@ export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: Cus | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         while (rowSize < tableColCount) { |         while (rowSize < tableColCount) { | ||||||
|             row.append($createCustomTableCellNode()); |             row.append($createTableCellNode()); | ||||||
|             rowSize++; |             rowSize++; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -98,11 +102,11 @@ export function $pasteClipboardRowsAfter(editor: LexicalEditor): void { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const columnClipboard: NodeClipboard<CustomTableCellNode>[] = []; | const columnClipboard: NodeClipboard<TableCellNode>[] = []; | ||||||
| 
 | 
 | ||||||
| function setColumnClipboard(columns: CustomTableCellNode[][]): void { | function setColumnClipboard(columns: TableCellNode[][]): void { | ||||||
|     const newClipboards = columns.map(cells => { |     const newClipboards = columns.map(cells => { | ||||||
|         const clipboard = new NodeClipboard<CustomTableCellNode>(); |         const clipboard = new NodeClipboard<TableCellNode>(); | ||||||
|         clipboard.set(...cells); |         clipboard.set(...cells); | ||||||
|         return clipboard; |         return clipboard; | ||||||
|     }); |     }); | ||||||
|  | @ -122,9 +126,9 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul | ||||||
|         return {from: shape.fromX, to: shape.toX}; |         return {from: shape.fromX, to: shape.toX}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const cell = $getNodeFromSelection(selection, $isCustomTableCellNode); |     const cell = $getNodeFromSelection(selection, $isTableCellNode); | ||||||
|     const table = $getTableFromSelection(selection); |     const table = $getTableFromSelection(selection); | ||||||
|     if (!$isCustomTableCellNode(cell) || !table) { |     if (!$isTableCellNode(cell) || !table) { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -137,7 +141,7 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul | ||||||
|     return {from: range.fromX, to: range.toX}; |     return {from: range.fromX, to: range.toX}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTableNode): CustomTableCellNode[][] { | function $getTableColumnCellsFromSelection(range: TableRange, table: TableNode): TableCellNode[][] { | ||||||
|     const map = new TableMap(table); |     const map = new TableMap(table); | ||||||
|     const columns = []; |     const columns = []; | ||||||
|     for (let x = range.from; x <= range.to; x++) { |     for (let x = range.from; x <= range.to; x++) { | ||||||
|  | @ -148,7 +152,7 @@ function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTable | ||||||
|     return columns; |     return columns; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function validateColumnsToCopy(columns: CustomTableCellNode[][]): void { | function validateColumnsToCopy(columns: TableCellNode[][]): void { | ||||||
|     let commonColSize: number|null = null; |     let commonColSize: number|null = null; | ||||||
| 
 | 
 | ||||||
|     for (const cells of columns) { |     for (const cells of columns) { | ||||||
|  | @ -203,7 +207,7 @@ export function $copySelectedColumnsToClipboard(): void { | ||||||
|     setColumnClipboard(columns); |     setColumnClipboard(columns); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: CustomTableNode) { | function validateColumnsToPaste(columns: TableCellNode[][], targetTable: TableNode) { | ||||||
|     const tableRowCount = (new TableMap(targetTable)).rowCount; |     const tableRowCount = (new TableMap(targetTable)).rowCount; | ||||||
|     for (const cells of columns) { |     for (const cells of columns) { | ||||||
|         let colSize = 0; |         let colSize = 0; | ||||||
|  | @ -216,7 +220,7 @@ function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: C | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         while (colSize < tableRowCount) { |         while (colSize < tableRowCount) { | ||||||
|             cells.push($createCustomTableCellNode()); |             cells.push($createTableCellNode()); | ||||||
|             colSize++; |             colSize++; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| import {CustomTableNode} from "../nodes/custom-table"; | import {$isTableCellNode, $isTableRowNode, TableCellNode, TableNode} from "@lexical/table"; | ||||||
| import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; |  | ||||||
| import {$isTableRowNode} from "@lexical/table"; |  | ||||||
| 
 | 
 | ||||||
| export type CellRange = { | export type CellRange = { | ||||||
|     fromX: number; |     fromX: number; | ||||||
|  | @ -16,15 +14,15 @@ export class TableMap { | ||||||
| 
 | 
 | ||||||
|     // Represents an array (rows*columns in length) of cell nodes from top-left to
 |     // Represents an array (rows*columns in length) of cell nodes from top-left to
 | ||||||
|     // bottom right. Cells may repeat where merged and covering multiple spaces.
 |     // bottom right. Cells may repeat where merged and covering multiple spaces.
 | ||||||
|     cells: CustomTableCellNode[] = []; |     cells: TableCellNode[] = []; | ||||||
| 
 | 
 | ||||||
|     constructor(table: CustomTableNode) { |     constructor(table: TableNode) { | ||||||
|         this.buildCellMap(table); |         this.buildCellMap(table); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected buildCellMap(table: CustomTableNode) { |     protected buildCellMap(table: TableNode) { | ||||||
|         const rowsAndCells: CustomTableCellNode[][] = []; |         const rowsAndCells: TableCellNode[][] = []; | ||||||
|         const setCell = (x: number, y: number, cell: CustomTableCellNode) => { |         const setCell = (x: number, y: number, cell: TableCellNode) => { | ||||||
|             if (typeof rowsAndCells[y] === 'undefined') { |             if (typeof rowsAndCells[y] === 'undefined') { | ||||||
|                 rowsAndCells[y] = []; |                 rowsAndCells[y] = []; | ||||||
|             } |             } | ||||||
|  | @ -36,7 +34,7 @@ export class TableMap { | ||||||
|         const rowNodes = table.getChildren().filter(r => $isTableRowNode(r)); |         const rowNodes = table.getChildren().filter(r => $isTableRowNode(r)); | ||||||
|         for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) { |         for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) { | ||||||
|             const rowNode = rowNodes[rowIndex]; |             const rowNode = rowNodes[rowIndex]; | ||||||
|             const cellNodes = rowNode.getChildren().filter(c => $isCustomTableCellNode(c)); |             const cellNodes = rowNode.getChildren().filter(c => $isTableCellNode(c)); | ||||||
|             let targetColIndex: number = 0; |             let targetColIndex: number = 0; | ||||||
|             for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) { |             for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) { | ||||||
|                 const cellNode = cellNodes[cellIndex]; |                 const cellNode = cellNodes[cellIndex]; | ||||||
|  | @ -60,7 +58,7 @@ export class TableMap { | ||||||
|         this.columnCount = Math.max(...rowsAndCells.map(r => r.length)); |         this.columnCount = Math.max(...rowsAndCells.map(r => r.length)); | ||||||
| 
 | 
 | ||||||
|         const cells = []; |         const cells = []; | ||||||
|         let lastCell: CustomTableCellNode = rowsAndCells[0][0]; |         let lastCell: TableCellNode = rowsAndCells[0][0]; | ||||||
|         for (let y = 0; y < this.rowCount; y++) { |         for (let y = 0; y < this.rowCount; y++) { | ||||||
|             for (let x = 0; x < this.columnCount; x++) { |             for (let x = 0; x < this.columnCount; x++) { | ||||||
|                 if (!rowsAndCells[y] || !rowsAndCells[y][x]) { |                 if (!rowsAndCells[y] || !rowsAndCells[y][x]) { | ||||||
|  | @ -75,7 +73,7 @@ export class TableMap { | ||||||
|         this.cells = cells; |         this.cells = cells; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public getCellAtPosition(x: number, y: number): CustomTableCellNode { |     public getCellAtPosition(x: number, y: number): TableCellNode { | ||||||
|         const position = (y * this.columnCount) + x; |         const position = (y * this.columnCount) + x; | ||||||
|         if (position >= this.cells.length) { |         if (position >= this.cells.length) { | ||||||
|             throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`); |             throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`); | ||||||
|  | @ -84,13 +82,13 @@ export class TableMap { | ||||||
|         return this.cells[position]; |         return this.cells[position]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public getCellsInRange(range: CellRange): CustomTableCellNode[] { |     public getCellsInRange(range: CellRange): TableCellNode[] { | ||||||
|         const minX = Math.max(Math.min(range.fromX, range.toX), 0); |         const minX = Math.max(Math.min(range.fromX, range.toX), 0); | ||||||
|         const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1); |         const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1); | ||||||
|         const minY = Math.max(Math.min(range.fromY, range.toY), 0); |         const minY = Math.max(Math.min(range.fromY, range.toY), 0); | ||||||
|         const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1); |         const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1); | ||||||
| 
 | 
 | ||||||
|         const cells = new Set<CustomTableCellNode>(); |         const cells = new Set<TableCellNode>(); | ||||||
| 
 | 
 | ||||||
|         for (let y = minY; y <= maxY; y++) { |         for (let y = minY; y <= maxY; y++) { | ||||||
|             for (let x = minX; x <= maxX; x++) { |             for (let x = minX; x <= maxX; x++) { | ||||||
|  | @ -101,7 +99,7 @@ export class TableMap { | ||||||
|         return [...cells.values()]; |         return [...cells.values()]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public getCellsInColumn(columnIndex: number): CustomTableCellNode[] { |     public getCellsInColumn(columnIndex: number): TableCellNode[] { | ||||||
|         return this.getCellsInRange({ |         return this.getCellsInRange({ | ||||||
|             fromX: columnIndex, |             fromX: columnIndex, | ||||||
|             toX: columnIndex, |             toX: columnIndex, | ||||||
|  | @ -110,7 +108,7 @@ export class TableMap { | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public getRangeForCell(cell: CustomTableCellNode): CellRange|null { |     public getRangeForCell(cell: TableCellNode): CellRange|null { | ||||||
|         let range: CellRange|null = null; |         let range: CellRange|null = null; | ||||||
|         const cellKey = cell.getKey(); |         const cellKey = cell.getKey(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,15 +1,19 @@ | ||||||
| import {BaseSelection, LexicalEditor} from "lexical"; | import {BaseSelection, LexicalEditor} from "lexical"; | ||||||
| import {$isTableRowNode, $isTableSelection, TableRowNode, TableSelection, TableSelectionShape} from "@lexical/table"; | import { | ||||||
| import {$isCustomTableNode, CustomTableNode} from "../nodes/custom-table"; |     $isTableCellNode, | ||||||
| import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; |     $isTableNode, | ||||||
|  |     $isTableRowNode, | ||||||
|  |     $isTableSelection, TableCellNode, TableNode, | ||||||
|  |     TableRowNode, | ||||||
|  |     TableSelection, | ||||||
|  | } from "@lexical/table"; | ||||||
| import {$getParentOfType} from "./nodes"; | import {$getParentOfType} from "./nodes"; | ||||||
| import {$getNodeFromSelection} from "./selection"; | import {$getNodeFromSelection} from "./selection"; | ||||||
| import {formatSizeValue} from "./dom"; | import {formatSizeValue} from "./dom"; | ||||||
| import {TableMap} from "./table-map"; | import {TableMap} from "./table-map"; | ||||||
| import {$isCustomTableRowNode, CustomTableRowNode} from "../nodes/custom-table-row"; |  | ||||||
| 
 | 
 | ||||||
| function $getTableFromCell(cell: CustomTableCellNode): CustomTableNode|null { | function $getTableFromCell(cell: TableCellNode): TableNode|null { | ||||||
|     return $getParentOfType(cell, $isCustomTableNode) as CustomTableNode|null; |     return $getParentOfType(cell, $isTableNode) as TableNode|null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getTableColumnWidths(table: HTMLTableElement): string[] { | export function getTableColumnWidths(table: HTMLTableElement): string[] { | ||||||
|  | @ -55,7 +59,7 @@ function extractWidthFromElement(element: HTMLElement): string { | ||||||
|     return width || ''; |     return width || ''; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number, width: number|string): void { | export function $setTableColumnWidth(node: TableNode, columnIndex: number, width: number|string): void { | ||||||
|     const rows = node.getChildren() as TableRowNode[]; |     const rows = node.getChildren() as TableRowNode[]; | ||||||
|     let maxCols = 0; |     let maxCols = 0; | ||||||
|     for (const row of rows) { |     for (const row of rows) { | ||||||
|  | @ -78,7 +82,7 @@ export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number, | ||||||
|     node.setColWidths(colWidths); |     node.setColWidths(colWidths); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNode, columnIndex: number): number { | export function $getTableColumnWidth(editor: LexicalEditor, node: TableNode, columnIndex: number): number { | ||||||
|     const colWidths = node.getColWidths(); |     const colWidths = node.getColWidths(); | ||||||
|     if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) { |     if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) { | ||||||
|         return Number(colWidths[columnIndex].replace('px', '')); |         return Number(colWidths[columnIndex].replace('px', '')); | ||||||
|  | @ -97,14 +101,14 @@ export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNod | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function $getCellColumnIndex(node: CustomTableCellNode): number { | function $getCellColumnIndex(node: TableCellNode): number { | ||||||
|     const row = node.getParent(); |     const row = node.getParent(); | ||||||
|     if (!$isTableRowNode(row)) { |     if (!$isTableRowNode(row)) { | ||||||
|         return -1; |         return -1; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let index = 0; |     let index = 0; | ||||||
|     const cells = row.getChildren<CustomTableCellNode>(); |     const cells = row.getChildren<TableCellNode>(); | ||||||
|     for (const cell of cells) { |     for (const cell of cells) { | ||||||
|         let colSpan = cell.getColSpan() || 1; |         let colSpan = cell.getColSpan() || 1; | ||||||
|         index += colSpan; |         index += colSpan; | ||||||
|  | @ -116,7 +120,7 @@ function $getCellColumnIndex(node: CustomTableCellNode): number { | ||||||
|     return index - 1; |     return index - 1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: string): void { | export function $setTableCellColumnWidth(cell: TableCellNode, width: string): void { | ||||||
|     const table = $getTableFromCell(cell) |     const table = $getTableFromCell(cell) | ||||||
|     const index = $getCellColumnIndex(cell); |     const index = $getCellColumnIndex(cell); | ||||||
| 
 | 
 | ||||||
|  | @ -125,7 +129,7 @@ export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: strin | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTableCellNode): string { | export function $getTableCellColumnWidth(editor: LexicalEditor, cell: TableCellNode): string { | ||||||
|     const table = $getTableFromCell(cell) |     const table = $getTableFromCell(cell) | ||||||
|     const index = $getCellColumnIndex(cell); |     const index = $getCellColumnIndex(cell); | ||||||
|     if (!table) { |     if (!table) { | ||||||
|  | @ -136,13 +140,13 @@ export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTabl | ||||||
|     return (widths.length > index) ? widths[index] : ''; |     return (widths.length > index) ? widths[index] : ''; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $getTableCellsFromSelection(selection: BaseSelection|null): CustomTableCellNode[]  { | export function $getTableCellsFromSelection(selection: BaseSelection|null): TableCellNode[]  { | ||||||
|     if ($isTableSelection(selection)) { |     if ($isTableSelection(selection)) { | ||||||
|         const nodes = selection.getNodes(); |         const nodes = selection.getNodes(); | ||||||
|         return nodes.filter(n => $isCustomTableCellNode(n)); |         return nodes.filter(n => $isTableCellNode(n)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as CustomTableCellNode; |     const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode; | ||||||
|     return cell ? [cell] : []; |     return cell ? [cell] : []; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -193,12 +197,12 @@ export function $mergeTableCellsInSelection(selection: TableSelection): void { | ||||||
|     firstCell.setRowSpan(newHeight); |     firstCell.setRowSpan(newHeight); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $getTableRowsFromSelection(selection: BaseSelection|null): CustomTableRowNode[] { | export function $getTableRowsFromSelection(selection: BaseSelection|null): TableRowNode[] { | ||||||
|     const cells = $getTableCellsFromSelection(selection); |     const cells = $getTableCellsFromSelection(selection); | ||||||
|     const rowsByKey: Record<string, CustomTableRowNode> = {}; |     const rowsByKey: Record<string, TableRowNode> = {}; | ||||||
|     for (const cell of cells) { |     for (const cell of cells) { | ||||||
|         const row = cell.getParent(); |         const row = cell.getParent(); | ||||||
|         if ($isCustomTableRowNode(row)) { |         if ($isTableRowNode(row)) { | ||||||
|             rowsByKey[row.getKey()] = row; |             rowsByKey[row.getKey()] = row; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -206,28 +210,28 @@ export function $getTableRowsFromSelection(selection: BaseSelection|null): Custo | ||||||
|     return Object.values(rowsByKey); |     return Object.values(rowsByKey); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $getTableFromSelection(selection: BaseSelection|null): CustomTableNode|null { | export function $getTableFromSelection(selection: BaseSelection|null): TableNode|null { | ||||||
|     const cells = $getTableCellsFromSelection(selection); |     const cells = $getTableCellsFromSelection(selection); | ||||||
|     if (cells.length === 0) { |     if (cells.length === 0) { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const table = $getParentOfType(cells[0], $isCustomTableNode); |     const table = $getParentOfType(cells[0], $isTableNode); | ||||||
|     if ($isCustomTableNode(table)) { |     if ($isTableNode(table)) { | ||||||
|         return table; |         return table; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return null; |     return null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $clearTableSizes(table: CustomTableNode): void { | export function $clearTableSizes(table: TableNode): void { | ||||||
|     table.setColWidths([]); |     table.setColWidths([]); | ||||||
| 
 | 
 | ||||||
|     // TODO - Extra form things once table properties and extra things
 |     // TODO - Extra form things once table properties and extra things
 | ||||||
|     //   are supported
 |     //   are supported
 | ||||||
| 
 | 
 | ||||||
|     for (const row of table.getChildren()) { |     for (const row of table.getChildren()) { | ||||||
|         if (!$isCustomTableRowNode(row)) { |         if (!$isTableRowNode(row)) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -236,7 +240,7 @@ export function $clearTableSizes(table: CustomTableNode): void { | ||||||
|         rowStyles.delete('width'); |         rowStyles.delete('width'); | ||||||
|         row.setStyles(rowStyles); |         row.setStyles(rowStyles); | ||||||
| 
 | 
 | ||||||
|         const cells = row.getChildren().filter(c => $isCustomTableCellNode(c)); |         const cells = row.getChildren().filter(c => $isTableCellNode(c)); | ||||||
|         for (const cell of cells) { |         for (const cell of cells) { | ||||||
|             const cellStyles = cell.getStyles(); |             const cellStyles = cell.getStyles(); | ||||||
|             cellStyles.delete('height'); |             cellStyles.delete('height'); | ||||||
|  | @ -247,23 +251,21 @@ export function $clearTableSizes(table: CustomTableNode): void { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $clearTableFormatting(table: CustomTableNode): void { | export function $clearTableFormatting(table: TableNode): void { | ||||||
|     table.setColWidths([]); |     table.setColWidths([]); | ||||||
|     table.setStyles(new Map); |     table.setStyles(new Map); | ||||||
| 
 | 
 | ||||||
|     for (const row of table.getChildren()) { |     for (const row of table.getChildren()) { | ||||||
|         if (!$isCustomTableRowNode(row)) { |         if (!$isTableRowNode(row)) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         row.setStyles(new Map); |         row.setStyles(new Map); | ||||||
|         row.setFormat(''); |  | ||||||
| 
 | 
 | ||||||
|         const cells = row.getChildren().filter(c => $isCustomTableCellNode(c)); |         const cells = row.getChildren().filter(c => $isTableCellNode(c)); | ||||||
|         for (const cell of cells) { |         for (const cell of cells) { | ||||||
|             cell.setStyles(new Map); |             cell.setStyles(new Map); | ||||||
|             cell.clearWidth(); |             cell.clearWidth(); | ||||||
|             cell.setFormat(''); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -272,14 +274,14 @@ export function $clearTableFormatting(table: CustomTableNode): void { | ||||||
|  * Perform the given callback for each cell in the given table. |  * Perform the given callback for each cell in the given table. | ||||||
|  * Returning false from the callback stops the function early. |  * Returning false from the callback stops the function early. | ||||||
|  */ |  */ | ||||||
| export function $forEachTableCell(table: CustomTableNode, callback: (c: CustomTableCellNode) => void|false): void { | export function $forEachTableCell(table: TableNode, callback: (c: TableCellNode) => void|false): void { | ||||||
|     outer: for (const row of table.getChildren()) { |     outer: for (const row of table.getChildren()) { | ||||||
|         if (!$isCustomTableRowNode(row)) { |         if (!$isTableRowNode(row)) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|         const cells = row.getChildren(); |         const cells = row.getChildren(); | ||||||
|         for (const cell of cells) { |         for (const cell of cells) { | ||||||
|             if (!$isCustomTableCellNode(cell)) { |             if (!$isTableCellNode(cell)) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             const result = callback(cell); |             const result = callback(cell); | ||||||
|  | @ -290,10 +292,10 @@ export function $forEachTableCell(table: CustomTableNode, callback: (c: CustomTa | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function $getCellPaddingForTable(table: CustomTableNode): string { | export function $getCellPaddingForTable(table: TableNode): string { | ||||||
|     let padding: string|null = null; |     let padding: string|null = null; | ||||||
| 
 | 
 | ||||||
|     $forEachTableCell(table, (cell: CustomTableCellNode) => { |     $forEachTableCell(table, (cell: TableCellNode) => { | ||||||
|         const cellPadding = cell.getStyles().get('padding') || '' |         const cellPadding = cell.getStyles().get('padding') || '' | ||||||
|         if (padding === null) { |         if (padding === null) { | ||||||
|             padding = cellPadding; |             padding = cellPadding; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue