Lexical: Merged custom table node code

This commit is contained in:
Dan Brown
2024-12-03 20:08:33 +00:00
parent ebd4604f21
commit 57d8449660
17 changed files with 322 additions and 787 deletions

View File

@ -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; serializedNode.headerState,
const cellNode = $createTableCellNode( serializedNode.colSpan,
serializedNode.headerState, serializedNode.width,
colSpan,
serializedNode.width || undefined,
); );
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
); );
} }
@ -287,38 +321,42 @@ export class TableCellNode extends ElementNode {
} }
export function $convertTableCellNodeElement( export function $convertTableCellNodeElement(
domNode: Node, domNode: Node,
): DOMConversionOutput { ): DOMConversionOutput {
const domNode_ = domNode as HTMLTableCellElement; const domNode_ = domNode as HTMLTableCellElement;
const nodeName = domNode.nodeName.toLowerCase(); const nodeName = domNode.nodeName.toLowerCase();
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);
} }
const tableCellNode = $createTableCellNode( const tableCellNode = $createTableCellNode(
nodeName === 'th' nodeName === 'th'
? TableCellHeaderStates.ROW ? TableCellHeaderStates.ROW
: TableCellHeaderStates.NO_STATUS, : TableCellHeaderStates.NO_STATUS,
domNode_.colSpan, domNode_.colSpan,
width, width,
); );
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(' ');
const hasBoldFontWeight = const hasBoldFontWeight =
style.fontWeight === '700' || style.fontWeight === 'bold'; style.fontWeight === '700' || style.fontWeight === 'bold';
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) {
@ -330,8 +368,8 @@ export function $convertTableCellNodeElement(
if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) { if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
const paragraphNode = $createParagraphNode(); const paragraphNode = $createParagraphNode();
if ( if (
$isLineBreakNode(lexicalNode) && $isLineBreakNode(lexicalNode) &&
lexicalNode.getTextContent() === '\n' lexicalNode.getTextContent() === '\n'
) { ) {
return null; return null;
} }
@ -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 {

View File

@ -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 {

View File

@ -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(

View File

@ -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,

View File

@ -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>`,
); );
}); });
}); });

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
},
]; ];
} }

View File

@ -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;

View File

@ -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);

View File

@ -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);
}); });

View File

@ -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);

View File

@ -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);
} }
}); });

View File

@ -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++;
} }
} }

View File

@ -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();

View File

@ -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;