1 import {CustomTableNode} from "../nodes/custom-table";
2 import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
3 import {$isTableRowNode} from "@lexical/table";
5 export type CellRange = {
12 export class TableMap {
15 columnCount: number = 0;
17 // Represents an array (rows*columns in length) of cell nodes from top-left to
18 // bottom right. Cells may repeat where merged and covering multiple spaces.
19 cells: CustomTableCellNode[] = [];
21 constructor(table: CustomTableNode) {
22 this.buildCellMap(table);
25 protected buildCellMap(table: CustomTableNode) {
26 const rowsAndCells: CustomTableCellNode[][] = [];
27 const setCell = (x: number, y: number, cell: CustomTableCellNode) => {
28 if (typeof rowsAndCells[y] === 'undefined') {
32 rowsAndCells[y][x] = cell;
34 const cellFilled = (x: number, y: number): boolean => !!(rowsAndCells[y] && rowsAndCells[y][x]);
36 const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
37 for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
38 const rowNode = rowNodes[rowIndex];
39 const cellNodes = rowNode.getChildren().filter(c => $isCustomTableCellNode(c));
40 let targetColIndex: number = 0;
41 for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
42 const cellNode = cellNodes[cellIndex];
43 const colspan = cellNode.getColSpan() || 1;
44 const rowSpan = cellNode.getRowSpan() || 1;
45 for (let x = targetColIndex; x < targetColIndex + colspan; x++) {
46 for (let y = rowIndex; y < rowIndex + rowSpan; y++) {
47 while (cellFilled(x, y)) {
52 setCell(x, y, cellNode);
55 targetColIndex += colspan;
59 this.rowCount = rowsAndCells.length;
60 this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
63 let lastCell: CustomTableCellNode = rowsAndCells[0][0];
64 for (let y = 0; y < this.rowCount; y++) {
65 for (let x = 0; x < this.columnCount; x++) {
66 if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
69 cells.push(rowsAndCells[y][x]);
70 lastCell = rowsAndCells[y][x];
78 public getCellAtPosition(x: number, y: number): CustomTableCellNode {
79 const position = (y * this.columnCount) + x;
80 if (position >= this.cells.length) {
81 throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
84 return this.cells[position];
87 public getCellsInRange(range: CellRange): CustomTableCellNode[] {
88 const minX = Math.max(Math.min(range.fromX, range.toX), 0);
89 const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1);
90 const minY = Math.max(Math.min(range.fromY, range.toY), 0);
91 const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1);
93 const cells = new Set<CustomTableCellNode>();
95 for (let y = minY; y <= maxY; y++) {
96 for (let x = minX; x <= maxX; x++) {
97 cells.add(this.getCellAtPosition(x, y));
101 return [...cells.values()];
104 public getCellsInColumn(columnIndex: number): CustomTableCellNode[] {
105 return this.getCellsInRange({
109 toY: this.rowCount - 1,
113 public getRangeForCell(cell: CustomTableCellNode): CellRange|null {
114 let range: CellRange|null = null;
115 const cellKey = cell.getKey();
117 for (let y = 0; y < this.rowCount; y++) {
118 for (let x = 0; x < this.columnCount; x++) {
119 const index = (y * this.columnCount) + x;
120 const lCell = this.cells[index];
121 if (lCell.getKey() === cellKey) {
122 if (range === null) {
123 range = {fromX: x, toX: x, fromY: y, toY: y};
125 range.fromX = Math.min(range.fromX, x);
126 range.toX = Math.max(range.toX, x);
127 range.fromY = Math.min(range.fromY, y);
128 range.toY = Math.max(range.toY, y);