1 import {$isTableCellNode, $isTableRowNode, TableCellNode, TableNode} from "@lexical/table";
3 export type CellRange = {
10 export class TableMap {
13 columnCount: number = 0;
15 // Represents an array (rows*columns in length) of cell nodes from top-left to
16 // bottom right. Cells may repeat where merged and covering multiple spaces.
17 cells: TableCellNode[] = [];
19 constructor(table: TableNode) {
20 this.buildCellMap(table);
23 protected buildCellMap(table: TableNode) {
24 const rowsAndCells: TableCellNode[][] = [];
25 const setCell = (x: number, y: number, cell: TableCellNode) => {
26 if (typeof rowsAndCells[y] === 'undefined') {
30 rowsAndCells[y][x] = cell;
32 const cellFilled = (x: number, y: number): boolean => !!(rowsAndCells[y] && rowsAndCells[y][x]);
34 const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
35 for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
36 const rowNode = rowNodes[rowIndex];
37 const cellNodes = rowNode.getChildren().filter(c => $isTableCellNode(c));
38 let targetColIndex: number = 0;
39 for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
40 const cellNode = cellNodes[cellIndex];
41 const colspan = cellNode.getColSpan() || 1;
42 const rowSpan = cellNode.getRowSpan() || 1;
43 for (let x = targetColIndex; x < targetColIndex + colspan; x++) {
44 for (let y = rowIndex; y < rowIndex + rowSpan; y++) {
45 while (cellFilled(x, y)) {
50 setCell(x, y, cellNode);
53 targetColIndex += colspan;
57 this.rowCount = rowsAndCells.length;
58 this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
61 let lastCell: TableCellNode = rowsAndCells[0][0];
62 for (let y = 0; y < this.rowCount; y++) {
63 for (let x = 0; x < this.columnCount; x++) {
64 if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
67 cells.push(rowsAndCells[y][x]);
68 lastCell = rowsAndCells[y][x];
76 public getCellAtPosition(x: number, y: number): TableCellNode {
77 const position = (y * this.columnCount) + x;
78 if (position >= this.cells.length) {
79 throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
82 return this.cells[position];
85 public getCellsInRange(range: CellRange): TableCellNode[] {
86 const minX = Math.max(Math.min(range.fromX, range.toX), 0);
87 const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1);
88 const minY = Math.max(Math.min(range.fromY, range.toY), 0);
89 const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1);
91 const cells = new Set<TableCellNode>();
93 for (let y = minY; y <= maxY; y++) {
94 for (let x = minX; x <= maxX; x++) {
95 cells.add(this.getCellAtPosition(x, y));
99 return [...cells.values()];
102 public getCellsInColumn(columnIndex: number): TableCellNode[] {
103 return this.getCellsInRange({
107 toY: this.rowCount - 1,
111 public getRangeForCell(cell: TableCellNode): CellRange|null {
112 let range: CellRange|null = null;
113 const cellKey = cell.getKey();
115 for (let y = 0; y < this.rowCount; y++) {
116 for (let x = 0; x < this.columnCount; x++) {
117 const index = (y * this.columnCount) + x;
118 const lCell = this.cells[index];
119 if (lCell.getKey() === cellKey) {
120 if (range === null) {
121 range = {fromX: x, toX: x, fromY: y, toY: y};
123 range.fromX = Math.min(range.fromX, x);
124 range.toX = Math.max(range.toX, x);
125 range.fromY = Math.min(range.fromY, y);
126 range.toY = Math.max(range.toY, y);