1 import {NodeClipboard} from "./node-clipboard";
2 import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables";
3 import {$getSelection, BaseSelection, LexicalEditor} from "lexical";
4 import {TableMap} from "./table-map";
12 } from "@lexical/table";
13 import {$getNodeFromSelection} from "./selection";
15 const rowClipboard: NodeClipboard<TableRowNode> = new NodeClipboard<TableRowNode>();
17 export function isRowClipboardEmpty(): boolean {
18 return rowClipboard.size() === 0;
21 export function validateRowsToCopy(rows: TableRowNode[]): void {
22 let commonRowSize: number|null = null;
24 for (const row of rows) {
25 const cells = row.getChildren().filter(n => $isTableCellNode(n));
27 for (const cell of cells) {
28 rowSize += cell.getColSpan() || 1;
29 if (cell.getRowSpan() > 1) {
30 throw Error('Cannot copy rows with merged cells');
34 if (commonRowSize === null) {
35 commonRowSize = rowSize;
36 } else if (commonRowSize !== rowSize) {
37 throw Error('Cannot copy rows with inconsistent sizes');
42 export function validateRowsToPaste(rows: TableRowNode[], targetTable: TableNode): void {
43 const tableColCount = (new TableMap(targetTable)).columnCount;
44 for (const row of rows) {
45 const cells = row.getChildren().filter(n => $isTableCellNode(n));
47 for (const cell of cells) {
48 rowSize += cell.getColSpan() || 1;
51 if (rowSize > tableColCount) {
52 throw Error('Cannot paste rows that are wider than target table');
55 while (rowSize < tableColCount) {
56 row.append($createTableCellNode());
62 export function $cutSelectedRowsToClipboard(): void {
63 const rows = $getTableRowsFromSelection($getSelection());
64 validateRowsToCopy(rows);
65 rowClipboard.set(...rows);
66 for (const row of rows) {
71 export function $copySelectedRowsToClipboard(): void {
72 const rows = $getTableRowsFromSelection($getSelection());
73 validateRowsToCopy(rows);
74 rowClipboard.set(...rows);
77 export function $pasteClipboardRowsBefore(editor: LexicalEditor): void {
78 const selection = $getSelection();
79 const rows = $getTableRowsFromSelection(selection);
80 const table = $getTableFromSelection(selection);
81 const lastRow = rows[rows.length - 1];
82 if (lastRow && table) {
83 const clipboardRows = rowClipboard.get(editor);
84 validateRowsToPaste(clipboardRows, table);
85 for (const row of clipboardRows) {
86 lastRow.insertBefore(row);
91 export function $pasteClipboardRowsAfter(editor: LexicalEditor): void {
92 const selection = $getSelection();
93 const rows = $getTableRowsFromSelection(selection);
94 const table = $getTableFromSelection(selection);
95 const lastRow = rows[rows.length - 1];
96 if (lastRow && table) {
97 const clipboardRows = rowClipboard.get(editor).reverse();
98 validateRowsToPaste(clipboardRows, table);
99 for (const row of clipboardRows) {
100 lastRow.insertAfter(row);
105 const columnClipboard: NodeClipboard<TableCellNode>[] = [];
107 function setColumnClipboard(columns: TableCellNode[][]): void {
108 const newClipboards = columns.map(cells => {
109 const clipboard = new NodeClipboard<TableCellNode>();
110 clipboard.set(...cells);
114 columnClipboard.splice(0, columnClipboard.length, ...newClipboards);
117 type TableRange = {from: number, to: number};
119 export function isColumnClipboardEmpty(): boolean {
120 return columnClipboard.length === 0;
123 function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|null {
124 if ($isTableSelection(selection)) {
125 const shape = selection.getShape()
126 return {from: shape.fromX, to: shape.toX};
129 const cell = $getNodeFromSelection(selection, $isTableCellNode);
130 const table = $getTableFromSelection(selection);
131 if (!$isTableCellNode(cell) || !table) {
135 const map = new TableMap(table);
136 const range = map.getRangeForCell(cell);
141 return {from: range.fromX, to: range.toX};
144 function $getTableColumnCellsFromSelection(range: TableRange, table: TableNode): TableCellNode[][] {
145 const map = new TableMap(table);
147 for (let x = range.from; x <= range.to; x++) {
148 const cells = map.getCellsInColumn(x);
155 function validateColumnsToCopy(columns: TableCellNode[][]): void {
156 let commonColSize: number|null = null;
158 for (const cells of columns) {
160 for (const cell of cells) {
161 colSize += cell.getRowSpan() || 1;
162 if (cell.getColSpan() > 1) {
163 throw Error('Cannot copy columns with merged cells');
167 if (commonColSize === null) {
168 commonColSize = colSize;
169 } else if (commonColSize !== colSize) {
170 throw Error('Cannot copy columns with inconsistent sizes');
175 export function $cutSelectedColumnsToClipboard(): void {
176 const selection = $getSelection();
177 const range = $getSelectionColumnRange(selection);
178 const table = $getTableFromSelection(selection);
179 if (!range || !table) {
183 const colWidths = table.getColWidths();
184 const columns = $getTableColumnCellsFromSelection(range, table);
185 validateColumnsToCopy(columns);
186 setColumnClipboard(columns);
187 for (const cells of columns) {
188 for (const cell of cells) {
193 const newWidths = [...colWidths].splice(range.from, (range.to - range.from) + 1);
194 table.setColWidths(newWidths);
197 export function $copySelectedColumnsToClipboard(): void {
198 const selection = $getSelection();
199 const range = $getSelectionColumnRange(selection);
200 const table = $getTableFromSelection(selection);
201 if (!range || !table) {
205 const columns = $getTableColumnCellsFromSelection(range, table);
206 validateColumnsToCopy(columns);
207 setColumnClipboard(columns);
210 function validateColumnsToPaste(columns: TableCellNode[][], targetTable: TableNode) {
211 const tableRowCount = (new TableMap(targetTable)).rowCount;
212 for (const cells of columns) {
214 for (const cell of cells) {
215 colSize += cell.getRowSpan() || 1;
218 if (colSize > tableRowCount) {
219 throw Error('Cannot paste columns that are taller than target table');
222 while (colSize < tableRowCount) {
223 cells.push($createTableCellNode());
229 function $pasteClipboardColumns(editor: LexicalEditor, isBefore: boolean): void {
230 const selection = $getSelection();
231 const table = $getTableFromSelection(selection);
232 const cells = $getTableCellsFromSelection(selection);
233 const referenceCell = cells[isBefore ? 0 : cells.length - 1];
234 if (!table || !referenceCell) {
238 const clipboardCols = columnClipboard.map(cb => cb.get(editor));
240 clipboardCols.reverse();
243 validateColumnsToPaste(clipboardCols, table);
244 const map = new TableMap(table);
245 const cellRange = map.getRangeForCell(referenceCell);
250 const colIndex = isBefore ? cellRange.fromX : cellRange.toX;
251 const colWidths = table.getColWidths();
253 for (let y = 0; y < map.rowCount; y++) {
254 const relCell = map.getCellAtPosition(colIndex, y);
255 for (const cells of clipboardCols) {
256 const newCell = cells[y];
258 relCell.insertBefore(newCell);
260 relCell.insertAfter(newCell);
265 const refWidth = colWidths[colIndex];
266 const addedWidths = clipboardCols.map(_ => refWidth);
267 colWidths.splice(isBefore ? colIndex : colIndex + 1, 0, ...addedWidths);
270 export function $pasteClipboardColumnsBefore(editor: LexicalEditor): void {
271 $pasteClipboardColumns(editor, true);
274 export function $pasteClipboardColumnsAfter(editor: LexicalEditor): void {
275 $pasteClipboardColumns(editor, false);