Lexical: Merged custom table node code
This commit is contained in:
		
							parent
							
								
									ebd4604f21
								
							
						
					
					
						commit
						57d8449660
					
				|  | @ -28,7 +28,8 @@ import { | |||
|   ElementNode, | ||||
| } 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 = { | ||||
|   BOTH: 3, | ||||
|  | @ -47,6 +48,8 @@ export type SerializedTableCellNode = Spread< | |||
|     headerState: TableCellHeaderState; | ||||
|     width?: number; | ||||
|     backgroundColor?: null | string; | ||||
|     styles: Record<string, string>; | ||||
|     alignment: CommonBlockAlignment; | ||||
|   }, | ||||
|   SerializedElementNode | ||||
| >; | ||||
|  | @ -63,6 +66,10 @@ export class TableCellNode extends ElementNode { | |||
|   __width?: number; | ||||
|   /** @internal */ | ||||
|   __backgroundColor: null | string; | ||||
|   /** @internal */ | ||||
|   __styles: StyleMap = new Map; | ||||
|   /** @internal */ | ||||
|   __alignment: CommonBlockAlignment = ''; | ||||
| 
 | ||||
|   static getType(): string { | ||||
|     return 'tablecell'; | ||||
|  | @ -77,6 +84,8 @@ export class TableCellNode extends ElementNode { | |||
|     ); | ||||
|     cellNode.__rowSpan = node.__rowSpan; | ||||
|     cellNode.__backgroundColor = node.__backgroundColor; | ||||
|     cellNode.__styles = new Map(node.__styles); | ||||
|     cellNode.__alignment = node.__alignment; | ||||
|     return cellNode; | ||||
|   } | ||||
| 
 | ||||
|  | @ -94,16 +103,20 @@ export class TableCellNode extends ElementNode { | |||
|   } | ||||
| 
 | ||||
|   static importJSON(serializedNode: SerializedTableCellNode): TableCellNode { | ||||
|     const colSpan = serializedNode.colSpan || 1; | ||||
|     const rowSpan = serializedNode.rowSpan || 1; | ||||
|     const cellNode = $createTableCellNode( | ||||
|       serializedNode.headerState, | ||||
|       colSpan, | ||||
|       serializedNode.width || undefined, | ||||
|     const node = $createTableCellNode( | ||||
|         serializedNode.headerState, | ||||
|         serializedNode.colSpan, | ||||
|         serializedNode.width, | ||||
|     ); | ||||
|     cellNode.__rowSpan = rowSpan; | ||||
|     cellNode.__backgroundColor = serializedNode.backgroundColor || null; | ||||
|     return cellNode; | ||||
| 
 | ||||
|     if (serializedNode.rowSpan) { | ||||
|         node.setRowSpan(serializedNode.rowSpan); | ||||
|     } | ||||
| 
 | ||||
|     node.setStyles(new Map(Object.entries(serializedNode.styles))); | ||||
|     node.setAlignment(serializedNode.alignment); | ||||
| 
 | ||||
|     return node; | ||||
|   } | ||||
| 
 | ||||
|   constructor( | ||||
|  | @ -144,34 +157,19 @@ export class TableCellNode extends ElementNode { | |||
|       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; | ||||
|   } | ||||
| 
 | ||||
|   exportDOM(editor: LexicalEditor): DOMExportOutput { | ||||
|     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 { | ||||
|       element, | ||||
|     }; | ||||
|  | @ -186,6 +184,8 @@ export class TableCellNode extends ElementNode { | |||
|       rowSpan: this.__rowSpan, | ||||
|       type: 'tablecell', | ||||
|       width: this.getWidth(), | ||||
|       styles: Object.fromEntries(this.__styles), | ||||
|       alignment: this.__alignment, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|  | @ -231,6 +231,38 @@ export class TableCellNode extends ElementNode { | |||
|     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 { | ||||
|     return this.getLatest().__backgroundColor; | ||||
|   } | ||||
|  | @ -265,7 +297,9 @@ export class TableCellNode extends ElementNode { | |||
|       prevNode.__width !== this.__width || | ||||
|       prevNode.__colSpan !== this.__colSpan || | ||||
|       prevNode.__rowSpan !== this.__rowSpan || | ||||
|       prevNode.__backgroundColor !== this.__backgroundColor | ||||
|       prevNode.__backgroundColor !== this.__backgroundColor || | ||||
|       prevNode.__styles !== this.__styles || | ||||
|       prevNode.__alignment !== this.__alignment | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | @ -287,38 +321,42 @@ export class TableCellNode extends ElementNode { | |||
| } | ||||
| 
 | ||||
| export function $convertTableCellNodeElement( | ||||
|   domNode: Node, | ||||
|     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, | ||||
|       nodeName === 'th' | ||||
|           ? TableCellHeaderStates.ROW | ||||
|           : TableCellHeaderStates.NO_STATUS, | ||||
|       domNode_.colSpan, | ||||
|       width, | ||||
|   ); | ||||
| 
 | ||||
|   tableCellNode.__rowSpan = domNode_.rowSpan; | ||||
|   const backgroundColor = domNode_.style.backgroundColor; | ||||
|   if (backgroundColor !== '') { | ||||
|     tableCellNode.__backgroundColor = backgroundColor; | ||||
|   } | ||||
| 
 | ||||
|   const style = domNode_.style; | ||||
|   const textDecoration = style.textDecoration.split(' '); | ||||
|   const hasBoldFontWeight = | ||||
|     style.fontWeight === '700' || style.fontWeight === 'bold'; | ||||
|       style.fontWeight === '700' || style.fontWeight === 'bold'; | ||||
|   const hasLinethroughTextDecoration = textDecoration.includes('line-through'); | ||||
|   const hasItalicFontStyle = style.fontStyle === 'italic'; | ||||
|   const hasUnderlineTextDecoration = textDecoration.includes('underline'); | ||||
| 
 | ||||
|   if (domNode instanceof HTMLElement) { | ||||
|     tableCellNode.setStyles(extractStyleMapFromElement(domNode)); | ||||
|     tableCellNode.setAlignment(extractAlignmentFromElement(domNode)); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     after: (childLexicalNodes) => { | ||||
|       if (childLexicalNodes.length === 0) { | ||||
|  | @ -330,8 +368,8 @@ export function $convertTableCellNodeElement( | |||
|       if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) { | ||||
|         const paragraphNode = $createParagraphNode(); | ||||
|         if ( | ||||
|           $isLineBreakNode(lexicalNode) && | ||||
|           lexicalNode.getTextContent() === '\n' | ||||
|             $isLineBreakNode(lexicalNode) && | ||||
|             lexicalNode.getTextContent() === '\n' | ||||
|         ) { | ||||
|           return null; | ||||
|         } | ||||
|  | @ -360,7 +398,7 @@ export function $convertTableCellNodeElement( | |||
| } | ||||
| 
 | ||||
| export function $createTableCellNode( | ||||
|   headerState: TableCellHeaderState, | ||||
|   headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS, | ||||
|   colSpan = 1, | ||||
|   width?: number, | ||||
| ): TableCellNode { | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
|  */ | ||||
| 
 | ||||
| import type {TableCellNode} from './LexicalTableCellNode'; | ||||
| import type { | ||||
| import { | ||||
|   DOMConversionMap, | ||||
|   DOMConversionOutput, | ||||
|   DOMExportOutput, | ||||
|  | @ -15,7 +15,7 @@ import type { | |||
|   LexicalEditor, | ||||
|   LexicalNode, | ||||
|   NodeKey, | ||||
|   SerializedElementNode, | ||||
|   SerializedElementNode, Spread, | ||||
| } from 'lexical'; | ||||
| 
 | ||||
| import {addClassNamesToElement, isHTMLElement} from '@lexical/utils'; | ||||
|  | @ -27,19 +27,36 @@ import { | |||
| 
 | ||||
| import {$isTableCellNode} from './LexicalTableCellNode'; | ||||
| import {TableDOMCell, TableDOMTable} from './LexicalTableObserver'; | ||||
| import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode'; | ||||
| 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 */ | ||||
| export class TableNode extends ElementNode { | ||||
| export class TableNode extends CommonBlockNode { | ||||
|   __colWidths: string[] = []; | ||||
|   __styles: StyleMap = new Map; | ||||
| 
 | ||||
|   static getType(): string { | ||||
|     return 'table'; | ||||
|   } | ||||
| 
 | ||||
|   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 { | ||||
|  | @ -52,18 +69,24 @@ export class TableNode extends ElementNode { | |||
|   } | ||||
| 
 | ||||
|   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) { | ||||
|     super(key); | ||||
|   } | ||||
| 
 | ||||
|   exportJSON(): SerializedElementNode { | ||||
|   exportJSON(): SerializedTableNode { | ||||
|     return { | ||||
|       ...super.exportJSON(), | ||||
|       type: 'table', | ||||
|       version: 1, | ||||
|       colWidths: this.__colWidths, | ||||
|       styles: Object.fromEntries(this.__styles), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|  | @ -72,11 +95,33 @@ export class TableNode extends ElementNode { | |||
| 
 | ||||
|     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; | ||||
|   } | ||||
| 
 | ||||
|   updateDOM(): boolean { | ||||
|     return false; | ||||
|   updateDOM(_prevNode: TableNode): boolean { | ||||
|     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 { | ||||
|  | @ -115,6 +160,26 @@ export class TableNode extends ElementNode { | |||
|     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( | ||||
|     tableCellNode: TableCellNode, | ||||
|     table: TableDOMTable, | ||||
|  | @ -239,8 +304,15 @@ export function $getElementForTableNode( | |||
|   return getTable(tableElement); | ||||
| } | ||||
| 
 | ||||
| export function $convertTableElement(_domNode: Node): DOMConversionOutput { | ||||
|   return {node: $createTableNode()}; | ||||
| export function $convertTableElement(element: HTMLElement): DOMConversionOutput { | ||||
|   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 { | ||||
|  |  | |||
|  | @ -20,11 +20,12 @@ import { | |||
|   SerializedElementNode, | ||||
| } from 'lexical'; | ||||
| 
 | ||||
| import {PIXEL_VALUE_REG_EXP} from './constants'; | ||||
| import {extractStyleMapFromElement, sizeToPixels, StyleMap} from "../../utils/dom"; | ||||
| 
 | ||||
| export type SerializedTableRowNode = Spread< | ||||
|   { | ||||
|     height?: number; | ||||
|     styles: Record<string, string>, | ||||
|     height?: number, | ||||
|   }, | ||||
|   SerializedElementNode | ||||
| >; | ||||
|  | @ -33,13 +34,17 @@ export type SerializedTableRowNode = Spread< | |||
| export class TableRowNode extends ElementNode { | ||||
|   /** @internal */ | ||||
|   __height?: number; | ||||
|   /** @internal */ | ||||
|   __styles: StyleMap = new Map(); | ||||
| 
 | ||||
|   static getType(): string { | ||||
|     return 'tablerow'; | ||||
|   } | ||||
| 
 | ||||
|   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 { | ||||
|  | @ -52,20 +57,24 @@ export class TableRowNode extends ElementNode { | |||
|   } | ||||
| 
 | ||||
|   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); | ||||
|     this.__height = height; | ||||
|   } | ||||
| 
 | ||||
|   exportJSON(): SerializedTableRowNode { | ||||
|     return { | ||||
|       ...super.exportJSON(), | ||||
|       ...(this.getHeight() && {height: this.getHeight()}), | ||||
|       type: 'tablerow', | ||||
|       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`; | ||||
|     } | ||||
| 
 | ||||
|     for (const [name, value] of this.__styles.entries()) { | ||||
|       element.style.setProperty(name, value); | ||||
|     } | ||||
| 
 | ||||
|     addClassNamesToElement(element, config.theme.tableRow); | ||||
| 
 | ||||
|     return element; | ||||
|  | @ -85,6 +98,16 @@ export class TableRowNode extends ElementNode { | |||
|     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 { | ||||
|     const self = this.getWritable(); | ||||
|     self.__height = height; | ||||
|  | @ -96,7 +119,8 @@ export class TableRowNode extends ElementNode { | |||
|   } | ||||
| 
 | ||||
|   updateDOM(prevNode: TableRowNode): boolean { | ||||
|     return prevNode.__height !== this.__height; | ||||
|     return prevNode.__height !== this.__height | ||||
|         || prevNode.__styles !== this.__styles; | ||||
|   } | ||||
| 
 | ||||
|   canBeEmpty(): false { | ||||
|  | @ -109,18 +133,21 @@ export class TableRowNode extends ElementNode { | |||
| } | ||||
| 
 | ||||
| export function $convertTableRowElement(domNode: Node): DOMConversionOutput { | ||||
|   const domNode_ = domNode as HTMLTableCellElement; | ||||
|   let height: number | undefined = undefined; | ||||
|   const rowNode = $createTableRowNode(); | ||||
|   const domNode_ = domNode as HTMLElement; | ||||
| 
 | ||||
|   if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) { | ||||
|     height = parseFloat(domNode_.style.height); | ||||
|   const height = sizeToPixels(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 { | ||||
|   return $applyNodeReplacement(new TableRowNode(height)); | ||||
| export function $createTableRowNode(): TableRowNode { | ||||
|   return $applyNodeReplacement(new TableRowNode()); | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|     editor.registerCommand( | ||||
|       CONTROLLED_TEXT_INSERTION_COMMAND, | ||||
|  |  | |||
|  | @ -39,10 +39,9 @@ describe('LexicalTableRowNode tests', () => { | |||
|           `<tr class="${editorConfig.theme.tableRow}"></tr>`, | ||||
|         ); | ||||
| 
 | ||||
|         const rowHeight = 36; | ||||
|         const rowWithCustomHeightNode = $createTableRowNode(36); | ||||
|         const rowWithCustomHeightNode = $createTableRowNode(); | ||||
|         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 {ListItemNode, ListNode} from "@lexical/list"; | ||||
| import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; | ||||
| import {CustomTableNode} from "./custom-table"; | ||||
| import {HorizontalRuleNode} from "./horizontal-rule"; | ||||
| import {CodeBlockNode} from "./code-block"; | ||||
| import {DiagramNode} from "./diagram"; | ||||
| import {EditorUiContext} from "../ui/framework/core"; | ||||
| import {MediaNode} from "./media"; | ||||
| import {CustomTableCellNode} from "./custom-table-cell"; | ||||
| import {CustomTableRowNode} from "./custom-table-row"; | ||||
| import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; | ||||
| import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; | ||||
| 
 | ||||
|  | @ -32,9 +29,9 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | | |||
|         QuoteNode, | ||||
|         ListNode, | ||||
|         ListItemNode, | ||||
|         CustomTableNode, | ||||
|         CustomTableRowNode, | ||||
|         CustomTableCellNode, | ||||
|         TableNode, | ||||
|         TableRowNode, | ||||
|         TableCellNode, | ||||
|         ImageNode, // TODO - Alignment
 | ||||
|         HorizontalRuleNode, | ||||
|         DetailsNode, SummaryNode, | ||||
|  | @ -43,30 +40,6 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | | |||
|         MediaNode, // TODO - Alignment
 | ||||
|         ParagraphNode, | ||||
|         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 {EditorUiContext} from "../../framework/core"; | ||||
| import {$getSelection, BaseSelection} from "lexical"; | ||||
| import {$isCustomTableNode} from "../../../nodes/custom-table"; | ||||
| import { | ||||
|     $deleteTableColumn__EXPERIMENTAL, | ||||
|     $deleteTableRow__EXPERIMENTAL, | ||||
|     $insertTableColumn__EXPERIMENTAL, | ||||
|     $insertTableRow__EXPERIMENTAL, | ||||
|     $isTableNode, $isTableSelection, $unmergeCell, TableCellNode, | ||||
|     $insertTableRow__EXPERIMENTAL, $isTableCellNode, | ||||
|     $isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode, | ||||
| } from "@lexical/table"; | ||||
| import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection"; | ||||
| import {$getParentOfType} from "../../../utils/nodes"; | ||||
| import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell"; | ||||
| import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables"; | ||||
| import { | ||||
|     $clearTableFormatting, | ||||
|  | @ -27,7 +25,6 @@ import { | |||
|     $getTableRowsFromSelection, | ||||
|     $mergeTableCellsInSelection | ||||
| } from "../../../utils/tables"; | ||||
| import {$isCustomTableRowNode} from "../../../nodes/custom-table-row"; | ||||
| import { | ||||
|     $copySelectedColumnsToClipboard, | ||||
|     $copySelectedRowsToClipboard, | ||||
|  | @ -41,7 +38,7 @@ import { | |||
| } from "../../../utils/table-copy-paste"; | ||||
| 
 | ||||
| const neverActive = (): boolean => false; | ||||
| const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode); | ||||
| const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isTableCellNode); | ||||
| 
 | ||||
| export const table: EditorBasicButtonDefinition = { | ||||
|     label: 'Table', | ||||
|  | @ -54,7 +51,7 @@ export const tableProperties: EditorButtonDefinition = { | |||
|     action(context: EditorUiContext) { | ||||
|         context.editor.getEditorState().read(() => { | ||||
|             const table = $getTableFromSelection($getSelection()); | ||||
|             if ($isCustomTableNode(table)) { | ||||
|             if ($isTableNode(table)) { | ||||
|                 $showTablePropertiesForm(table, context); | ||||
|             } | ||||
|         }); | ||||
|  | @ -68,13 +65,13 @@ export const clearTableFormatting: EditorButtonDefinition = { | |||
|     format: 'long', | ||||
|     action(context: EditorUiContext) { | ||||
|         context.editor.update(() => { | ||||
|             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); | ||||
|             if (!$isCustomTableCellNode(cell)) { | ||||
|             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); | ||||
|             if (!$isTableCellNode(cell)) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const table = $getParentOfType(cell, $isTableNode); | ||||
|             if ($isCustomTableNode(table)) { | ||||
|             if ($isTableNode(table)) { | ||||
|                 $clearTableFormatting(table); | ||||
|             } | ||||
|         }); | ||||
|  | @ -88,13 +85,13 @@ export const resizeTableToContents: EditorButtonDefinition = { | |||
|     format: 'long', | ||||
|     action(context: EditorUiContext) { | ||||
|         context.editor.update(() => { | ||||
|             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); | ||||
|             if (!$isCustomTableCellNode(cell)) { | ||||
|             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); | ||||
|             if (!$isTableCellNode(cell)) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const table = $getParentOfType(cell, $isCustomTableNode); | ||||
|             if ($isCustomTableNode(table)) { | ||||
|             const table = $getParentOfType(cell, $isTableNode); | ||||
|             if ($isTableNode(table)) { | ||||
|                 $clearTableSizes(table); | ||||
|             } | ||||
|         }); | ||||
|  | @ -108,7 +105,7 @@ export const deleteTable: EditorButtonDefinition = { | |||
|     icon: deleteIcon, | ||||
|     action(context: EditorUiContext) { | ||||
|         context.editor.update(() => { | ||||
|             const table = $getNodeFromSelection($getSelection(), $isCustomTableNode); | ||||
|             const table = $getNodeFromSelection($getSelection(), $isTableNode); | ||||
|             if (table) { | ||||
|                 table.remove(); | ||||
|             } | ||||
|  | @ -169,7 +166,7 @@ export const rowProperties: EditorButtonDefinition = { | |||
|     action(context: EditorUiContext) { | ||||
|         context.editor.getEditorState().read(() => { | ||||
|             const rows = $getTableRowsFromSelection($getSelection()); | ||||
|             if ($isCustomTableRowNode(rows[0])) { | ||||
|             if ($isTableRowNode(rows[0])) { | ||||
|                 $showRowPropertiesForm(rows[0], context); | ||||
|             } | ||||
|         }); | ||||
|  | @ -350,8 +347,8 @@ export const cellProperties: EditorButtonDefinition = { | |||
|     format: 'long', | ||||
|     action(context: EditorUiContext) { | ||||
|         context.editor.getEditorState().read(() => { | ||||
|             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); | ||||
|             if ($isCustomTableCellNode(cell)) { | ||||
|             const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); | ||||
|             if ($isTableCellNode(cell)) { | ||||
|                 $showCellPropertiesForm(cell, context); | ||||
|             } | ||||
|         }); | ||||
|  | @ -387,7 +384,7 @@ export const splitCell: EditorButtonDefinition = { | |||
|     }, | ||||
|     isActive: neverActive, | ||||
|     isDisabled(selection) { | ||||
|         const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null; | ||||
|         const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode|null; | ||||
|         if (cell) { | ||||
|             const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1; | ||||
|             return !merged; | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ import { | |||
|     EditorSelectFormFieldDefinition | ||||
| } from "../../framework/forms"; | ||||
| import {EditorUiContext} from "../../framework/core"; | ||||
| import {CustomTableCellNode} from "../../../nodes/custom-table-cell"; | ||||
| import {EditorFormModal} from "../../framework/modals"; | ||||
| import {$getSelection, ElementFormatType} from "lexical"; | ||||
| import { | ||||
|  | @ -16,8 +15,8 @@ import { | |||
|     $setTableCellColumnWidth | ||||
| } from "../../../utils/tables"; | ||||
| import {formatSizeValue} from "../../../utils/dom"; | ||||
| import {CustomTableRowNode} from "../../../nodes/custom-table-row"; | ||||
| import {CustomTableNode} from "../../../nodes/custom-table"; | ||||
| import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; | ||||
| import {CommonBlockAlignment} from "../../../nodes/_common"; | ||||
| 
 | ||||
| const borderStyleInput: EditorSelectFormFieldDefinition = { | ||||
|     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 modalForm = context.manager.createModal('cell_properties'); | ||||
|     modalForm.show({ | ||||
|         width: $getTableCellColumnWidth(context.editor, cell), | ||||
|         height: styles.get('height') || '', | ||||
|         type: cell.getTag(), | ||||
|         h_align: cell.getFormatType(), | ||||
|         h_align: cell.getAlignment(), | ||||
|         v_align: styles.get('vertical-align') || '', | ||||
|         border_width: styles.get('border-width') || '', | ||||
|         border_style: styles.get('border-style') || '', | ||||
|  | @ -89,7 +88,7 @@ export const cellProperties: EditorFormDefinition = { | |||
| 
 | ||||
|                 $setTableCellColumnWidth(cell, width); | ||||
|                 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(); | ||||
|                 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 modalForm = context.manager.createModal('row_properties'); | ||||
|     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 modalForm = context.manager.createModal('table_properties'); | ||||
|     modalForm.show({ | ||||
|  | @ -229,7 +228,7 @@ export function $showTablePropertiesForm(table: CustomTableNode, context: Editor | |||
|         border_color: styles.get('border-color') || '', | ||||
|         background_color: styles.get('background-color') || '', | ||||
|         // caption: '', TODO
 | ||||
|         align: table.getFormatType(), | ||||
|         align: table.getAlignment(), | ||||
|     }); | ||||
|     return modalForm; | ||||
| } | ||||
|  | @ -253,12 +252,12 @@ export const tableProperties: EditorFormDefinition = { | |||
|             styles.set('background-color', formData.get('background_color')?.toString() || ''); | ||||
|             table.setStyles(styles); | ||||
| 
 | ||||
|             table.setFormat(formData.get('align') as ElementFormatType); | ||||
|             table.setAlignment(formData.get('align') as CommonBlockAlignment); | ||||
| 
 | ||||
|             const cellPadding = (formData.get('cell_padding')?.toString() || ''); | ||||
|             if (cellPadding) { | ||||
|                 const cellPaddingFormatted = formatSizeValue(cellPadding); | ||||
|                 $forEachTableCell(table, (cell: CustomTableCellNode) => { | ||||
|                 $forEachTableCell(table, (cell: TableCellNode) => { | ||||
|                     const styles = cell.getStyles(); | ||||
|                     styles.set('padding', cellPaddingFormatted); | ||||
|                     cell.setStyles(styles); | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import {EditorUiElement} from "../core"; | ||||
| import {$createTableNodeWithDimensions} from "@lexical/table"; | ||||
| import {CustomTableNode} from "../../../nodes/custom-table"; | ||||
| import {$insertNewBlockNodeAtSelection} from "../../../utils/selection"; | ||||
| import {el} from "../../../utils/dom"; | ||||
| 
 | ||||
|  | @ -78,7 +77,7 @@ export class EditorTableCreator extends EditorUiElement { | |||
|         const colWidths = Array(columns).fill(targetColWidth + 'px'); | ||||
| 
 | ||||
|         this.getContext().editor.update(() => { | ||||
|             const table = $createTableNodeWithDimensions(rows, columns, false) as CustomTableNode; | ||||
|             const table = $createTableNodeWithDimensions(rows, columns, false); | ||||
|             table.setColWidths(colWidths); | ||||
|             $insertNewBlockNodeAtSelection(table); | ||||
|         }); | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical"; | ||||
| import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker"; | ||||
| import {CustomTableNode} from "../../../nodes/custom-table"; | ||||
| import {TableRowNode} from "@lexical/table"; | ||||
| import {TableNode, TableRowNode} from "@lexical/table"; | ||||
| import {el} from "../../../utils/dom"; | ||||
| import {$getTableColumnWidth, $setTableColumnWidth} from "../../../utils/tables"; | ||||
| 
 | ||||
|  | @ -148,7 +147,7 @@ class TableResizer { | |||
| 
 | ||||
|                     _this.editor.update(() => { | ||||
|                         const table = $getNearestNodeFromDOMNode(parentTable); | ||||
|                         if (table instanceof CustomTableNode) { | ||||
|                         if (table instanceof TableNode) { | ||||
|                             const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex); | ||||
|                             const newWidth = Math.max(originalWidth + change, 10); | ||||
|                             $setTableColumnWidth(table, cellIndex, newWidth); | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| import {$getNodeByKey, LexicalEditor} from "lexical"; | ||||
| import {NodeKey} from "lexical/LexicalNode"; | ||||
| import { | ||||
|     $isTableNode, | ||||
|     applyTableHandlers, | ||||
|     HTMLTableElementWithWithTableSelectionState, | ||||
|     TableNode, | ||||
|     TableObserver | ||||
| } from "@lexical/table"; | ||||
| import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table"; | ||||
| 
 | ||||
| // File adapted from logic in:
 | ||||
| // https://github.com/facebook/lexical/blob/f373759a7849f473d34960a6bf4e34b2a011e762/packages/lexical-react/src/LexicalTablePlugin.ts#L49
 | ||||
|  | @ -25,12 +25,12 @@ class TableSelectionHandler { | |||
|     } | ||||
| 
 | ||||
|     protected init() { | ||||
|         this.unregisterMutationListener = this.editor.registerMutationListener(CustomTableNode, (mutations) => { | ||||
|         this.unregisterMutationListener = this.editor.registerMutationListener(TableNode, (mutations) => { | ||||
|             for (const [nodeKey, mutation] of mutations) { | ||||
|                 if (mutation === 'created') { | ||||
|                     this.editor.getEditorState().read(() => { | ||||
|                         const tableNode = $getNodeByKey<CustomTableNode>(nodeKey); | ||||
|                         if ($isCustomTableNode(tableNode)) { | ||||
|                         const tableNode = $getNodeByKey<TableNode>(nodeKey); | ||||
|                         if ($isTableNode(tableNode)) { | ||||
|                             this.initializeTableNode(tableNode); | ||||
|                         } | ||||
|                     }); | ||||
|  |  | |||
|  | @ -1,24 +1,28 @@ | |||
| import {NodeClipboard} from "./node-clipboard"; | ||||
| import {CustomTableRowNode} from "../nodes/custom-table-row"; | ||||
| import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables"; | ||||
| 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 {$isTableSelection} from "@lexical/table"; | ||||
| import { | ||||
|     $createTableCellNode, | ||||
|     $isTableCellNode, | ||||
|     $isTableSelection, | ||||
|     TableCellNode, | ||||
|     TableNode, | ||||
|     TableRowNode | ||||
| } from "@lexical/table"; | ||||
| import {$getNodeFromSelection} from "./selection"; | ||||
| 
 | ||||
| const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>(); | ||||
| const rowClipboard: NodeClipboard<TableRowNode> = new NodeClipboard<TableRowNode>(); | ||||
| 
 | ||||
| export function isRowClipboardEmpty(): boolean { | ||||
|     return rowClipboard.size() === 0; | ||||
| } | ||||
| 
 | ||||
| export function validateRowsToCopy(rows: CustomTableRowNode[]): void { | ||||
| export function validateRowsToCopy(rows: TableRowNode[]): void { | ||||
|     let commonRowSize: number|null = null; | ||||
| 
 | ||||
|     for (const row of rows) { | ||||
|         const cells = row.getChildren().filter(n => $isCustomTableCellNode(n)); | ||||
|         const cells = row.getChildren().filter(n => $isTableCellNode(n)); | ||||
|         let rowSize = 0; | ||||
|         for (const cell of cells) { | ||||
|             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; | ||||
|     for (const row of rows) { | ||||
|         const cells = row.getChildren().filter(n => $isCustomTableCellNode(n)); | ||||
|         const cells = row.getChildren().filter(n => $isTableCellNode(n)); | ||||
|         let rowSize = 0; | ||||
|         for (const cell of cells) { | ||||
|             rowSize += cell.getColSpan() || 1; | ||||
|  | @ -49,7 +53,7 @@ export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: Cus | |||
|         } | ||||
| 
 | ||||
|         while (rowSize < tableColCount) { | ||||
|             row.append($createCustomTableCellNode()); | ||||
|             row.append($createTableCellNode()); | ||||
|             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 clipboard = new NodeClipboard<CustomTableCellNode>(); | ||||
|         const clipboard = new NodeClipboard<TableCellNode>(); | ||||
|         clipboard.set(...cells); | ||||
|         return clipboard; | ||||
|     }); | ||||
|  | @ -122,9 +126,9 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul | |||
|         return {from: shape.fromX, to: shape.toX}; | ||||
|     } | ||||
| 
 | ||||
|     const cell = $getNodeFromSelection(selection, $isCustomTableCellNode); | ||||
|     const cell = $getNodeFromSelection(selection, $isTableCellNode); | ||||
|     const table = $getTableFromSelection(selection); | ||||
|     if (!$isCustomTableCellNode(cell) || !table) { | ||||
|     if (!$isTableCellNode(cell) || !table) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|  | @ -137,7 +141,7 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul | |||
|     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 columns = []; | ||||
|     for (let x = range.from; x <= range.to; x++) { | ||||
|  | @ -148,7 +152,7 @@ function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTable | |||
|     return columns; | ||||
| } | ||||
| 
 | ||||
| function validateColumnsToCopy(columns: CustomTableCellNode[][]): void { | ||||
| function validateColumnsToCopy(columns: TableCellNode[][]): void { | ||||
|     let commonColSize: number|null = null; | ||||
| 
 | ||||
|     for (const cells of columns) { | ||||
|  | @ -203,7 +207,7 @@ export function $copySelectedColumnsToClipboard(): void { | |||
|     setColumnClipboard(columns); | ||||
| } | ||||
| 
 | ||||
| function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: CustomTableNode) { | ||||
| function validateColumnsToPaste(columns: TableCellNode[][], targetTable: TableNode) { | ||||
|     const tableRowCount = (new TableMap(targetTable)).rowCount; | ||||
|     for (const cells of columns) { | ||||
|         let colSize = 0; | ||||
|  | @ -216,7 +220,7 @@ function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: C | |||
|         } | ||||
| 
 | ||||
|         while (colSize < tableRowCount) { | ||||
|             cells.push($createCustomTableCellNode()); | ||||
|             cells.push($createTableCellNode()); | ||||
|             colSize++; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,4 @@ | |||
| import {CustomTableNode} from "../nodes/custom-table"; | ||||
| import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; | ||||
| import {$isTableRowNode} from "@lexical/table"; | ||||
| import {$isTableCellNode, $isTableRowNode, TableCellNode, TableNode} from "@lexical/table"; | ||||
| 
 | ||||
| export type CellRange = { | ||||
|     fromX: number; | ||||
|  | @ -16,15 +14,15 @@ export class TableMap { | |||
| 
 | ||||
|     // 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.
 | ||||
|     cells: CustomTableCellNode[] = []; | ||||
|     cells: TableCellNode[] = []; | ||||
| 
 | ||||
|     constructor(table: CustomTableNode) { | ||||
|     constructor(table: TableNode) { | ||||
|         this.buildCellMap(table); | ||||
|     } | ||||
| 
 | ||||
|     protected buildCellMap(table: CustomTableNode) { | ||||
|         const rowsAndCells: CustomTableCellNode[][] = []; | ||||
|         const setCell = (x: number, y: number, cell: CustomTableCellNode) => { | ||||
|     protected buildCellMap(table: TableNode) { | ||||
|         const rowsAndCells: TableCellNode[][] = []; | ||||
|         const setCell = (x: number, y: number, cell: TableCellNode) => { | ||||
|             if (typeof rowsAndCells[y] === 'undefined') { | ||||
|                 rowsAndCells[y] = []; | ||||
|             } | ||||
|  | @ -36,7 +34,7 @@ export class TableMap { | |||
|         const rowNodes = table.getChildren().filter(r => $isTableRowNode(r)); | ||||
|         for (let rowIndex = 0; rowIndex < rowNodes.length; 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; | ||||
|             for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) { | ||||
|                 const cellNode = cellNodes[cellIndex]; | ||||
|  | @ -60,7 +58,7 @@ export class TableMap { | |||
|         this.columnCount = Math.max(...rowsAndCells.map(r => r.length)); | ||||
| 
 | ||||
|         const cells = []; | ||||
|         let lastCell: CustomTableCellNode = rowsAndCells[0][0]; | ||||
|         let lastCell: TableCellNode = rowsAndCells[0][0]; | ||||
|         for (let y = 0; y < this.rowCount; y++) { | ||||
|             for (let x = 0; x < this.columnCount; x++) { | ||||
|                 if (!rowsAndCells[y] || !rowsAndCells[y][x]) { | ||||
|  | @ -75,7 +73,7 @@ export class TableMap { | |||
|         this.cells = cells; | ||||
|     } | ||||
| 
 | ||||
|     public getCellAtPosition(x: number, y: number): CustomTableCellNode { | ||||
|     public getCellAtPosition(x: number, y: number): TableCellNode { | ||||
|         const position = (y * this.columnCount) + x; | ||||
|         if (position >= 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]; | ||||
|     } | ||||
| 
 | ||||
|     public getCellsInRange(range: CellRange): CustomTableCellNode[] { | ||||
|     public getCellsInRange(range: CellRange): TableCellNode[] { | ||||
|         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 minY = Math.max(Math.min(range.fromY, range.toY), 0); | ||||
|         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 x = minX; x <= maxX; x++) { | ||||
|  | @ -101,7 +99,7 @@ export class TableMap { | |||
|         return [...cells.values()]; | ||||
|     } | ||||
| 
 | ||||
|     public getCellsInColumn(columnIndex: number): CustomTableCellNode[] { | ||||
|     public getCellsInColumn(columnIndex: number): TableCellNode[] { | ||||
|         return this.getCellsInRange({ | ||||
|             fromX: 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; | ||||
|         const cellKey = cell.getKey(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +1,19 @@ | |||
| import {BaseSelection, LexicalEditor} from "lexical"; | ||||
| import {$isTableRowNode, $isTableSelection, TableRowNode, TableSelection, TableSelectionShape} from "@lexical/table"; | ||||
| import {$isCustomTableNode, CustomTableNode} from "../nodes/custom-table"; | ||||
| import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; | ||||
| import { | ||||
|     $isTableCellNode, | ||||
|     $isTableNode, | ||||
|     $isTableRowNode, | ||||
|     $isTableSelection, TableCellNode, TableNode, | ||||
|     TableRowNode, | ||||
|     TableSelection, | ||||
| } from "@lexical/table"; | ||||
| import {$getParentOfType} from "./nodes"; | ||||
| import {$getNodeFromSelection} from "./selection"; | ||||
| import {formatSizeValue} from "./dom"; | ||||
| import {TableMap} from "./table-map"; | ||||
| import {$isCustomTableRowNode, CustomTableRowNode} from "../nodes/custom-table-row"; | ||||
| 
 | ||||
| function $getTableFromCell(cell: CustomTableCellNode): CustomTableNode|null { | ||||
|     return $getParentOfType(cell, $isCustomTableNode) as CustomTableNode|null; | ||||
| function $getTableFromCell(cell: TableCellNode): TableNode|null { | ||||
|     return $getParentOfType(cell, $isTableNode) as TableNode|null; | ||||
| } | ||||
| 
 | ||||
| export function getTableColumnWidths(table: HTMLTableElement): string[] { | ||||
|  | @ -55,7 +59,7 @@ function extractWidthFromElement(element: HTMLElement): string { | |||
|     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[]; | ||||
|     let maxCols = 0; | ||||
|     for (const row of rows) { | ||||
|  | @ -78,7 +82,7 @@ export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number, | |||
|     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(); | ||||
|     if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) { | ||||
|         return Number(colWidths[columnIndex].replace('px', '')); | ||||
|  | @ -97,14 +101,14 @@ export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNod | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| function $getCellColumnIndex(node: CustomTableCellNode): number { | ||||
| function $getCellColumnIndex(node: TableCellNode): number { | ||||
|     const row = node.getParent(); | ||||
|     if (!$isTableRowNode(row)) { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     let index = 0; | ||||
|     const cells = row.getChildren<CustomTableCellNode>(); | ||||
|     const cells = row.getChildren<TableCellNode>(); | ||||
|     for (const cell of cells) { | ||||
|         let colSpan = cell.getColSpan() || 1; | ||||
|         index += colSpan; | ||||
|  | @ -116,7 +120,7 @@ function $getCellColumnIndex(node: CustomTableCellNode): number { | |||
|     return index - 1; | ||||
| } | ||||
| 
 | ||||
| export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: string): void { | ||||
| export function $setTableCellColumnWidth(cell: TableCellNode, width: string): void { | ||||
|     const table = $getTableFromCell(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 index = $getCellColumnIndex(cell); | ||||
|     if (!table) { | ||||
|  | @ -136,13 +140,13 @@ export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTabl | |||
|     return (widths.length > index) ? widths[index] : ''; | ||||
| } | ||||
| 
 | ||||
| export function $getTableCellsFromSelection(selection: BaseSelection|null): CustomTableCellNode[]  { | ||||
| export function $getTableCellsFromSelection(selection: BaseSelection|null): TableCellNode[]  { | ||||
|     if ($isTableSelection(selection)) { | ||||
|         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] : []; | ||||
| } | ||||
| 
 | ||||
|  | @ -193,12 +197,12 @@ export function $mergeTableCellsInSelection(selection: TableSelection): void { | |||
|     firstCell.setRowSpan(newHeight); | ||||
| } | ||||
| 
 | ||||
| export function $getTableRowsFromSelection(selection: BaseSelection|null): CustomTableRowNode[] { | ||||
| export function $getTableRowsFromSelection(selection: BaseSelection|null): TableRowNode[] { | ||||
|     const cells = $getTableCellsFromSelection(selection); | ||||
|     const rowsByKey: Record<string, CustomTableRowNode> = {}; | ||||
|     const rowsByKey: Record<string, TableRowNode> = {}; | ||||
|     for (const cell of cells) { | ||||
|         const row = cell.getParent(); | ||||
|         if ($isCustomTableRowNode(row)) { | ||||
|         if ($isTableRowNode(row)) { | ||||
|             rowsByKey[row.getKey()] = row; | ||||
|         } | ||||
|     } | ||||
|  | @ -206,28 +210,28 @@ export function $getTableRowsFromSelection(selection: BaseSelection|null): Custo | |||
|     return Object.values(rowsByKey); | ||||
| } | ||||
| 
 | ||||
| export function $getTableFromSelection(selection: BaseSelection|null): CustomTableNode|null { | ||||
| export function $getTableFromSelection(selection: BaseSelection|null): TableNode|null { | ||||
|     const cells = $getTableCellsFromSelection(selection); | ||||
|     if (cells.length === 0) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     const table = $getParentOfType(cells[0], $isCustomTableNode); | ||||
|     if ($isCustomTableNode(table)) { | ||||
|     const table = $getParentOfType(cells[0], $isTableNode); | ||||
|     if ($isTableNode(table)) { | ||||
|         return table; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
| } | ||||
| 
 | ||||
| export function $clearTableSizes(table: CustomTableNode): void { | ||||
| export function $clearTableSizes(table: TableNode): void { | ||||
|     table.setColWidths([]); | ||||
| 
 | ||||
|     // TODO - Extra form things once table properties and extra things
 | ||||
|     //   are supported
 | ||||
| 
 | ||||
|     for (const row of table.getChildren()) { | ||||
|         if (!$isCustomTableRowNode(row)) { | ||||
|         if (!$isTableRowNode(row)) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|  | @ -236,7 +240,7 @@ export function $clearTableSizes(table: CustomTableNode): void { | |||
|         rowStyles.delete('width'); | ||||
|         row.setStyles(rowStyles); | ||||
| 
 | ||||
|         const cells = row.getChildren().filter(c => $isCustomTableCellNode(c)); | ||||
|         const cells = row.getChildren().filter(c => $isTableCellNode(c)); | ||||
|         for (const cell of cells) { | ||||
|             const cellStyles = cell.getStyles(); | ||||
|             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.setStyles(new Map); | ||||
| 
 | ||||
|     for (const row of table.getChildren()) { | ||||
|         if (!$isCustomTableRowNode(row)) { | ||||
|         if (!$isTableRowNode(row)) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         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) { | ||||
|             cell.setStyles(new Map); | ||||
|             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. | ||||
|  * 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()) { | ||||
|         if (!$isCustomTableRowNode(row)) { | ||||
|         if (!$isTableRowNode(row)) { | ||||
|             continue; | ||||
|         } | ||||
|         const cells = row.getChildren(); | ||||
|         for (const cell of cells) { | ||||
|             if (!$isCustomTableCellNode(cell)) { | ||||
|             if (!$isTableCellNode(cell)) { | ||||
|                 return; | ||||
|             } | ||||
|             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; | ||||
| 
 | ||||
|     $forEachTableCell(table, (cell: CustomTableCellNode) => { | ||||
|     $forEachTableCell(table, (cell: TableCellNode) => { | ||||
|         const cellPadding = cell.getStyles().get('padding') || '' | ||||
|         if (padding === null) { | ||||
|             padding = cellPadding; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue