@lofcz/platejs-table 52.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2536 @@
1
+ import { ElementApi, KEYS, NodeApi, PathApi, PointApi, RangeApi, TextApi, bindFirst, combineTransformMatchOptions, createSlatePlugin, createTSlatePlugin, getEditorPlugin, getPluginTypes, isHotkey } from "platejs";
2
+ import cloneDeep from "lodash/cloneDeep.js";
3
+
4
+ //#region src/lib/api/getEmptyCellNode.ts
5
+ const getEmptyCellNode = (editor, { children, header, row } = {}) => {
6
+ header = header ?? (row ? row.children.every((c) => c.type === editor.getType(KEYS.th)) : false);
7
+ return {
8
+ children: children ?? [editor.api.create.block()],
9
+ type: header ? editor.getType(KEYS.th) : editor.getType(KEYS.td)
10
+ };
11
+ };
12
+
13
+ //#endregion
14
+ //#region src/lib/api/getEmptyRowNode.ts
15
+ const getEmptyRowNode = (editor, { colCount = 1, ...cellOptions } = {}) => {
16
+ const { api } = editor.getPlugin({ key: KEYS.table });
17
+ return {
18
+ children: Array.from({ length: colCount }).fill(colCount).map(() => api.create.tableCell(cellOptions)),
19
+ type: editor.getType(KEYS.tr)
20
+ };
21
+ };
22
+
23
+ //#endregion
24
+ //#region src/lib/api/getEmptyTableNode.ts
25
+ const getEmptyTableNode = (editor, { colCount, header, rowCount = 0, ...cellOptions } = {}) => {
26
+ const { api } = editor.getPlugin({ key: KEYS.table });
27
+ return {
28
+ children: Array.from({ length: rowCount }).fill(rowCount).map((_, index) => api.create.tableRow({
29
+ colCount,
30
+ ...cellOptions,
31
+ header: header && index === 0
32
+ })),
33
+ type: editor.getType(KEYS.table)
34
+ };
35
+ };
36
+
37
+ //#endregion
38
+ //#region src/lib/queries/getCellInNextTableRow.ts
39
+ const getCellInNextTableRow = (editor, currentRowPath) => {
40
+ const nextRow = editor.api.node(PathApi.next(currentRowPath));
41
+ if (!nextRow) return;
42
+ const [nextRowNode, nextRowPath] = nextRow;
43
+ const nextCell = nextRowNode?.children?.[0];
44
+ const nextCellPath = nextRowPath.concat(0);
45
+ if (nextCell && nextCellPath) return editor.api.node(nextCellPath);
46
+ };
47
+
48
+ //#endregion
49
+ //#region src/lib/queries/getCellInPreviousTableRow.ts
50
+ const getCellInPreviousTableRow = (editor, currentRowPath) => {
51
+ const prevPath = PathApi.previous(currentRowPath);
52
+ if (!prevPath) return;
53
+ const previousRow = editor.api.node(prevPath);
54
+ if (!previousRow) return;
55
+ const [previousRowNode, previousRowPath] = previousRow;
56
+ const previousCell = previousRowNode?.children?.[previousRowNode.children.length - 1];
57
+ const previousCellPath = previousRowPath.concat(previousRowNode.children.length - 1);
58
+ if (previousCell && previousCellPath) return editor.api.node(previousCellPath);
59
+ };
60
+
61
+ //#endregion
62
+ //#region src/lib/queries/getColSpan.ts
63
+ /**
64
+ * Returns the colspan attribute of the table cell element.
65
+ *
66
+ * @default 1 if undefined.
67
+ */
68
+ const getColSpan = (cellElem) => cellElem.colSpan || Number(cellElem.attributes?.colspan) || 1;
69
+
70
+ //#endregion
71
+ //#region src/lib/utils/computeCellIndices.ts
72
+ function computeCellIndices(editor, { all, cellNode, tableNode }) {
73
+ const { api, getOptions, setOption } = getEditorPlugin(editor, { key: KEYS.table });
74
+ if (!tableNode) {
75
+ if (!cellNode) return;
76
+ tableNode = editor.api.above({
77
+ at: cellNode,
78
+ match: { type: editor.getType(KEYS.table) }
79
+ })?.[0];
80
+ if (!tableNode) return;
81
+ }
82
+ const { _cellIndices: prevIndices } = getOptions();
83
+ const cellIndices = { ...prevIndices };
84
+ let hasIndicesChanged = false;
85
+ const skipCells = [];
86
+ let targetIndices;
87
+ for (let rowIndex = 0; rowIndex < tableNode.children.length; rowIndex++) {
88
+ const row = tableNode.children[rowIndex];
89
+ let colIndex = 0;
90
+ for (const cellElement of row.children) {
91
+ while (skipCells[rowIndex]?.[colIndex]) colIndex++;
92
+ const currentIndices = {
93
+ col: colIndex,
94
+ row: rowIndex
95
+ };
96
+ const prevIndicesForCell = prevIndices[cellElement.id];
97
+ if (prevIndicesForCell?.col !== currentIndices.col || prevIndicesForCell?.row !== currentIndices.row) hasIndicesChanged = true;
98
+ cellIndices[cellElement.id] = currentIndices;
99
+ if (cellElement.id === cellNode?.id) {
100
+ targetIndices = currentIndices;
101
+ if (!all) break;
102
+ }
103
+ const colSpan = api.table.getColSpan(cellElement);
104
+ const rowSpan = api.table.getRowSpan(cellElement);
105
+ for (let r = 0; r < rowSpan; r++) {
106
+ skipCells[rowIndex + r] = skipCells[rowIndex + r] || [];
107
+ for (let c = 0; c < colSpan; c++) skipCells[rowIndex + r][colIndex + c] = true;
108
+ }
109
+ colIndex += colSpan;
110
+ }
111
+ }
112
+ if (hasIndicesChanged) setOption("_cellIndices", cellIndices);
113
+ return targetIndices;
114
+ }
115
+
116
+ //#endregion
117
+ //#region src/lib/utils/getCellIndices.ts
118
+ const getCellIndices = (editor, element) => {
119
+ const { getOption } = getEditorPlugin(editor, { key: KEYS.table });
120
+ let indices = getOption("cellIndices", element.id);
121
+ if (!indices) {
122
+ indices = computeCellIndices(editor, { cellNode: element });
123
+ if (!indices) editor.api.debug.warn("No cell indices found for element. Make sure all table cells have an id.", "TABLE_CELL_INDICES");
124
+ }
125
+ return indices ?? {
126
+ col: 0,
127
+ row: 0
128
+ };
129
+ };
130
+
131
+ //#endregion
132
+ //#region src/lib/utils/getCellRowIndexByPath.ts
133
+ const getCellRowIndexByPath = (cellPath) => {
134
+ const index = cellPath.at(-2);
135
+ if (index === void 0) throw new Error(`can not get rowIndex of path ${cellPath}`);
136
+ return index;
137
+ };
138
+
139
+ //#endregion
140
+ //#region src/lib/utils/getCellType.ts
141
+ /** Get td and th types */
142
+ const getCellTypes = (editor) => getPluginTypes(editor, [KEYS.td, KEYS.th]);
143
+
144
+ //#endregion
145
+ //#region src/lib/queries/getLeftTableCell.ts
146
+ const getLeftTableCell = (editor, { at: cellPath } = {}) => {
147
+ if (!cellPath) {
148
+ cellPath = editor.api.node({ match: { type: getCellTypes(editor) } })?.[1];
149
+ if (!cellPath) return;
150
+ }
151
+ if (!cellPath.at(-1)) return;
152
+ const prevCellPath = PathApi.previous(cellPath);
153
+ return editor.api.node(prevCellPath);
154
+ };
155
+
156
+ //#endregion
157
+ //#region src/lib/queries/getNextTableCell.ts
158
+ const getNextTableCell = (editor, _currentCell, currentPath, currentRow) => {
159
+ const cell = editor.api.node(PathApi.next(currentPath));
160
+ if (cell) return cell;
161
+ const [, currentRowPath] = currentRow;
162
+ return getCellInNextTableRow(editor, currentRowPath);
163
+ };
164
+
165
+ //#endregion
166
+ //#region src/lib/queries/getPreviousTableCell.ts
167
+ const getPreviousTableCell = (editor, _currentCell, currentPath, currentRow) => {
168
+ const prevPath = PathApi.previous(currentPath);
169
+ if (!prevPath) {
170
+ const [, currentRowPath] = currentRow;
171
+ return getCellInPreviousTableRow(editor, currentRowPath);
172
+ }
173
+ const cell = editor.api.node(prevPath);
174
+ if (cell) return cell;
175
+ };
176
+
177
+ //#endregion
178
+ //#region src/lib/queries/getRowSpan.ts
179
+ /**
180
+ * Returns the rowspan attribute of the table cell element.
181
+ *
182
+ * @default 1 if undefined
183
+ */
184
+ const getRowSpan = (cellElem) => cellElem.rowSpan || Number(cellElem.attributes?.rowspan) || 1;
185
+
186
+ //#endregion
187
+ //#region src/lib/queries/getSelectedCellsBoundingBox.ts
188
+ /** Return bounding box [minRow..maxRow, minCol..maxCol] of all selected cells. */
189
+ function getSelectedCellsBoundingBox(editor, cells) {
190
+ let minRow = Number.POSITIVE_INFINITY;
191
+ let maxRow = Number.NEGATIVE_INFINITY;
192
+ let minCol = Number.POSITIVE_INFINITY;
193
+ let maxCol = Number.NEGATIVE_INFINITY;
194
+ for (const cell of cells) {
195
+ const { col, row } = getCellIndices(editor, cell);
196
+ const cSpan = getColSpan(cell);
197
+ const endRow = row + getRowSpan(cell) - 1;
198
+ const endCol = col + cSpan - 1;
199
+ if (row < minRow) minRow = row;
200
+ if (endRow > maxRow) maxRow = endRow;
201
+ if (col < minCol) minCol = col;
202
+ if (endCol > maxCol) maxCol = endCol;
203
+ }
204
+ return {
205
+ maxCol,
206
+ maxRow,
207
+ minCol,
208
+ minRow
209
+ };
210
+ }
211
+
212
+ //#endregion
213
+ //#region src/lib/queries/getTopTableCell.ts
214
+ const getTopTableCell = (editor, { at: cellPath } = {}) => {
215
+ if (!cellPath) {
216
+ cellPath = editor.api.node({ match: { type: getCellTypes(editor) } })?.[1];
217
+ if (!cellPath) return;
218
+ }
219
+ const cellIndex = cellPath.at(-1);
220
+ const rowIndex = cellPath.at(-2);
221
+ if (rowIndex === 0) return;
222
+ const cellAbovePath = [
223
+ ...PathApi.parent(PathApi.parent(cellPath)),
224
+ rowIndex - 1,
225
+ cellIndex
226
+ ];
227
+ return editor.api.node(cellAbovePath);
228
+ };
229
+
230
+ //#endregion
231
+ //#region src/lib/queries/getSelectedCellsBorders.ts
232
+ /**
233
+ * Get all border states for the selected cells at once. Returns an object with
234
+ * boolean flags for each border state:
235
+ *
236
+ * - Top/bottom/left/right: true if border is visible (size > 0)
237
+ * - Outer: true if all outer borders are visible
238
+ * - None: true if all borders are hidden (size === 0)
239
+ */
240
+ const getSelectedCellsBorders = (editor, selectedCells, options = {}) => {
241
+ const { select = {
242
+ none: true,
243
+ outer: true,
244
+ side: true
245
+ } } = options;
246
+ let cells = selectedCells;
247
+ if (!cells || cells.length === 0) {
248
+ const cell = editor.api.block({ match: { type: getCellTypes(editor) } });
249
+ if (cell) cells = [cell[0]];
250
+ else return {
251
+ bottom: true,
252
+ left: true,
253
+ none: false,
254
+ outer: true,
255
+ right: true,
256
+ top: true
257
+ };
258
+ }
259
+ const cellElements = cells.map((cell) => cell);
260
+ const { maxCol, maxRow, minCol, minRow } = getSelectedCellsBoundingBox(editor, cellElements);
261
+ let hasAnyBorder = false;
262
+ let allOuterBordersSet = true;
263
+ const borderStates = {
264
+ bottom: false,
265
+ left: false,
266
+ right: false,
267
+ top: false
268
+ };
269
+ for (const cell of cellElements) {
270
+ const { col, row } = getCellIndices(editor, cell);
271
+ const cellPath = editor.api.findPath(cell);
272
+ const cSpan = getColSpan(cell);
273
+ const rSpan = getRowSpan(cell);
274
+ const isFirstRow = row === 0;
275
+ const isFirstCell = col === 0;
276
+ if (!cellPath) continue;
277
+ if (select.none && !hasAnyBorder) {
278
+ if (isFirstRow && (cell.borders?.top?.size ?? 1) > 0) hasAnyBorder = true;
279
+ if (isFirstCell && (cell.borders?.left?.size ?? 1) > 0) hasAnyBorder = true;
280
+ if ((cell.borders?.bottom?.size ?? 1) > 0) hasAnyBorder = true;
281
+ if ((cell.borders?.right?.size ?? 1) > 0) hasAnyBorder = true;
282
+ if (!hasAnyBorder) {
283
+ if (!isFirstRow) {
284
+ const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
285
+ if (cellAboveEntry && (cellAboveEntry[0].borders?.bottom?.size ?? 1) > 0) hasAnyBorder = true;
286
+ }
287
+ if (!isFirstCell) {
288
+ const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
289
+ if (prevCellEntry && (prevCellEntry[0].borders?.right?.size ?? 1) > 0) hasAnyBorder = true;
290
+ }
291
+ }
292
+ }
293
+ if (select.side || select.outer) for (let rr = row; rr < row + rSpan; rr++) for (let cc = col; cc < col + cSpan; cc++) {
294
+ if (rr === minRow) if (isFirstRow) {
295
+ if ((cell.borders?.top?.size ?? 1) < 1) {
296
+ borderStates.top = false;
297
+ if (select.outer) allOuterBordersSet = false;
298
+ } else if (!borderStates.top) borderStates.top = true;
299
+ } else {
300
+ const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
301
+ if (cellAboveEntry) {
302
+ const [cellAbove] = cellAboveEntry;
303
+ if ((cellAbove.borders?.bottom?.size ?? 1) < 1) {
304
+ borderStates.top = false;
305
+ if (select.outer) allOuterBordersSet = false;
306
+ } else if (!borderStates.top) borderStates.top = true;
307
+ }
308
+ }
309
+ if (rr === maxRow) {
310
+ if ((cell.borders?.bottom?.size ?? 1) < 1) {
311
+ borderStates.bottom = false;
312
+ if (select.outer) allOuterBordersSet = false;
313
+ } else if (!borderStates.bottom) borderStates.bottom = true;
314
+ }
315
+ if (cc === minCol) if (isFirstCell) {
316
+ if ((cell.borders?.left?.size ?? 1) < 1) {
317
+ borderStates.left = false;
318
+ if (select.outer) allOuterBordersSet = false;
319
+ } else if (!borderStates.left) borderStates.left = true;
320
+ } else {
321
+ const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
322
+ if (prevCellEntry) {
323
+ const [prevCell] = prevCellEntry;
324
+ if ((prevCell.borders?.right?.size ?? 1) < 1) {
325
+ borderStates.left = false;
326
+ if (select.outer) allOuterBordersSet = false;
327
+ } else if (!borderStates.left) borderStates.left = true;
328
+ }
329
+ }
330
+ if (cc === maxCol) {
331
+ if ((cell.borders?.right?.size ?? 1) < 1) {
332
+ borderStates.right = false;
333
+ if (select.outer) allOuterBordersSet = false;
334
+ } else if (!borderStates.right) borderStates.right = true;
335
+ }
336
+ }
337
+ }
338
+ return {
339
+ ...select.side ? borderStates : {
340
+ bottom: true,
341
+ left: true,
342
+ right: true,
343
+ top: true
344
+ },
345
+ none: select.none ? !hasAnyBorder : false,
346
+ outer: select.outer ? allOuterBordersSet : true
347
+ };
348
+ };
349
+ /**
350
+ * Tells if the entire selection is currently borderless (size=0 on all edges).
351
+ * If **any** edge is > 0, returns false.
352
+ */
353
+ function isSelectedCellBordersNone(editor, cells) {
354
+ return cells.every((cell) => {
355
+ const { borders } = cell;
356
+ const { col, row } = getCellIndices(editor, cell);
357
+ const cellPath = editor.api.findPath(cell);
358
+ if (!cellPath) return true;
359
+ const isFirstRow = row === 0;
360
+ const isFirstCell = col === 0;
361
+ if (isFirstRow && (borders?.top?.size ?? 1) > 0) return false;
362
+ if (isFirstCell && (borders?.left?.size ?? 1) > 0) return false;
363
+ if ((borders?.bottom?.size ?? 1) > 0) return false;
364
+ if ((borders?.right?.size ?? 1) > 0) return false;
365
+ if (!isFirstRow) {
366
+ const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
367
+ if (cellAboveEntry) {
368
+ const [cellAbove] = cellAboveEntry;
369
+ if ((cellAbove.borders?.bottom?.size ?? 1) > 0) return false;
370
+ }
371
+ }
372
+ if (!isFirstCell) {
373
+ const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
374
+ if (prevCellEntry) {
375
+ const [prevCell] = prevCellEntry;
376
+ if ((prevCell.borders?.right?.size ?? 1) > 0) return false;
377
+ }
378
+ }
379
+ return true;
380
+ });
381
+ }
382
+ /**
383
+ * Tells if the bounding rectangle for the entire selection is fully set for the
384
+ * **outer** edges, i.e. top/left/bottom/right edges have size=1. We ignore
385
+ * internal edges, only bounding rectangle edges.
386
+ */
387
+ function isSelectedCellBordersOuter(editor, cells) {
388
+ const { maxCol, maxRow, minCol, minRow } = getSelectedCellsBoundingBox(editor, cells);
389
+ for (const cell of cells) {
390
+ const { col, row } = getCellIndices(editor, cell);
391
+ const cSpan = getColSpan(cell);
392
+ const rSpan = getRowSpan(cell);
393
+ for (let rr = row; rr < row + rSpan; rr++) for (let cc = col; cc < col + cSpan; cc++) {
394
+ if (rr === minRow && (cell.borders?.top?.size ?? 1) < 1) return false;
395
+ if (rr === maxRow && (cell.borders?.bottom?.size ?? 1) < 1) return false;
396
+ if (cc === minCol && (cell.borders?.left?.size ?? 1) < 1) return false;
397
+ if (cc === maxCol && (cell.borders?.right?.size ?? 1) < 1) return false;
398
+ }
399
+ }
400
+ return true;
401
+ }
402
+ /**
403
+ * Tells if the bounding rectangle for the entire selection is fully set for
404
+ * that single side. Example: border='top' => if every cell that sits along the
405
+ * top boundary has top=1.
406
+ */
407
+ function isSelectedCellBorder(editor, cells, side) {
408
+ const { maxCol, maxRow, minCol, minRow } = getSelectedCellsBoundingBox(editor, cells);
409
+ return cells.every((cell) => {
410
+ const { col, row } = getCellIndices(editor, cell);
411
+ const cSpan = getColSpan(cell);
412
+ const rSpan = getRowSpan(cell);
413
+ const cellPath = editor.api.findPath(cell);
414
+ if (!cellPath) return true;
415
+ for (let rr = row; rr < row + rSpan; rr++) for (let cc = col; cc < col + cSpan; cc++) {
416
+ if (side === "top" && rr === minRow) {
417
+ if (row === 0) return (cell.borders?.top?.size ?? 1) >= 1;
418
+ const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
419
+ if (!cellAboveEntry) return true;
420
+ const [cellAboveNode] = cellAboveEntry;
421
+ return (cellAboveNode.borders?.bottom?.size ?? 1) >= 1;
422
+ }
423
+ if (side === "bottom" && rr === maxRow) return (cell.borders?.bottom?.size ?? 1) >= 1;
424
+ if (side === "left" && cc === minCol) {
425
+ if (col === 0) return (cell.borders?.left?.size ?? 1) >= 1;
426
+ const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
427
+ if (!prevCellEntry) return true;
428
+ const [prevCellNode] = prevCellEntry;
429
+ return (prevCellNode.borders?.right?.size ?? 1) >= 1;
430
+ }
431
+ if (side === "right" && cc === maxCol) return (cell.borders?.right?.size ?? 1) >= 1;
432
+ }
433
+ return true;
434
+ });
435
+ }
436
+
437
+ //#endregion
438
+ //#region src/lib/queries/getTableAbove.ts
439
+ const getTableAbove = (editor, options) => editor.api.block({
440
+ above: true,
441
+ match: { type: editor.getType(KEYS.table) },
442
+ ...options
443
+ });
444
+
445
+ //#endregion
446
+ //#region src/lib/queries/getTableCellBorders.ts
447
+ const getTableCellBorders = (editor, { cellIndices, defaultBorder = { size: 1 }, element }) => {
448
+ const cellPath = editor.api.findPath(element);
449
+ const [rowNode, rowPath] = editor.api.parent(cellPath) ?? [];
450
+ if (!rowNode || !rowPath) return {
451
+ bottom: defaultBorder,
452
+ right: defaultBorder
453
+ };
454
+ const [tableNode] = editor.api.parent(rowPath);
455
+ const { col } = cellIndices ?? getCellIndices(editor, element);
456
+ const isFirstCell = col === 0;
457
+ const isFirstRow = tableNode.children?.[0] === rowNode;
458
+ const getBorder = (dir) => {
459
+ const border = element.borders?.[dir];
460
+ return {
461
+ color: border?.color ?? defaultBorder.color,
462
+ size: border?.size ?? defaultBorder.size,
463
+ style: border?.style ?? defaultBorder.style
464
+ };
465
+ };
466
+ return {
467
+ bottom: getBorder("bottom"),
468
+ left: isFirstCell ? getBorder("left") : void 0,
469
+ right: getBorder("right"),
470
+ top: isFirstRow ? getBorder("top") : void 0
471
+ };
472
+ };
473
+
474
+ //#endregion
475
+ //#region src/lib/queries/getTableCellSize.ts
476
+ /** Get the width of a cell with colSpan support. */
477
+ const getTableCellSize = (editor, { cellIndices, colSizes, element, rowSize }) => {
478
+ const { api } = getEditorPlugin(editor, { key: KEYS.table });
479
+ const path = editor.api.findPath(element);
480
+ if (!rowSize) {
481
+ const [rowElement] = editor.api.parent(path) ?? [];
482
+ if (!rowElement) return {
483
+ minHeight: 0,
484
+ width: 0
485
+ };
486
+ rowSize = rowElement.size;
487
+ }
488
+ if (!colSizes) {
489
+ const [, rowPath] = editor.api.parent(path);
490
+ const [tableNode] = editor.api.parent(rowPath);
491
+ colSizes = getTableOverriddenColSizes(tableNode);
492
+ }
493
+ const colSpan = api.table.getColSpan(element);
494
+ const { col } = cellIndices ?? getCellIndices(editor, element);
495
+ const width = (colSizes ?? []).slice(col, col + colSpan).reduce((total, w) => total + (w || 0), 0);
496
+ return {
497
+ minHeight: rowSize,
498
+ width
499
+ };
500
+ };
501
+
502
+ //#endregion
503
+ //#region src/lib/queries/getTableColumnCount.ts
504
+ const getTableColumnCount = (tableNode) => {
505
+ if (tableNode.children?.[0]?.children) return tableNode.children[0].children.map((element) => element.colSpan || (element?.attributes)?.colspan || 1).reduce((total, num) => Number(total) + Number(num));
506
+ return 0;
507
+ };
508
+
509
+ //#endregion
510
+ //#region src/lib/queries/getTableEntries.ts
511
+ /**
512
+ * If at (default = selection) is in table>tr>td|th, return table, row, and cell
513
+ * node entries.
514
+ */
515
+ const getTableEntries = (editor, { at = editor.selection } = {}) => {
516
+ if (!at) return;
517
+ const cellEntry = editor.api.node({
518
+ at,
519
+ match: { type: getCellTypes(editor) }
520
+ });
521
+ if (!cellEntry) return;
522
+ const [, cellPath] = cellEntry;
523
+ const rowEntry = editor.api.above({
524
+ at: cellPath,
525
+ match: { type: editor.getType(KEYS.tr) }
526
+ });
527
+ if (!rowEntry) return;
528
+ const [, rowPath] = rowEntry;
529
+ const tableEntry = editor.api.above({
530
+ at: rowPath,
531
+ match: { type: editor.getType(KEYS.table) }
532
+ });
533
+ if (!tableEntry) return;
534
+ return {
535
+ cell: cellEntry,
536
+ row: rowEntry,
537
+ table: tableEntry
538
+ };
539
+ };
540
+
541
+ //#endregion
542
+ //#region src/lib/merge/getCellIndicesWithSpans.ts
543
+ const getCellIndicesWithSpans = ({ col, row }, endCell) => ({
544
+ col: col + getColSpan(endCell) - 1,
545
+ row: row + getRowSpan(endCell) - 1
546
+ });
547
+
548
+ //#endregion
549
+ //#region src/lib/merge/findCellByIndexes.ts
550
+ const findCellByIndexes = (editor, table, searchRowIndex, searchColIndex) => {
551
+ return table.children.flatMap((current) => current.children).find((cellNode) => {
552
+ const indices = getCellIndices(editor, cellNode);
553
+ const { col: _startColIndex, row: _startRowIndex } = indices;
554
+ const { col: _endColIndex, row: _endRowIndex } = getCellIndicesWithSpans(indices, cellNode);
555
+ if (searchColIndex >= _startColIndex && searchColIndex <= _endColIndex && searchRowIndex >= _startRowIndex && searchRowIndex <= _endRowIndex) return true;
556
+ return false;
557
+ });
558
+ };
559
+
560
+ //#endregion
561
+ //#region src/lib/merge/getTableGridByRange.ts
562
+ /**
563
+ * Get sub table between 2 cell paths. Ensure that the selection is always a
564
+ * valid table grid.
565
+ */
566
+ const getTableMergeGridByRange = (editor, { at, format }) => {
567
+ const { api, type } = getEditorPlugin(editor, BaseTablePlugin);
568
+ const startCellEntry = editor.api.node({
569
+ at: at.anchor.path,
570
+ match: { type: getCellTypes(editor) }
571
+ });
572
+ const endCellEntry = editor.api.node({
573
+ at: at.focus.path,
574
+ match: { type: getCellTypes(editor) }
575
+ });
576
+ const startCell = startCellEntry[0];
577
+ const endCell = endCellEntry[0];
578
+ const tablePath = at.anchor.path.slice(0, -2);
579
+ const realTable = editor.api.node({
580
+ at: tablePath,
581
+ match: { type }
582
+ })[0];
583
+ const { col: _startColIndex, row: _startRowIndex } = getCellIndicesWithSpans(getCellIndices(editor, startCell), startCell);
584
+ const { col: _endColIndex, row: _endRowIndex } = getCellIndicesWithSpans(getCellIndices(editor, endCell), endCell);
585
+ let startRowIndex = Math.min(_startRowIndex, _endRowIndex);
586
+ let endRowIndex = Math.max(_startRowIndex, _endRowIndex);
587
+ let startColIndex = Math.min(_startColIndex, _endColIndex);
588
+ let endColIndex = Math.max(_startColIndex, _endColIndex);
589
+ const relativeRowIndex = endRowIndex - startRowIndex;
590
+ const relativeColIndex = endColIndex - startColIndex;
591
+ let table = api.create.table({
592
+ children: [],
593
+ colCount: relativeColIndex + 1,
594
+ rowCount: relativeRowIndex + 1
595
+ });
596
+ let cellEntries = [];
597
+ let cellsSet = /* @__PURE__ */ new WeakSet();
598
+ let rowIndex = startRowIndex;
599
+ let colIndex = startColIndex;
600
+ while (true) {
601
+ const cell = findCellByIndexes(editor, realTable, rowIndex, colIndex);
602
+ if (!cell) break;
603
+ const indicies = getCellIndices(editor, cell);
604
+ const { col: cellColWithSpan, row: cellRowWithSpan } = getCellIndicesWithSpans(indicies, cell);
605
+ const { col: cellCol, row: cellRow } = indicies;
606
+ if (cellRow < startRowIndex || cellRowWithSpan > endRowIndex || cellCol < startColIndex || cellColWithSpan > endColIndex) {
607
+ cellsSet = /* @__PURE__ */ new WeakSet();
608
+ cellEntries = [];
609
+ startRowIndex = Math.min(startRowIndex, cellRow);
610
+ endRowIndex = Math.max(endRowIndex, cellRowWithSpan);
611
+ startColIndex = Math.min(startColIndex, cellCol);
612
+ endColIndex = Math.max(endColIndex, cellColWithSpan);
613
+ rowIndex = startRowIndex;
614
+ colIndex = startColIndex;
615
+ const newRelativeRowIndex = endRowIndex - startRowIndex;
616
+ const newRelativeColIndex = endColIndex - startColIndex;
617
+ table = api.create.table({
618
+ children: [],
619
+ colCount: newRelativeColIndex + 1,
620
+ rowCount: newRelativeRowIndex + 1
621
+ });
622
+ continue;
623
+ }
624
+ if (!cellsSet.has(cell)) {
625
+ cellsSet.add(cell);
626
+ const rows = table.children[rowIndex - startRowIndex].children;
627
+ rows[colIndex - startColIndex] = cell;
628
+ const cellPath = editor.api.findPath(cell);
629
+ cellEntries.push([cell, cellPath]);
630
+ }
631
+ if (colIndex + 1 <= endColIndex) colIndex += 1;
632
+ else if (rowIndex + 1 <= endRowIndex) {
633
+ colIndex = startColIndex;
634
+ rowIndex += 1;
635
+ } else break;
636
+ }
637
+ const formatType = format || "table";
638
+ if (formatType === "cell") return cellEntries;
639
+ table.children?.forEach((rowEl) => {
640
+ const rowElement = rowEl;
641
+ rowElement.children = rowElement.children?.filter((cellEl) => {
642
+ const cellElement = cellEl;
643
+ return api.table.getCellChildren(cellElement).length > 0;
644
+ });
645
+ });
646
+ if (formatType === "table") return [[table, tablePath]];
647
+ return {
648
+ cellEntries,
649
+ tableEntries: [[table, tablePath]]
650
+ };
651
+ };
652
+
653
+ //#endregion
654
+ //#region src/lib/queries/getTableGridByRange.ts
655
+ /** Get sub table between 2 cell paths. */
656
+ const getTableGridByRange = (editor, { at, format = "table" }) => {
657
+ const { api } = editor.getPlugin({ key: KEYS.table });
658
+ const { disableMerge } = editor.getOptions(BaseTablePlugin);
659
+ if (!disableMerge) return getTableMergeGridByRange(editor, {
660
+ at,
661
+ format
662
+ });
663
+ const startCellPath = at.anchor.path;
664
+ const endCellPath = at.focus.path;
665
+ const _startRowIndex = startCellPath.at(-2);
666
+ const _endRowIndex = endCellPath.at(-2);
667
+ const _startColIndex = startCellPath.at(-1);
668
+ const _endColIndex = endCellPath.at(-1);
669
+ const startRowIndex = Math.min(_startRowIndex, _endRowIndex);
670
+ const endRowIndex = Math.max(_startRowIndex, _endRowIndex);
671
+ const startColIndex = Math.min(_startColIndex, _endColIndex);
672
+ const endColIndex = Math.max(_startColIndex, _endColIndex);
673
+ const tablePath = startCellPath.slice(0, -2);
674
+ const relativeRowIndex = endRowIndex - startRowIndex;
675
+ const relativeColIndex = endColIndex - startColIndex;
676
+ const table = api.create.table({
677
+ children: [],
678
+ colCount: relativeColIndex + 1,
679
+ rowCount: relativeRowIndex + 1
680
+ });
681
+ let rowIndex = startRowIndex;
682
+ let colIndex = startColIndex;
683
+ const cellEntries = [];
684
+ while (true) {
685
+ const cellPath = tablePath.concat([rowIndex, colIndex]);
686
+ const cell = NodeApi.get(editor, cellPath);
687
+ if (!cell) break;
688
+ const rows = table.children[rowIndex - startRowIndex].children;
689
+ rows[colIndex - startColIndex] = cell;
690
+ cellEntries.push([cell, cellPath]);
691
+ if (colIndex + 1 <= endColIndex) colIndex += 1;
692
+ else if (rowIndex + 1 <= endRowIndex) {
693
+ colIndex = startColIndex;
694
+ rowIndex += 1;
695
+ } else break;
696
+ }
697
+ if (format === "cell") return cellEntries;
698
+ return [[table, tablePath]];
699
+ };
700
+
701
+ //#endregion
702
+ //#region src/lib/queries/getTableGridAbove.ts
703
+ /** Get sub table above anchor and focus. Format: tables or cells. */
704
+ const getTableGridAbove = (editor, { format = "table", ...options } = {}) => {
705
+ const { api } = editor.getPlugin({ key: KEYS.table });
706
+ const edges = editor.api.edgeBlocks({
707
+ match: { type: getCellTypes(editor) },
708
+ ...options
709
+ });
710
+ if (edges) {
711
+ const [start, end] = edges;
712
+ if (!PathApi.equals(start[1], end[1])) return getTableGridByRange(editor, {
713
+ at: {
714
+ anchor: {
715
+ offset: 0,
716
+ path: start[1]
717
+ },
718
+ focus: {
719
+ offset: 0,
720
+ path: end[1]
721
+ }
722
+ },
723
+ format
724
+ });
725
+ if (format === "table") {
726
+ const table = api.create.table({ rowCount: 1 });
727
+ table.children[0].children = [start[0]];
728
+ return [[table, start[1].slice(0, -2)]];
729
+ }
730
+ return [start];
731
+ }
732
+ return [];
733
+ };
734
+
735
+ //#endregion
736
+ //#region src/lib/queries/getTableOverriddenColSizes.ts
737
+ /**
738
+ * Returns node.colSizes if it exists, applying overrides, otherwise returns a
739
+ * 0-filled array.
740
+ */
741
+ const getTableOverriddenColSizes = (tableNode, colSizeOverrides) => {
742
+ const colCount = getTableColumnCount(tableNode);
743
+ return (tableNode.colSizes ? [...tableNode.colSizes] : Array.from({ length: colCount }).fill(0)).map((size, index) => colSizeOverrides?.get?.(index) ?? size);
744
+ };
745
+
746
+ //#endregion
747
+ //#region src/lib/merge/deleteColumnWhenExpanded.ts
748
+ const deleteColumnWhenExpanded = (editor, tableEntry) => {
749
+ const [start, end] = RangeApi.edges(editor.selection);
750
+ const firstRow = NodeApi.child(tableEntry[0], 0);
751
+ const lastRow = NodeApi.child(tableEntry[0], tableEntry[0].children.length - 1);
752
+ const firstSelectionRow = editor.api.above({
753
+ at: start,
754
+ match: (n) => n.type === KEYS.tr
755
+ });
756
+ const lastSelectionRow = editor.api.above({
757
+ at: end,
758
+ match: (n) => n.type === KEYS.tr
759
+ });
760
+ if (!firstSelectionRow || !lastSelectionRow) return;
761
+ if (firstRow.id === firstSelectionRow[0].id && lastSelectionRow[0].id === lastRow.id) deleteSelection(editor);
762
+ };
763
+ const deleteSelection = (editor) => {
764
+ const cells = getTableGridAbove(editor, { format: "cell" });
765
+ const pathRefs = [];
766
+ cells.forEach(([_cell, cellPath]) => {
767
+ pathRefs.push(editor.api.pathRef(cellPath));
768
+ });
769
+ pathRefs.forEach((pathRef) => {
770
+ editor.tf.removeNodes({ at: pathRef.unref() });
771
+ });
772
+ };
773
+
774
+ //#endregion
775
+ //#region src/lib/merge/getCellPath.ts
776
+ const getCellPath = (editor, tableEntry, curRowIndex, curColIndex) => {
777
+ const [tableNode, tablePath] = tableEntry;
778
+ const foundColIndex = tableNode.children[curRowIndex].children.findIndex((c) => {
779
+ const { col: colIndex } = getCellIndices(editor, c);
780
+ return colIndex === curColIndex;
781
+ });
782
+ return tablePath.concat([curRowIndex, foundColIndex]);
783
+ };
784
+
785
+ //#endregion
786
+ //#region src/lib/merge/deleteColumn.ts
787
+ const deleteTableMergeColumn = (editor) => {
788
+ const type = editor.getType(KEYS.table);
789
+ const tableEntry = editor.api.above({ match: { type } });
790
+ if (!tableEntry) return;
791
+ editor.tf.withoutNormalizing(() => {
792
+ const { api } = getEditorPlugin(editor, BaseTablePlugin);
793
+ if (editor.api.isExpanded()) return deleteColumnWhenExpanded(editor, tableEntry);
794
+ const table = tableEntry[0];
795
+ const selectedCellEntry = editor.api.above({ match: { type: getCellTypes(editor) } });
796
+ if (!selectedCellEntry) return;
797
+ const selectedCell = selectedCellEntry[0];
798
+ const { col: deletingColIndex } = getCellIndices(editor, selectedCell);
799
+ const colsDeleteNumber = api.table.getColSpan(selectedCell);
800
+ const endingColIndex = deletingColIndex + colsDeleteNumber - 1;
801
+ const rowNumber = table.children.length;
802
+ const affectedCellsSet = /* @__PURE__ */ new Set();
803
+ for (const rI of Array.from({ length: rowNumber }, (_, i) => i)) for (const cI of Array.from({ length: colsDeleteNumber }, (_, i) => i)) {
804
+ const found = findCellByIndexes(editor, table, rI, deletingColIndex + cI);
805
+ if (found) affectedCellsSet.add(found);
806
+ }
807
+ const affectedCells = Array.from(affectedCellsSet);
808
+ const { squizeColSpanCells } = affectedCells.reduce((acc, cur) => {
809
+ if (!cur) return acc;
810
+ const currentCell = cur;
811
+ const { col: curColIndex } = getCellIndices(editor, currentCell);
812
+ const curColSpan = api.table.getColSpan(currentCell);
813
+ if (curColIndex < deletingColIndex && curColSpan > 1) acc.squizeColSpanCells.push(currentCell);
814
+ else if (curColSpan > 1 && curColIndex + curColSpan - 1 > endingColIndex) acc.squizeColSpanCells.push(currentCell);
815
+ return acc;
816
+ }, { squizeColSpanCells: [] });
817
+ /** Change colSpans */
818
+ squizeColSpanCells.forEach((cur) => {
819
+ const curCell = cur;
820
+ const { col: curColIndex, row: curColRowIndex } = getCellIndices(editor, curCell);
821
+ const curColSpan = api.table.getColSpan(curCell);
822
+ const curCellPath = getCellPath(editor, tableEntry, curColRowIndex, curColIndex);
823
+ const colSpan = curColSpan - (Math.min(curColIndex + curColSpan - 1, endingColIndex) - deletingColIndex + 1);
824
+ const newCell = cloneDeep({
825
+ ...curCell,
826
+ colSpan
827
+ });
828
+ if (newCell.attributes?.colspan) newCell.attributes.colspan = colSpan.toString();
829
+ editor.tf.setNodes(newCell, { at: curCellPath });
830
+ });
831
+ const trEntry = editor.api.above({ match: { type: editor.getType(KEYS.tr) } });
832
+ /** Remove cells */
833
+ if (selectedCell && trEntry && tableEntry && trEntry[0].children.length > 1) {
834
+ const [tableNode, tablePath] = tableEntry;
835
+ const paths = [];
836
+ affectedCells.forEach((cur) => {
837
+ const curCell = cur;
838
+ const { col: curColIndex, row: curRowIndex } = getCellIndices(editor, curCell);
839
+ if (!squizeColSpanCells.includes(curCell) && curColIndex >= deletingColIndex && curColIndex <= endingColIndex) {
840
+ const cellPath = getCellPath(editor, tableEntry, curRowIndex, curColIndex);
841
+ if (!paths[curRowIndex]) paths[curRowIndex] = [];
842
+ paths[curRowIndex].push(cellPath);
843
+ }
844
+ });
845
+ paths.forEach((cellPaths) => {
846
+ const pathToDelete = cellPaths[0];
847
+ cellPaths.forEach(() => {
848
+ editor.tf.removeNodes({ at: pathToDelete });
849
+ });
850
+ });
851
+ const { colSizes } = tableNode;
852
+ if (colSizes) {
853
+ const newColSizes = [...colSizes];
854
+ newColSizes.splice(deletingColIndex, 1);
855
+ editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
856
+ }
857
+ }
858
+ });
859
+ };
860
+
861
+ //#endregion
862
+ //#region src/lib/merge/deleteRowWhenExpanded.ts
863
+ const deleteRowWhenExpanded = (editor, [table, tablePath]) => {
864
+ const { api } = getEditorPlugin(editor, BaseTablePlugin);
865
+ const columnCount = getTableMergedColumnCount(table);
866
+ const cells = getTableGridAbove(editor, { format: "cell" });
867
+ const firsRowIndex = getCellRowIndexByPath(cells[0][1]);
868
+ if (firsRowIndex === null) return;
869
+ let acrossColumn = 0;
870
+ let lastRowIndex = -1;
871
+ let rowSpanCarry = 0;
872
+ let acrossRow = 0;
873
+ cells.forEach(([cell, cellPath]) => {
874
+ if (cellPath.at(-2) === firsRowIndex) acrossColumn += cell.colSpan ?? 1;
875
+ const currentRowIndex = getCellRowIndexByPath(cellPath);
876
+ if (lastRowIndex !== currentRowIndex) {
877
+ if (rowSpanCarry !== 0) {
878
+ rowSpanCarry--;
879
+ return;
880
+ }
881
+ const rowSpan = api.table.getRowSpan(cell);
882
+ rowSpanCarry = rowSpan && rowSpan > 1 ? rowSpan - 1 : 0;
883
+ acrossRow += rowSpan ?? 1;
884
+ }
885
+ lastRowIndex = currentRowIndex;
886
+ });
887
+ if (acrossColumn === columnCount) {
888
+ const pathRefs = [];
889
+ for (let i = firsRowIndex; i < firsRowIndex + acrossRow; i++) {
890
+ const removedPath = tablePath.concat(i);
891
+ pathRefs.push(editor.api.pathRef(removedPath));
892
+ }
893
+ pathRefs.forEach((item) => {
894
+ editor.tf.removeNodes({ at: item.unref() });
895
+ });
896
+ }
897
+ };
898
+
899
+ //#endregion
900
+ //#region src/lib/merge/deleteRow.ts
901
+ const deleteTableMergeRow = (editor) => {
902
+ const { api, tf, type } = getEditorPlugin(editor, { key: KEYS.table });
903
+ if (editor.api.some({ match: { type } })) {
904
+ const currentTableItem = editor.api.above({ match: { type } });
905
+ if (!currentTableItem) return;
906
+ if (editor.api.isExpanded()) return deleteRowWhenExpanded(editor, currentTableItem);
907
+ const table = currentTableItem[0];
908
+ const selectedCellEntry = editor.api.above({ match: { type: getCellTypes(editor) } });
909
+ if (!selectedCellEntry) return;
910
+ const selectedCell = selectedCellEntry[0];
911
+ const { row: deletingRowIndex } = getCellIndices(editor, selectedCell);
912
+ const rowsDeleteNumber = api.table.getRowSpan(selectedCell);
913
+ const endingRowIndex = deletingRowIndex + rowsDeleteNumber - 1;
914
+ const colNumber = getTableColumnCount(table);
915
+ const affectedCellsSet = /* @__PURE__ */ new Set();
916
+ for (const cI of Array.from({ length: colNumber }, (_, i) => i)) for (const rI of Array.from({ length: rowsDeleteNumber }, (_, i) => i)) {
917
+ const found = findCellByIndexes(editor, table, deletingRowIndex + rI, cI);
918
+ affectedCellsSet.add(found);
919
+ }
920
+ const { moveToNextRowCells, squizeRowSpanCells } = Array.from(affectedCellsSet).reduce((acc, cur) => {
921
+ if (!cur) return acc;
922
+ const currentCell = cur;
923
+ const { row: curRowIndex } = getCellIndices(editor, currentCell);
924
+ const curRowSpan = api.table.getRowSpan(currentCell);
925
+ if (curRowIndex < deletingRowIndex && curRowSpan > 1) acc.squizeRowSpanCells.push(currentCell);
926
+ else if (curRowSpan > 1 && curRowIndex + curRowSpan - 1 > endingRowIndex) acc.moveToNextRowCells.push(currentCell);
927
+ return acc;
928
+ }, {
929
+ moveToNextRowCells: [],
930
+ squizeRowSpanCells: []
931
+ });
932
+ const nextRowIndex = deletingRowIndex + rowsDeleteNumber;
933
+ const nextRow = table.children[nextRowIndex];
934
+ if (nextRow === void 0 && deletingRowIndex === 0) {
935
+ tf.remove.table();
936
+ return;
937
+ }
938
+ if (nextRow) for (let index = 0; index < moveToNextRowCells.length; index++) {
939
+ const curRowCell = moveToNextRowCells[index];
940
+ const { col: curRowCellColIndex, row: curRowCellRowIndex } = getCellIndices(editor, curRowCell);
941
+ const curRowCellRowSpan = api.table.getRowSpan(curRowCell);
942
+ const startingCellIndex = nextRow.children.findIndex((curC) => {
943
+ const { col: curColIndex } = getCellIndices(editor, curC);
944
+ return curColIndex >= curRowCellColIndex;
945
+ });
946
+ if (startingCellIndex === -1) {
947
+ const startingCell$1 = nextRow.children.at(-1);
948
+ const startingCellPath$1 = editor.api.findPath(startingCell$1);
949
+ const tablePath$1 = startingCellPath$1.slice(0, -2);
950
+ const colPath$1 = startingCellPath$1.at(-1) + index + 1;
951
+ const nextRowStartCellPath$1 = [
952
+ ...tablePath$1,
953
+ nextRowIndex,
954
+ colPath$1
955
+ ];
956
+ const rowSpan$1 = curRowCellRowSpan - (endingRowIndex - curRowCellRowIndex + 1);
957
+ const newCell$1 = cloneDeep({
958
+ ...curRowCell,
959
+ rowSpan: rowSpan$1
960
+ });
961
+ if (newCell$1.attributes?.rowspan) newCell$1.attributes.rowspan = rowSpan$1.toString();
962
+ editor.tf.insertNodes(newCell$1, { at: nextRowStartCellPath$1 });
963
+ continue;
964
+ }
965
+ const startingCell = nextRow.children[startingCellIndex];
966
+ const { col: startingColIndex } = getCellIndices(editor, startingCell);
967
+ let incrementBy = index;
968
+ if (startingColIndex < curRowCellColIndex) incrementBy += 1;
969
+ const startingCellPath = editor.api.findPath(startingCell);
970
+ const tablePath = startingCellPath.slice(0, -2);
971
+ const colPath = startingCellPath.at(-1);
972
+ const nextRowStartCellPath = [
973
+ ...tablePath,
974
+ nextRowIndex,
975
+ colPath + incrementBy
976
+ ];
977
+ const rowSpan = curRowCellRowSpan - (endingRowIndex - curRowCellRowIndex + 1);
978
+ const newCell = cloneDeep({
979
+ ...curRowCell,
980
+ rowSpan
981
+ });
982
+ if (newCell.attributes?.rowspan) newCell.attributes.rowspan = rowSpan.toString();
983
+ editor.tf.insertNodes(newCell, { at: nextRowStartCellPath });
984
+ }
985
+ squizeRowSpanCells.forEach((cur) => {
986
+ const curRowCell = cur;
987
+ const { row: curRowCellRowIndex } = getCellIndices(editor, curRowCell);
988
+ const curRowCellRowSpan = api.table.getRowSpan(curRowCell);
989
+ const curCellPath = editor.api.findPath(curRowCell);
990
+ const rowSpan = curRowCellRowSpan - (Math.min(curRowCellRowIndex + curRowCellRowSpan - 1, endingRowIndex) - deletingRowIndex + 1);
991
+ const newCell = cloneDeep({
992
+ ...curRowCell,
993
+ rowSpan
994
+ });
995
+ if (newCell.attributes?.rowspan) newCell.attributes.rowspan = rowSpan.toString();
996
+ editor.tf.setNodes(newCell, { at: curCellPath });
997
+ });
998
+ const rowToDelete = table.children[deletingRowIndex];
999
+ const rowPath = editor.api.findPath(rowToDelete);
1000
+ Array.from({ length: rowsDeleteNumber }).forEach(() => {
1001
+ editor.tf.removeNodes({ at: rowPath });
1002
+ });
1003
+ }
1004
+ };
1005
+
1006
+ //#endregion
1007
+ //#region src/lib/merge/getTableMergedColumnCount.ts
1008
+ const getTableMergedColumnCount = (tableNode) => tableNode.children?.[0]?.children?.reduce((prev, cur) => prev + (getColSpan(cur) ?? 1), 0);
1009
+
1010
+ //#endregion
1011
+ //#region src/lib/merge/insertTableColumn.ts
1012
+ const insertTableMergeColumn = (editor, { at, before, fromCell, header, select: shouldSelect } = {}) => {
1013
+ const { api, getOptions, type } = getEditorPlugin(editor, BaseTablePlugin);
1014
+ const { initialTableWidth, minColumnWidth } = getOptions();
1015
+ if (at && !fromCell) {
1016
+ if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
1017
+ fromCell = NodeApi.lastChild(editor, at.concat([0]))[1];
1018
+ at = void 0;
1019
+ }
1020
+ }
1021
+ const cellEntry = fromCell ? editor.api.node({
1022
+ at: fromCell,
1023
+ match: { type: getCellTypes(editor) }
1024
+ }) : editor.api.block({ match: { type: getCellTypes(editor) } });
1025
+ if (!cellEntry) return;
1026
+ const [, cellPath] = cellEntry;
1027
+ const cell = cellEntry[0];
1028
+ const tableEntry = editor.api.block({
1029
+ above: true,
1030
+ at: cellPath,
1031
+ match: { type }
1032
+ });
1033
+ if (!tableEntry) return;
1034
+ const [tableNode, tablePath] = tableEntry;
1035
+ const { col: cellColIndex } = getCellIndices(editor, cell);
1036
+ const cellColSpan = api.table.getColSpan(cell);
1037
+ let nextColIndex;
1038
+ let checkingColIndex;
1039
+ if (PathApi.isPath(at)) {
1040
+ nextColIndex = cellColIndex;
1041
+ checkingColIndex = cellColIndex - 1;
1042
+ } else {
1043
+ nextColIndex = before ? cellColIndex : cellColIndex + cellColSpan;
1044
+ checkingColIndex = before ? cellColIndex : cellColIndex + cellColSpan - 1;
1045
+ }
1046
+ const rowNumber = tableNode.children.length;
1047
+ const firstCol = nextColIndex <= 0;
1048
+ let placementCorrection = before ? 0 : 1;
1049
+ if (firstCol) {
1050
+ checkingColIndex = 0;
1051
+ placementCorrection = 0;
1052
+ }
1053
+ const affectedCellsSet = /* @__PURE__ */ new Set();
1054
+ Array.from({ length: rowNumber }, (_, i) => i).forEach((rI) => {
1055
+ const found = findCellByIndexes(editor, tableNode, rI, checkingColIndex);
1056
+ if (found) affectedCellsSet.add(found);
1057
+ });
1058
+ Array.from(affectedCellsSet).forEach((curCell) => {
1059
+ const { col: curColIndex, row: curRowIndex } = getCellIndices(editor, curCell);
1060
+ const curRowSpan = api.table.getRowSpan(curCell);
1061
+ const curColSpan = api.table.getColSpan(curCell);
1062
+ const currentCellPath = getCellPath(editor, tableEntry, curRowIndex, curColIndex);
1063
+ if (curColIndex + curColSpan - 1 >= nextColIndex && !firstCol && !before) {
1064
+ const colSpan = curColSpan + 1;
1065
+ const newCell = cloneDeep({
1066
+ ...curCell,
1067
+ colSpan
1068
+ });
1069
+ if (newCell.attributes?.colspan) newCell.attributes.colspan = colSpan.toString();
1070
+ editor.tf.setNodes(newCell, { at: currentCellPath });
1071
+ } else {
1072
+ const curRowPath = currentCellPath.slice(0, -1);
1073
+ const curColPath = currentCellPath.at(-1);
1074
+ const placementPath = [...curRowPath, before ? curColPath : curColPath + placementCorrection];
1075
+ const rowElement = editor.api.parent(currentCellPath)[0];
1076
+ const emptyCell = {
1077
+ ...api.create.tableCell({
1078
+ header,
1079
+ row: rowElement
1080
+ }),
1081
+ colSpan: 1,
1082
+ rowSpan: curRowSpan
1083
+ };
1084
+ editor.tf.insertNodes(emptyCell, {
1085
+ at: placementPath,
1086
+ select: shouldSelect
1087
+ });
1088
+ }
1089
+ });
1090
+ editor.tf.withoutNormalizing(() => {
1091
+ const { colSizes } = tableNode;
1092
+ if (colSizes) {
1093
+ let newColSizes = [
1094
+ ...colSizes.slice(0, nextColIndex),
1095
+ 0,
1096
+ ...colSizes.slice(nextColIndex)
1097
+ ];
1098
+ if (initialTableWidth) {
1099
+ newColSizes[nextColIndex] = colSizes[nextColIndex] ?? colSizes[nextColIndex - 1] ?? initialTableWidth / colSizes.length;
1100
+ const oldTotal = colSizes.reduce((a, b) => a + b, 0);
1101
+ const newTotal = newColSizes.reduce((a, b) => a + b, 0);
1102
+ const maxTotal = Math.max(oldTotal, initialTableWidth);
1103
+ if (newTotal > maxTotal) {
1104
+ const factor = maxTotal / newTotal;
1105
+ newColSizes = newColSizes.map((size) => Math.max(minColumnWidth ?? 0, Math.floor(size * factor)));
1106
+ }
1107
+ }
1108
+ editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
1109
+ }
1110
+ });
1111
+ };
1112
+
1113
+ //#endregion
1114
+ //#region src/lib/merge/insertTableRow.ts
1115
+ const insertTableMergeRow = (editor, { at, before, fromRow, header, select: shouldSelect } = {}) => {
1116
+ const { api, type } = getEditorPlugin(editor, BaseTablePlugin);
1117
+ if (at && !fromRow) {
1118
+ if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
1119
+ fromRow = NodeApi.lastChild(editor, at)[1];
1120
+ at = void 0;
1121
+ }
1122
+ }
1123
+ const trEntry = editor.api.block({
1124
+ at: fromRow,
1125
+ match: { type: editor.getType(KEYS.tr) }
1126
+ });
1127
+ if (!trEntry) return;
1128
+ const [, trPath] = trEntry;
1129
+ const tableEntry = editor.api.block({
1130
+ above: true,
1131
+ at: trPath,
1132
+ match: { type }
1133
+ });
1134
+ if (!tableEntry) return;
1135
+ const tableNode = tableEntry[0];
1136
+ const cellEntry = editor.api.node({
1137
+ at: fromRow,
1138
+ match: { type: getCellTypes(editor) }
1139
+ });
1140
+ if (!cellEntry) return;
1141
+ const [cellNode, cellPath] = cellEntry;
1142
+ const cellElement = cellNode;
1143
+ const cellRowSpan = api.table.getRowSpan(cellElement);
1144
+ const { row: cellRowIndex } = getCellIndices(editor, cellElement);
1145
+ const rowPath = cellPath.at(-2);
1146
+ const tablePath = cellPath.slice(0, -2);
1147
+ let nextRowIndex;
1148
+ let checkingRowIndex;
1149
+ let nextRowPath;
1150
+ if (PathApi.isPath(at)) {
1151
+ nextRowIndex = at.at(-1);
1152
+ checkingRowIndex = cellRowIndex - 1;
1153
+ nextRowPath = at;
1154
+ } else {
1155
+ nextRowIndex = before ? cellRowIndex : cellRowIndex + cellRowSpan;
1156
+ checkingRowIndex = before ? cellRowIndex - 1 : cellRowIndex + cellRowSpan - 1;
1157
+ nextRowPath = [...tablePath, before ? rowPath : rowPath + cellRowSpan];
1158
+ }
1159
+ const firstRow = nextRowIndex === 0;
1160
+ if (firstRow) checkingRowIndex = 0;
1161
+ const colCount = getTableColumnCount(tableNode);
1162
+ const affectedCellsSet = /* @__PURE__ */ new Set();
1163
+ Array.from({ length: colCount }, (_, i) => i).forEach((cI) => {
1164
+ const found = findCellByIndexes(editor, tableNode, checkingRowIndex, cI);
1165
+ if (found) affectedCellsSet.add(found);
1166
+ });
1167
+ const affectedCells = Array.from(affectedCellsSet);
1168
+ const newRowChildren = [];
1169
+ affectedCells.forEach((cur) => {
1170
+ if (!cur) return;
1171
+ const curCell = cur;
1172
+ const { col: curColIndex, row: curRowIndex } = getCellIndices(editor, curCell);
1173
+ const curRowSpan = api.table.getRowSpan(curCell);
1174
+ const curColSpan = api.table.getColSpan(curCell);
1175
+ const currentCellPath = getCellPath(editor, tableEntry, curRowIndex, curColIndex);
1176
+ if (curRowIndex + curRowSpan - 1 >= nextRowIndex && !firstRow) {
1177
+ const rowSpan = curRowSpan + 1;
1178
+ const newCell = cloneDeep({
1179
+ ...curCell,
1180
+ rowSpan
1181
+ });
1182
+ if (newCell.attributes?.rowspan) newCell.attributes.rowspan = rowSpan.toString();
1183
+ editor.tf.setNodes(newCell, { at: currentCellPath });
1184
+ } else {
1185
+ const rowElement = editor.api.parent(currentCellPath)[0];
1186
+ const emptyCell = api.create.tableCell({
1187
+ header,
1188
+ row: rowElement
1189
+ });
1190
+ newRowChildren.push({
1191
+ ...emptyCell,
1192
+ colSpan: curColSpan,
1193
+ rowSpan: 1
1194
+ });
1195
+ }
1196
+ });
1197
+ editor.tf.withoutNormalizing(() => {
1198
+ editor.tf.insertNodes({
1199
+ children: newRowChildren,
1200
+ type: editor.getType(KEYS.tr)
1201
+ }, {
1202
+ at: nextRowPath,
1203
+ select: false
1204
+ });
1205
+ if (shouldSelect) {
1206
+ const cellEntry$1 = editor.api.node({
1207
+ at: nextRowPath,
1208
+ match: { type: getCellTypes(editor) }
1209
+ });
1210
+ if (cellEntry$1) {
1211
+ const [, nextCellPath] = cellEntry$1;
1212
+ editor.tf.select(nextCellPath);
1213
+ }
1214
+ }
1215
+ });
1216
+ };
1217
+
1218
+ //#endregion
1219
+ //#region src/lib/merge/isTableRectangular.ts
1220
+ const allEqual = (arr) => arr.every((val) => val === arr[0]);
1221
+ /**
1222
+ * Checks if the given table is rectangular, meaning all rows have the same
1223
+ * effective number of cells, considering colspan and rowspan.
1224
+ */
1225
+ const isTableRectangular = (table) => {
1226
+ const arr = [];
1227
+ table?.children?.forEach((row, rI) => {
1228
+ row.children?.forEach((cell) => {
1229
+ const cellElem = cell;
1230
+ Array.from({ length: getRowSpan(cellElem) || 1 }).forEach((_, i) => {
1231
+ if (!arr[rI + i]) arr[rI + i] = 0;
1232
+ arr[rI + i] += getColSpan(cellElem);
1233
+ });
1234
+ });
1235
+ });
1236
+ return allEqual(arr);
1237
+ };
1238
+
1239
+ //#endregion
1240
+ //#region src/lib/merge/mergeTableCells.ts
1241
+ /** Merges multiple selected cells into one. */
1242
+ const mergeTableCells = (editor) => {
1243
+ const { api } = getEditorPlugin(editor, BaseTablePlugin);
1244
+ const cellEntries = getTableGridAbove(editor, { format: "cell" });
1245
+ editor.tf.withoutNormalizing(() => {
1246
+ let colSpan = 0;
1247
+ for (const entry of cellEntries) {
1248
+ const [cell, path] = entry;
1249
+ if (path.at(-2) === cellEntries[0][1].at(-2)) {
1250
+ const cellColSpan = api.table.getColSpan(cell);
1251
+ colSpan += cellColSpan;
1252
+ }
1253
+ }
1254
+ let rowSpan = 0;
1255
+ const { col } = getCellIndices(editor, cellEntries[0][0]);
1256
+ cellEntries.forEach((entry) => {
1257
+ const cell = entry[0];
1258
+ const { col: curCol } = getCellIndices(editor, cell);
1259
+ if (col === curCol) rowSpan += api.table.getRowSpan(cell);
1260
+ });
1261
+ const mergingCellChildren = [];
1262
+ for (const cellEntry of cellEntries) {
1263
+ const [el] = cellEntry;
1264
+ const cellChildren = api.table.getCellChildren(el);
1265
+ if (cellChildren.length !== 1 || !editor.api.isEmpty(cellChildren[0])) mergingCellChildren.push(...cloneDeep(cellChildren));
1266
+ }
1267
+ const cols = {};
1268
+ cellEntries.forEach(([_entry, path]) => {
1269
+ const rowIndex = path.at(-2);
1270
+ if (cols[rowIndex]) cols[rowIndex].push(path);
1271
+ else cols[rowIndex] = [path];
1272
+ });
1273
+ Object.values(cols).forEach((paths) => {
1274
+ paths?.forEach(() => {
1275
+ editor.tf.removeNodes({ at: paths[0] });
1276
+ });
1277
+ });
1278
+ const mergedCell = {
1279
+ ...api.create.tableCell({
1280
+ children: mergingCellChildren,
1281
+ header: cellEntries[0][0].type === editor.getType(KEYS.th)
1282
+ }),
1283
+ colSpan,
1284
+ rowSpan
1285
+ };
1286
+ editor.tf.insertNodes(mergedCell, { at: cellEntries[0][1] });
1287
+ });
1288
+ editor.tf.select(editor.api.end(cellEntries[0][1]));
1289
+ };
1290
+
1291
+ //#endregion
1292
+ //#region src/lib/merge/splitTableCell.ts
1293
+ const splitTableCell = (editor) => {
1294
+ const { api } = getEditorPlugin(editor, BaseTablePlugin);
1295
+ const tableRowType = editor.getType(KEYS.tr);
1296
+ const [[cellElem, path]] = getTableGridAbove(editor, { format: "cell" });
1297
+ editor.tf.withoutNormalizing(() => {
1298
+ const createEmptyCell = (children) => ({
1299
+ ...api.create.tableCell({
1300
+ children,
1301
+ header: cellElem.type === editor.getType(KEYS.th)
1302
+ }),
1303
+ colSpan: 1,
1304
+ rowSpan: 1
1305
+ });
1306
+ const tablePath = path.slice(0, -2);
1307
+ const [rowPath, colPath] = path.slice(-2);
1308
+ const colSpan = api.table.getColSpan(cellElem);
1309
+ const rowSpan = api.table.getRowSpan(cellElem);
1310
+ const colPaths = [];
1311
+ for (let i = 0; i < colSpan; i++) colPaths.push(colPath + i);
1312
+ const { col } = getCellIndices(editor, cellElem);
1313
+ editor.tf.removeNodes({ at: path });
1314
+ const getClosestColPathForRow = (row, targetCol) => {
1315
+ const rowEntry = editor.api.node({
1316
+ at: [...tablePath, row],
1317
+ match: { type: tableRowType }
1318
+ });
1319
+ if (!rowEntry) return 0;
1320
+ const rowEl = rowEntry[0];
1321
+ let closestColPath = [];
1322
+ let smallestDiff = Number.POSITIVE_INFINITY;
1323
+ let isDirectionLeft = false;
1324
+ rowEl.children.forEach((cell) => {
1325
+ const cellElement = cell;
1326
+ const { col: cellCol } = getCellIndices(editor, cellElement);
1327
+ const diff = Math.abs(cellCol - targetCol);
1328
+ if (diff < smallestDiff) {
1329
+ smallestDiff = diff;
1330
+ closestColPath = editor.api.findPath(cellElement);
1331
+ isDirectionLeft = cellCol < targetCol;
1332
+ }
1333
+ });
1334
+ if (closestColPath.length > 0) {
1335
+ const lastIndex = closestColPath.at(-1);
1336
+ if (isDirectionLeft) return lastIndex + 1;
1337
+ return lastIndex;
1338
+ }
1339
+ return 1;
1340
+ };
1341
+ for (let i = 0; i < rowSpan; i++) {
1342
+ const currentRowPath = rowPath + i;
1343
+ const pathForNextRows = getClosestColPathForRow(currentRowPath, col);
1344
+ const newRowChildren = [];
1345
+ const _rowPath = [...tablePath, currentRowPath];
1346
+ const rowEntry = editor.api.node({
1347
+ at: _rowPath,
1348
+ match: { type: tableRowType }
1349
+ });
1350
+ for (let j = 0; j < colPaths.length; j++) {
1351
+ const cellChildren = api.table.getCellChildren(cellElem);
1352
+ const cellToInsert = i === 0 && j === 0 ? createEmptyCell(cellChildren) : createEmptyCell();
1353
+ if (rowEntry) {
1354
+ const currentColPath = i === 0 ? colPaths[j] : pathForNextRows;
1355
+ const pathForNewCell = [
1356
+ ...tablePath,
1357
+ currentRowPath,
1358
+ currentColPath
1359
+ ];
1360
+ editor.tf.insertNodes(cellToInsert, { at: pathForNewCell });
1361
+ } else newRowChildren.push(cellToInsert);
1362
+ }
1363
+ if (!rowEntry) editor.tf.insertNodes({
1364
+ children: newRowChildren,
1365
+ type: editor.getType(KEYS.tr)
1366
+ }, { at: _rowPath });
1367
+ }
1368
+ });
1369
+ editor.tf.select(editor.api.end(path));
1370
+ };
1371
+
1372
+ //#endregion
1373
+ //#region src/lib/normalizeInitialValueTable.ts
1374
+ const normalizeInitialValueTable = ({ editor, type }) => {
1375
+ const tables = editor.api.nodes({
1376
+ at: [],
1377
+ match: { type }
1378
+ });
1379
+ for (const [table] of tables) computeCellIndices(editor, { tableNode: table });
1380
+ };
1381
+
1382
+ //#endregion
1383
+ //#region src/lib/transforms/deleteColumn.ts
1384
+ const deleteColumn = (editor) => {
1385
+ const { getOptions, type } = getEditorPlugin(editor, { key: KEYS.table });
1386
+ const { disableMerge } = getOptions();
1387
+ const tableEntry = editor.api.above({ match: { type } });
1388
+ if (!tableEntry) return;
1389
+ editor.tf.withoutNormalizing(() => {
1390
+ if (!disableMerge) {
1391
+ deleteTableMergeColumn(editor);
1392
+ return;
1393
+ }
1394
+ if (editor.api.isExpanded()) return deleteColumnWhenExpanded(editor, tableEntry);
1395
+ const tdEntry = editor.api.above({ match: { type: getCellTypes(editor) } });
1396
+ const trEntry = editor.api.above({ match: { type: editor.getType(KEYS.tr) } });
1397
+ if (tdEntry && trEntry && tableEntry && trEntry[0].children.length > 1) {
1398
+ const [tableNode, tablePath] = tableEntry;
1399
+ const tdPath = tdEntry[1];
1400
+ const colIndex = tdPath.at(-1);
1401
+ const pathToDelete = tdPath.slice();
1402
+ const replacePathPos = pathToDelete.length - 2;
1403
+ tableNode.children.forEach((row, rowIdx) => {
1404
+ pathToDelete[replacePathPos] = rowIdx;
1405
+ if (row.children.length === 1 || colIndex > row.children.length - 1) return;
1406
+ editor.tf.removeNodes({ at: pathToDelete });
1407
+ });
1408
+ const { colSizes } = tableNode;
1409
+ if (colSizes) {
1410
+ const newColSizes = [...colSizes];
1411
+ newColSizes.splice(colIndex, 1);
1412
+ editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
1413
+ }
1414
+ }
1415
+ });
1416
+ };
1417
+
1418
+ //#endregion
1419
+ //#region src/lib/transforms/deleteRow.ts
1420
+ const deleteRow = (editor) => {
1421
+ const { getOptions, type } = getEditorPlugin(editor, { key: KEYS.table });
1422
+ const { disableMerge } = getOptions();
1423
+ if (!disableMerge) return deleteTableMergeRow(editor);
1424
+ if (editor.api.some({ match: { type } })) {
1425
+ const currentTableItem = editor.api.above({ match: { type } });
1426
+ if (!currentTableItem) return;
1427
+ if (editor.api.isExpanded()) return deleteRowWhenExpanded(editor, currentTableItem);
1428
+ const currentRowItem = editor.api.above({ match: { type: editor.getType(KEYS.tr) } });
1429
+ if (currentRowItem && currentTableItem && currentTableItem[0].children.length > 1) editor.tf.removeNodes({ at: currentRowItem[1] });
1430
+ }
1431
+ };
1432
+
1433
+ //#endregion
1434
+ //#region src/lib/transforms/deleteTable.ts
1435
+ const deleteTable = (editor) => {
1436
+ if (editor.api.some({ match: { type: editor.getType(KEYS.table) } })) {
1437
+ const tableItem = editor.api.above({ match: { type: editor.getType(KEYS.table) } });
1438
+ if (tableItem) editor.tf.removeNodes({ at: tableItem[1] });
1439
+ }
1440
+ };
1441
+
1442
+ //#endregion
1443
+ //#region src/lib/transforms/insertTable.ts
1444
+ /**
1445
+ * Insert table. If selection in table and no 'at' specified, insert after
1446
+ * current table. Select start of new table.
1447
+ */
1448
+ const insertTable = (editor, { colCount = 2, header, rowCount = 2 } = {}, { select: shouldSelect, ...options } = {}) => {
1449
+ const { api } = editor.getPlugin({ key: KEYS.table });
1450
+ const type = editor.getType(KEYS.table);
1451
+ editor.tf.withoutNormalizing(() => {
1452
+ const newTable = api.create.table({
1453
+ colCount,
1454
+ header,
1455
+ rowCount
1456
+ });
1457
+ if (!options.at) {
1458
+ const currentTableEntry = editor.api.block({ match: { type } });
1459
+ if (currentTableEntry) {
1460
+ const [, tablePath] = currentTableEntry;
1461
+ const insertPath = PathApi.next(tablePath);
1462
+ editor.tf.insertNodes(newTable, {
1463
+ at: insertPath,
1464
+ ...options
1465
+ });
1466
+ if (editor.selection) editor.tf.select(editor.api.start(insertPath));
1467
+ return;
1468
+ }
1469
+ }
1470
+ editor.tf.insertNodes(newTable, {
1471
+ nextBlock: !options.at,
1472
+ select: shouldSelect,
1473
+ ...options
1474
+ });
1475
+ if (shouldSelect) {
1476
+ const tableEntry = editor.api.node({
1477
+ at: options.at,
1478
+ match: { type }
1479
+ });
1480
+ if (!tableEntry) return;
1481
+ editor.tf.select(editor.api.start(tableEntry[1]));
1482
+ }
1483
+ });
1484
+ };
1485
+
1486
+ //#endregion
1487
+ //#region src/lib/transforms/insertTableColumn.ts
1488
+ const insertTableColumn = (editor, options = {}) => {
1489
+ const { api, getOptions, type } = getEditorPlugin(editor, BaseTablePlugin);
1490
+ const { disableMerge, initialTableWidth, minColumnWidth } = getOptions();
1491
+ if (!disableMerge) return insertTableMergeColumn(editor, options);
1492
+ const { before, header, select: shouldSelect } = options;
1493
+ let { at, fromCell } = options;
1494
+ if (at && !fromCell) {
1495
+ if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
1496
+ fromCell = NodeApi.lastChild(editor, at.concat([0]))[1];
1497
+ at = void 0;
1498
+ }
1499
+ }
1500
+ const cellEntry = editor.api.block({
1501
+ at: fromCell,
1502
+ match: { type: getCellTypes(editor) }
1503
+ });
1504
+ if (!cellEntry) return;
1505
+ const [, cellPath] = cellEntry;
1506
+ const tableEntry = editor.api.block({
1507
+ above: true,
1508
+ at: cellPath,
1509
+ match: { type }
1510
+ });
1511
+ if (!tableEntry) return;
1512
+ const [tableNode, tablePath] = tableEntry;
1513
+ let nextCellPath;
1514
+ let nextColIndex;
1515
+ if (PathApi.isPath(at)) {
1516
+ nextCellPath = at;
1517
+ nextColIndex = at.at(-1);
1518
+ } else {
1519
+ nextCellPath = before ? cellPath : PathApi.next(cellPath);
1520
+ nextColIndex = before ? cellPath.at(-1) : cellPath.at(-1) + 1;
1521
+ }
1522
+ const currentRowIndex = cellPath.at(-2);
1523
+ editor.tf.withoutNormalizing(() => {
1524
+ tableNode.children.forEach((row, rowIndex) => {
1525
+ const insertCellPath = [...nextCellPath];
1526
+ if (PathApi.isPath(at)) insertCellPath[at.length - 2] = rowIndex;
1527
+ else insertCellPath[cellPath.length - 2] = rowIndex;
1528
+ const isHeaderRow = header === void 0 ? row.children.every((c) => c.type === editor.getType(KEYS.th)) : header;
1529
+ editor.tf.insertNodes(api.create.tableCell({ header: isHeaderRow }), {
1530
+ at: insertCellPath,
1531
+ select: shouldSelect && rowIndex === currentRowIndex
1532
+ });
1533
+ });
1534
+ const { colSizes } = tableNode;
1535
+ if (colSizes) {
1536
+ let newColSizes = [
1537
+ ...colSizes.slice(0, nextColIndex),
1538
+ 0,
1539
+ ...colSizes.slice(nextColIndex)
1540
+ ];
1541
+ if (initialTableWidth) {
1542
+ newColSizes[nextColIndex] = colSizes[nextColIndex] ?? colSizes[nextColIndex - 1] ?? initialTableWidth / colSizes.length;
1543
+ const oldTotal = colSizes.reduce((a, b) => a + b, 0);
1544
+ const newTotal = newColSizes.reduce((a, b) => a + b, 0);
1545
+ const maxTotal = Math.max(oldTotal, initialTableWidth);
1546
+ if (newTotal > maxTotal) {
1547
+ const factor = maxTotal / newTotal;
1548
+ newColSizes = newColSizes.map((size) => Math.max(minColumnWidth ?? 0, Math.floor(size * factor)));
1549
+ }
1550
+ }
1551
+ editor.tf.setNodes({ colSizes: newColSizes }, { at: tablePath });
1552
+ }
1553
+ });
1554
+ };
1555
+
1556
+ //#endregion
1557
+ //#region src/lib/transforms/insertTableRow.ts
1558
+ const insertTableRow = (editor, options = {}) => {
1559
+ const { api, getOptions, type } = getEditorPlugin(editor, BaseTablePlugin);
1560
+ const { disableMerge } = getOptions();
1561
+ if (!disableMerge) return insertTableMergeRow(editor, options);
1562
+ const { before, header, select: shouldSelect } = options;
1563
+ let { at, fromRow } = options;
1564
+ if (at && !fromRow) {
1565
+ if (NodeApi.get(editor, at)?.type === editor.getType(KEYS.table)) {
1566
+ fromRow = NodeApi.lastChild(editor, at)[1];
1567
+ at = void 0;
1568
+ }
1569
+ }
1570
+ const trEntry = editor.api.block({
1571
+ at: fromRow,
1572
+ match: { type: editor.getType(KEYS.tr) }
1573
+ });
1574
+ if (!trEntry) return;
1575
+ const [trNode, trPath] = trEntry;
1576
+ const tableEntry = editor.api.block({
1577
+ above: true,
1578
+ at: trPath,
1579
+ match: { type }
1580
+ });
1581
+ if (!tableEntry) return;
1582
+ const getEmptyRowNode$1 = () => ({
1583
+ children: trNode.children.map((_, i) => {
1584
+ const isHeaderColumn = !(tableEntry[0].children.length === 1) && tableEntry[0].children.every((n) => n.children[i].type === editor.getType(KEYS.th));
1585
+ return api.create.tableCell({ header: header ?? isHeaderColumn });
1586
+ }),
1587
+ type: editor.getType(KEYS.tr)
1588
+ });
1589
+ editor.tf.withoutNormalizing(() => {
1590
+ editor.tf.insertNodes(getEmptyRowNode$1(), { at: PathApi.isPath(at) ? at : before ? trPath : PathApi.next(trPath) });
1591
+ });
1592
+ if (shouldSelect) {
1593
+ const cellEntry = editor.api.block({ match: { type: getCellTypes(editor) } });
1594
+ if (!cellEntry) return;
1595
+ const [, nextCellPath] = cellEntry;
1596
+ if (PathApi.isPath(at)) nextCellPath[nextCellPath.length - 2] = at.at(-2);
1597
+ else nextCellPath[nextCellPath.length - 2] = before ? nextCellPath.at(-2) : nextCellPath.at(-2) + 1;
1598
+ editor.tf.select(nextCellPath);
1599
+ }
1600
+ };
1601
+
1602
+ //#endregion
1603
+ //#region src/lib/transforms/moveSelectionFromCell.ts
1604
+ /** Move selection by cell unit. */
1605
+ const moveSelectionFromCell = (editor, { at, edge, fromOneCell, reverse } = {}) => {
1606
+ if (edge) {
1607
+ const cellEntries = getTableGridAbove(editor, {
1608
+ at,
1609
+ format: "cell"
1610
+ });
1611
+ const minCell = fromOneCell ? 0 : 1;
1612
+ if (cellEntries.length > minCell) {
1613
+ const [, firstCellPath] = cellEntries[0];
1614
+ const [, lastCellPath] = cellEntries.at(-1);
1615
+ const anchorPath = [...firstCellPath];
1616
+ const focusPath = [...lastCellPath];
1617
+ switch (edge) {
1618
+ case "bottom":
1619
+ focusPath[focusPath.length - 2] += 1;
1620
+ break;
1621
+ case "left":
1622
+ anchorPath[anchorPath.length - 1] -= 1;
1623
+ break;
1624
+ case "right":
1625
+ focusPath[focusPath.length - 1] += 1;
1626
+ break;
1627
+ case "top":
1628
+ anchorPath[anchorPath.length - 2] -= 1;
1629
+ break;
1630
+ }
1631
+ if (NodeApi.has(editor, anchorPath) && NodeApi.has(editor, focusPath)) editor.tf.select({
1632
+ anchor: editor.api.start(anchorPath),
1633
+ focus: editor.api.start(focusPath)
1634
+ });
1635
+ return true;
1636
+ }
1637
+ return;
1638
+ }
1639
+ const cellEntry = editor.api.block({
1640
+ at,
1641
+ match: { type: getCellTypes(editor) }
1642
+ });
1643
+ if (cellEntry) {
1644
+ const [, cellPath] = cellEntry;
1645
+ const nextCellPath = [...cellPath];
1646
+ const offset = reverse ? -1 : 1;
1647
+ nextCellPath[nextCellPath.length - 2] += offset;
1648
+ if (NodeApi.has(editor, nextCellPath)) editor.tf.select(editor.api.start(nextCellPath));
1649
+ else {
1650
+ const tablePath = cellPath.slice(0, -2);
1651
+ if (reverse) editor.tf.withoutNormalizing(() => {
1652
+ editor.tf.select(editor.api.start(tablePath));
1653
+ editor.tf.move({ reverse: true });
1654
+ });
1655
+ else editor.tf.withoutNormalizing(() => {
1656
+ editor.tf.select(editor.api.end(tablePath));
1657
+ editor.tf.move();
1658
+ });
1659
+ }
1660
+ return true;
1661
+ }
1662
+ };
1663
+
1664
+ //#endregion
1665
+ //#region src/lib/transforms/overrideSelectionFromCell.ts
1666
+ /**
1667
+ * Override the new selection if the previous selection and the new one are in
1668
+ * different cells.
1669
+ */
1670
+ const overrideSelectionFromCell = (editor, newSelection) => {
1671
+ let hotkey;
1672
+ if (!editor.dom.currentKeyboardEvent || ![
1673
+ "up",
1674
+ "down",
1675
+ "shift+up",
1676
+ "shift+right",
1677
+ "shift+down",
1678
+ "shift+left"
1679
+ ].some((key) => {
1680
+ const valid = isHotkey(key, editor.dom.currentKeyboardEvent);
1681
+ if (valid) hotkey = key;
1682
+ return valid;
1683
+ }) || !editor.selection?.focus || !newSelection?.focus || !editor.api.isAt({
1684
+ at: {
1685
+ anchor: editor.selection.focus,
1686
+ focus: newSelection.focus
1687
+ },
1688
+ blocks: true,
1689
+ match: { type: getCellTypes(editor) }
1690
+ })) return;
1691
+ if (!hotkey) return;
1692
+ const edge = KEY_SHIFT_EDGES[hotkey];
1693
+ if (edge && !editor.api.isAt({
1694
+ block: true,
1695
+ match: { type: getCellTypes(editor) }
1696
+ })) return;
1697
+ const prevSelection = editor.selection;
1698
+ const reverse = ["shift+up", "up"].includes(hotkey);
1699
+ setTimeout(() => {
1700
+ moveSelectionFromCell(editor, {
1701
+ at: prevSelection,
1702
+ edge,
1703
+ fromOneCell: true,
1704
+ reverse
1705
+ });
1706
+ }, 0);
1707
+ };
1708
+
1709
+ //#endregion
1710
+ //#region src/lib/transforms/setBorderSize.ts
1711
+ const setBorderSize = (editor, size, { at, border = "all" } = {}) => {
1712
+ const cellEntry = editor.api.node({
1713
+ at,
1714
+ match: { type: getCellTypes(editor) }
1715
+ });
1716
+ if (!cellEntry) return;
1717
+ const [cellNode, cellPath] = cellEntry;
1718
+ const cellIndex = cellPath.at(-1);
1719
+ const rowIndex = cellPath.at(-2);
1720
+ const borderStyle = { size };
1721
+ const setNodesOptions = { match: (n) => ElementApi.isElement(n) && getCellTypes(editor).includes(n.type) };
1722
+ if (border === "top") {
1723
+ if (rowIndex === 0) {
1724
+ const newBorders$1 = {
1725
+ ...cellNode.borders,
1726
+ top: borderStyle
1727
+ };
1728
+ editor.tf.setNodes({ borders: newBorders$1 }, {
1729
+ at: cellPath,
1730
+ ...setNodesOptions
1731
+ });
1732
+ return;
1733
+ }
1734
+ const cellAboveEntry = getTopTableCell(editor, { at: cellPath });
1735
+ if (!cellAboveEntry) return;
1736
+ const [cellAboveNode, cellAbovePath] = cellAboveEntry;
1737
+ const newBorders = {
1738
+ ...cellAboveNode.borders,
1739
+ bottom: borderStyle
1740
+ };
1741
+ editor.tf.setNodes({ borders: newBorders }, {
1742
+ at: cellAbovePath,
1743
+ ...setNodesOptions
1744
+ });
1745
+ } else if (border === "bottom") {
1746
+ const newBorders = {
1747
+ ...cellNode.borders,
1748
+ bottom: borderStyle
1749
+ };
1750
+ editor.tf.setNodes({ borders: newBorders }, {
1751
+ at: cellPath,
1752
+ ...setNodesOptions
1753
+ });
1754
+ }
1755
+ if (border === "left") {
1756
+ if (cellIndex === 0) {
1757
+ const newBorders$1 = {
1758
+ ...cellNode.borders,
1759
+ left: borderStyle
1760
+ };
1761
+ editor.tf.setNodes({ borders: newBorders$1 }, {
1762
+ at: cellPath,
1763
+ ...setNodesOptions
1764
+ });
1765
+ return;
1766
+ }
1767
+ const prevCellEntry = getLeftTableCell(editor, { at: cellPath });
1768
+ if (!prevCellEntry) return;
1769
+ const [prevCellNode, prevCellPath] = prevCellEntry;
1770
+ const newBorders = {
1771
+ ...prevCellNode.borders,
1772
+ right: borderStyle
1773
+ };
1774
+ editor.tf.setNodes({ borders: newBorders }, {
1775
+ at: prevCellPath,
1776
+ ...setNodesOptions
1777
+ });
1778
+ } else if (border === "right") {
1779
+ const newBorders = {
1780
+ ...cellNode.borders,
1781
+ right: borderStyle
1782
+ };
1783
+ editor.tf.setNodes({ borders: newBorders }, {
1784
+ at: cellPath,
1785
+ ...setNodesOptions
1786
+ });
1787
+ }
1788
+ if (border === "all") editor.tf.withoutNormalizing(() => {
1789
+ setBorderSize(editor, size, {
1790
+ at,
1791
+ border: "top"
1792
+ });
1793
+ setBorderSize(editor, size, {
1794
+ at,
1795
+ border: "bottom"
1796
+ });
1797
+ setBorderSize(editor, size, {
1798
+ at,
1799
+ border: "left"
1800
+ });
1801
+ setBorderSize(editor, size, {
1802
+ at,
1803
+ border: "right"
1804
+ });
1805
+ });
1806
+ };
1807
+
1808
+ //#endregion
1809
+ //#region src/lib/transforms/setTableColSize.ts
1810
+ const setTableColSize = (editor, { colIndex, width }, options = {}) => {
1811
+ const table = editor.api.node({
1812
+ match: { type: KEYS.table },
1813
+ ...options
1814
+ });
1815
+ if (!table) return;
1816
+ const [tableNode, tablePath] = table;
1817
+ const colSizes = tableNode.colSizes ? [...tableNode.colSizes] : Array.from({ length: getTableColumnCount(tableNode) }).fill(0);
1818
+ colSizes[colIndex] = width;
1819
+ editor.tf.setNodes({ colSizes }, { at: tablePath });
1820
+ };
1821
+
1822
+ //#endregion
1823
+ //#region src/lib/transforms/setTableMarginLeft.ts
1824
+ const setTableMarginLeft = (editor, { marginLeft }, options = {}) => {
1825
+ const table = editor.api.node({
1826
+ match: { type: KEYS.table },
1827
+ ...options
1828
+ });
1829
+ if (!table) return;
1830
+ const [, tablePath] = table;
1831
+ editor.tf.setNodes({ marginLeft }, { at: tablePath });
1832
+ };
1833
+
1834
+ //#endregion
1835
+ //#region src/lib/transforms/setTableRowSize.ts
1836
+ const setTableRowSize = (editor, { height, rowIndex }, options = {}) => {
1837
+ const table = editor.api.node({
1838
+ match: { type: KEYS.table },
1839
+ ...options
1840
+ });
1841
+ if (!table) return;
1842
+ const [, tablePath] = table;
1843
+ const tableRowPath = [...tablePath, rowIndex];
1844
+ editor.tf.setNodes({ size: height }, { at: tableRowPath });
1845
+ };
1846
+
1847
+ //#endregion
1848
+ //#region src/lib/withApplyTable.ts
1849
+ /**
1850
+ * Selection table:
1851
+ *
1852
+ * - If anchor is in table, focus in a block before: set focus to start of table
1853
+ * - If anchor is in table, focus in a block after: set focus to end of table
1854
+ * - If focus is in table, anchor in a block before: set focus to end of table
1855
+ * - If focus is in table, anchor in a block after: set focus to the point before
1856
+ * start of table
1857
+ */
1858
+ const withApplyTable = ({ api: _api, editor, getOptions, tf: { apply }, type: tableType }) => ({ transforms: { apply(op) {
1859
+ if (op.type === "set_selection" && op.newProperties) {
1860
+ const newSelection = {
1861
+ ...editor.selection,
1862
+ ...op.newProperties
1863
+ };
1864
+ if (RangeApi.isRange(newSelection) && editor.api.isAt({
1865
+ at: newSelection,
1866
+ blocks: true,
1867
+ match: (n) => n.type === tableType
1868
+ })) {
1869
+ const anchorEntry = editor.api.block({
1870
+ at: newSelection.anchor,
1871
+ match: (n) => n.type === tableType
1872
+ });
1873
+ if (anchorEntry) {
1874
+ const [, anchorPath] = anchorEntry;
1875
+ if (RangeApi.isBackward(newSelection)) op.newProperties.focus = editor.api.start(anchorPath);
1876
+ else if (editor.api.before(anchorPath)) op.newProperties.focus = editor.api.end(anchorPath);
1877
+ } else {
1878
+ const focusEntry = editor.api.block({
1879
+ at: newSelection.focus,
1880
+ match: (n) => n.type === tableType
1881
+ });
1882
+ if (focusEntry) {
1883
+ const [, focusPath] = focusEntry;
1884
+ if (RangeApi.isBackward(newSelection)) {
1885
+ const startPoint = editor.api.start(focusPath);
1886
+ const pointBefore = editor.api.before(startPoint);
1887
+ op.newProperties.focus = pointBefore ?? startPoint;
1888
+ } else op.newProperties.focus = editor.api.end(focusPath);
1889
+ }
1890
+ }
1891
+ }
1892
+ overrideSelectionFromCell(editor, newSelection);
1893
+ }
1894
+ const opType = op.type === "remove_node" ? op.node.type : op.type === "move_node" ? editor.api.node(op.path)?.[0].type : void 0;
1895
+ const isTableOperation = (op.type === "remove_node" || op.type === "move_node") && opType && [
1896
+ editor.getType(KEYS.tr),
1897
+ tableType,
1898
+ ...getCellTypes(editor)
1899
+ ].includes(opType);
1900
+ if (isTableOperation && op.type === "remove_node") {
1901
+ const cells = [...editor.api.nodes({
1902
+ at: op.path,
1903
+ match: { type: getCellTypes(editor) }
1904
+ })];
1905
+ const cellIndices = getOptions()._cellIndices;
1906
+ cells.forEach(([cell]) => {
1907
+ delete cellIndices[cell.id];
1908
+ });
1909
+ }
1910
+ apply(op);
1911
+ let table;
1912
+ if (isTableOperation && opType !== tableType) {
1913
+ table = editor.api.node({
1914
+ at: op.type === "move_node" ? op.newPath : op.path,
1915
+ match: { type: tableType }
1916
+ })?.[0];
1917
+ if (table) computeCellIndices(editor, { tableNode: table });
1918
+ }
1919
+ } } });
1920
+
1921
+ //#endregion
1922
+ //#region src/lib/withDeleteTable.ts
1923
+ /**
1924
+ * Return true if:
1925
+ *
1926
+ * - At start/end of a cell.
1927
+ * - Next to a table cell. Move selection to the table cell.
1928
+ */
1929
+ const preventDeleteTableCell = (editor, { reverse, unit }) => {
1930
+ const { selection } = editor;
1931
+ const getNextPoint = reverse ? editor.api.after : editor.api.before;
1932
+ if (editor.api.isCollapsed()) {
1933
+ const cellEntry = editor.api.block({ match: { type: getCellTypes(editor) } });
1934
+ if (cellEntry) {
1935
+ const [, cellPath] = cellEntry;
1936
+ const start = reverse ? editor.api.end(cellPath) : editor.api.start(cellPath);
1937
+ if (selection && PointApi.equals(selection.anchor, start)) return true;
1938
+ } else {
1939
+ const nextPoint = getNextPoint(selection, { unit });
1940
+ if (editor.api.block({
1941
+ at: nextPoint,
1942
+ match: { type: getCellTypes(editor) }
1943
+ })) {
1944
+ editor.tf.move({ reverse: !reverse });
1945
+ return true;
1946
+ }
1947
+ }
1948
+ }
1949
+ };
1950
+ /** Prevent cell deletion. */
1951
+ const withDeleteTable = ({ editor, tf: { deleteFragment }, type }) => ({ transforms: { deleteFragment(direction) {
1952
+ if (editor.api.isAt({
1953
+ block: true,
1954
+ match: (n) => n.type === type
1955
+ })) {
1956
+ const cellEntries = getTableGridAbove(editor, { format: "cell" });
1957
+ if (cellEntries.length > 1) {
1958
+ editor.tf.withoutNormalizing(() => {
1959
+ cellEntries.forEach(([, cellPath]) => {
1960
+ editor.tf.replaceNodes(editor.api.create.block(), {
1961
+ at: cellPath,
1962
+ children: true
1963
+ });
1964
+ });
1965
+ editor.tf.select({
1966
+ anchor: editor.api.start(cellEntries[0][1]),
1967
+ focus: editor.api.end(cellEntries.at(-1)[1])
1968
+ });
1969
+ });
1970
+ return;
1971
+ }
1972
+ }
1973
+ deleteFragment(direction);
1974
+ } } });
1975
+
1976
+ //#endregion
1977
+ //#region src/lib/withGetFragmentTable.ts
1978
+ /** If selection is in a table, get subtable above. */
1979
+ const withGetFragmentTable = ({ api, api: { getFragment }, editor, type }) => ({ api: { getFragment() {
1980
+ const fragment = getFragment();
1981
+ const newFragment = [];
1982
+ fragment.forEach((node) => {
1983
+ if (node.type === type) {
1984
+ const rows = node.children;
1985
+ const rowCount = rows.length;
1986
+ if (!rowCount) return;
1987
+ const colCount = rows[0].children.length;
1988
+ if (rowCount <= 1 && colCount <= 1) {
1989
+ const cell = rows[0];
1990
+ const cellChildren = api.table.getCellChildren(cell);
1991
+ newFragment.push(...cellChildren[0].children);
1992
+ return;
1993
+ }
1994
+ const subTable = getTableGridAbove(editor);
1995
+ if (subTable.length > 0) {
1996
+ newFragment.push(subTable[0][0]);
1997
+ return;
1998
+ }
1999
+ }
2000
+ newFragment.push(node);
2001
+ });
2002
+ return newFragment;
2003
+ } } });
2004
+
2005
+ //#endregion
2006
+ //#region src/lib/withInsertFragmentTable.ts
2007
+ /**
2008
+ * If inserting a table, If block above anchor is a table,
2009
+ *
2010
+ * - Replace each cell above by the inserted table until out of bounds.
2011
+ * - Select the inserted cells.
2012
+ */
2013
+ const withInsertFragmentTable = ({ api, editor, getOptions, tf: { insert, insertFragment }, type }) => ({ transforms: { insertFragment(fragment) {
2014
+ const insertedTable = fragment.find((n) => n.type === type);
2015
+ if (!insertedTable) {
2016
+ if (getTableAbove(editor, { at: editor.selection?.anchor })) {
2017
+ const cellEntries = getTableGridAbove(editor, { format: "cell" });
2018
+ if (cellEntries.length > 1) {
2019
+ cellEntries.forEach((cellEntry) => {
2020
+ if (cellEntry) {
2021
+ const [, cellPath] = cellEntry;
2022
+ editor.tf.replaceNodes(cloneDeep(fragment), {
2023
+ at: cellPath,
2024
+ children: true
2025
+ });
2026
+ }
2027
+ });
2028
+ editor.tf.select({
2029
+ anchor: editor.api.start(cellEntries[0][1]),
2030
+ focus: editor.api.end(cellEntries.at(-1)[1])
2031
+ });
2032
+ return;
2033
+ }
2034
+ }
2035
+ }
2036
+ if (insertedTable) {
2037
+ if (getTableAbove(editor, { at: editor.selection?.anchor })) {
2038
+ const [cellEntry] = getTableGridAbove(editor, {
2039
+ at: editor.selection?.anchor,
2040
+ format: "cell"
2041
+ });
2042
+ if (cellEntry) {
2043
+ editor.tf.withoutNormalizing(() => {
2044
+ const [, startCellPath] = cellEntry;
2045
+ const cellPath = [...startCellPath];
2046
+ const startColIndex = cellPath.at(-1);
2047
+ let lastCellPath = null;
2048
+ let initRow = true;
2049
+ insertedTable.children.forEach((row) => {
2050
+ cellPath[cellPath.length - 1] = startColIndex;
2051
+ if (!initRow) {
2052
+ const fromRow = cellPath.slice(0, -1);
2053
+ cellPath[cellPath.length - 2] += 1;
2054
+ if (!NodeApi.has(editor, cellPath)) {
2055
+ if (getOptions().disableExpandOnInsert) return;
2056
+ insert.tableRow({ fromRow });
2057
+ }
2058
+ }
2059
+ initRow = false;
2060
+ const insertedCells = row.children;
2061
+ let initCell = true;
2062
+ insertedCells.forEach((cell) => {
2063
+ if (!initCell) {
2064
+ const fromCell = [...cellPath];
2065
+ cellPath[cellPath.length - 1] += 1;
2066
+ if (!NodeApi.has(editor, cellPath)) {
2067
+ if (getOptions().disableExpandOnInsert) return;
2068
+ insert.tableColumn({ fromCell });
2069
+ }
2070
+ }
2071
+ initCell = false;
2072
+ const cellChildren = api.table.getCellChildren(cell);
2073
+ editor.tf.replaceNodes(cloneDeep(cellChildren), {
2074
+ at: cellPath,
2075
+ children: true
2076
+ });
2077
+ lastCellPath = [...cellPath];
2078
+ });
2079
+ });
2080
+ if (lastCellPath) editor.tf.select({
2081
+ anchor: editor.api.start(startCellPath),
2082
+ focus: editor.api.end(lastCellPath)
2083
+ });
2084
+ });
2085
+ return;
2086
+ }
2087
+ } else if (fragment.length === 1 && fragment[0].type === KEYS.table) {
2088
+ editor.tf.insertNodes(fragment[0]);
2089
+ return;
2090
+ }
2091
+ }
2092
+ insertFragment(fragment);
2093
+ } } });
2094
+
2095
+ //#endregion
2096
+ //#region src/lib/withInsertTextTable.ts
2097
+ const withInsertTextTable = ({ editor, tf: { insertText } }) => ({ transforms: { insertText(text, options) {
2098
+ if (editor.api.isExpanded()) {
2099
+ if (getTableAbove(editor, { at: editor.selection?.anchor })) {
2100
+ if (getTableGridAbove(editor, { format: "cell" }).length > 1) editor.tf.collapse({ edge: "focus" });
2101
+ }
2102
+ }
2103
+ insertText(text, options);
2104
+ } } });
2105
+
2106
+ //#endregion
2107
+ //#region src/lib/withNormalizeTable.ts
2108
+ /**
2109
+ * Normalize table:
2110
+ *
2111
+ * - Wrap cell children in a paragraph if they are texts.
2112
+ */
2113
+ const withNormalizeTable = ({ editor, getOption, getOptions, tf: { normalizeNode }, type }) => ({ transforms: { normalizeNode([n, path]) {
2114
+ const { enableUnsetSingleColSize, initialTableWidth } = getOptions();
2115
+ if (ElementApi.isElement(n)) {
2116
+ if (n.type === type) {
2117
+ const node = n;
2118
+ if (!node.children.some((child) => ElementApi.isElement(child) && child.type === editor.getType(KEYS.tr))) {
2119
+ editor.tf.removeNodes({ at: path });
2120
+ return;
2121
+ }
2122
+ if (node.colSizes && node.colSizes.length > 0 && enableUnsetSingleColSize && getTableColumnCount(node) < 2) {
2123
+ editor.tf.unsetNodes("colSizes", { at: path });
2124
+ return;
2125
+ }
2126
+ if (editor.api.block({
2127
+ above: true,
2128
+ at: path,
2129
+ match: { type }
2130
+ })) {
2131
+ editor.tf.unwrapNodes({ at: path });
2132
+ return;
2133
+ }
2134
+ if (initialTableWidth) {
2135
+ const tableNode = node;
2136
+ const colCount = (tableNode.children[0]?.children)?.length;
2137
+ if (colCount) {
2138
+ const colSizes = [];
2139
+ if (!tableNode.colSizes) for (let i = 0; i < colCount; i++) colSizes.push(initialTableWidth / colCount);
2140
+ else if (tableNode.colSizes.some((size) => !size)) tableNode.colSizes.forEach((colSize) => {
2141
+ colSizes.push(colSize || initialTableWidth / colCount);
2142
+ });
2143
+ if (colSizes.length > 0) {
2144
+ editor.tf.setNodes({ colSizes }, { at: path });
2145
+ return;
2146
+ }
2147
+ }
2148
+ }
2149
+ }
2150
+ if (n.type === editor.getType(KEYS.tr)) {
2151
+ if (editor.api.parent(path)?.[0].type !== type) {
2152
+ editor.tf.unwrapNodes({ at: path });
2153
+ return;
2154
+ }
2155
+ }
2156
+ if (getCellTypes(editor).includes(n.type)) {
2157
+ const node = n;
2158
+ const cellIndices = getOption("cellIndices", node.id);
2159
+ if (node.id && !cellIndices) computeCellIndices(editor, {
2160
+ all: true,
2161
+ cellNode: node
2162
+ });
2163
+ const { children } = node;
2164
+ if (editor.api.parent(path)?.[0].type !== editor.getType(KEYS.tr)) {
2165
+ editor.tf.unwrapNodes({ at: path });
2166
+ return;
2167
+ }
2168
+ if (TextApi.isText(children[0])) {
2169
+ editor.tf.wrapNodes(editor.api.create.block({}, path), {
2170
+ at: path,
2171
+ children: true
2172
+ });
2173
+ return;
2174
+ }
2175
+ }
2176
+ }
2177
+ normalizeNode([n, path]);
2178
+ } } });
2179
+
2180
+ //#endregion
2181
+ //#region src/lib/withSetFragmentDataTable.ts
2182
+ const withSetFragmentDataTable = ({ api, editor, plugin, tf: { setFragmentData } }) => ({ transforms: { setFragmentData(data, originEvent) {
2183
+ const tableEntry = getTableGridAbove(editor, { format: "table" })?.[0];
2184
+ const selectedCellEntries = getTableGridAbove(editor, { format: "cell" });
2185
+ const initialSelection = editor.selection;
2186
+ if (!tableEntry || !initialSelection) {
2187
+ setFragmentData(data, originEvent);
2188
+ return;
2189
+ }
2190
+ const [tableNode, tablePath] = tableEntry;
2191
+ const tableRows = tableNode.children;
2192
+ tableNode.children = tableNode.children.filter((v) => v.children.length > 0);
2193
+ let textCsv = "";
2194
+ let textTsv = "";
2195
+ const divElement = document.createElement("div");
2196
+ const tableElement = document.createElement("table");
2197
+ /**
2198
+ * Cover single cell copy | cut operation. In this case, copy cell content
2199
+ * instead of table structure.
2200
+ */
2201
+ if (tableEntry && initialSelection && selectedCellEntries.length === 1 && (originEvent === "copy" || originEvent === "cut")) {
2202
+ setFragmentData(data);
2203
+ return;
2204
+ }
2205
+ editor.tf.withoutNormalizing(() => {
2206
+ tableRows.forEach((row) => {
2207
+ const rowCells = row.children;
2208
+ const cellStrings = [];
2209
+ const rowElement = row.type === editor.getType(KEYS.th) ? document.createElement("th") : document.createElement("tr");
2210
+ rowCells.forEach((cell) => {
2211
+ data.clearData();
2212
+ const cellPath = editor.api.findPath(cell);
2213
+ editor.tf.select({
2214
+ anchor: editor.api.start(cellPath),
2215
+ focus: editor.api.end(cellPath)
2216
+ });
2217
+ setFragmentData(data);
2218
+ cellStrings.push(data.getData("text/plain"));
2219
+ const cellElement = document.createElement("td");
2220
+ cellElement.colSpan = api.table.getColSpan(cell);
2221
+ cellElement.rowSpan = api.table.getRowSpan(cell);
2222
+ cellElement.innerHTML = data.getData("text/html");
2223
+ rowElement.append(cellElement);
2224
+ });
2225
+ tableElement.append(rowElement);
2226
+ textCsv += `${cellStrings.join(",")}\n`;
2227
+ textTsv += `${cellStrings.join(" ")}\n`;
2228
+ });
2229
+ const _tableEntry = editor.api.node({
2230
+ at: tablePath,
2231
+ match: { type: KEYS.table }
2232
+ });
2233
+ if (_tableEntry != null && _tableEntry.length > 0) {
2234
+ const realTable = _tableEntry[0];
2235
+ if (realTable.attributes != null) Object.entries(realTable.attributes).forEach(([key, value]) => {
2236
+ if (value != null && plugin.node.dangerouslyAllowAttributes?.includes(key)) tableElement.setAttribute(key, String(value));
2237
+ });
2238
+ }
2239
+ editor.tf.select(initialSelection);
2240
+ divElement.append(tableElement);
2241
+ });
2242
+ data.setData("text/csv", textCsv);
2243
+ data.setData("text/tsv", textTsv);
2244
+ data.setData("text/plain", textTsv);
2245
+ data.setData("text/html", divElement.innerHTML);
2246
+ const selectedFragmentStr = JSON.stringify([tableNode]);
2247
+ const encodedFragment = window.btoa(encodeURIComponent(selectedFragmentStr));
2248
+ data.setData("application/x-slate-fragment", encodedFragment);
2249
+ } } });
2250
+
2251
+ //#endregion
2252
+ //#region src/lib/withTableCellSelection.tsx
2253
+ const withTableCellSelection = ({ api: { marks }, editor, tf: { addMark, removeMark, setNodes } }) => {
2254
+ return {
2255
+ api: { marks() {
2256
+ const apply = () => {
2257
+ const { selection } = editor;
2258
+ if (!selection || editor.api.isCollapsed()) return;
2259
+ const matchesCell = getTableGridAbove(editor, { format: "cell" });
2260
+ if (matchesCell.length <= 1) return;
2261
+ const markCounts = {};
2262
+ const totalMarks = {};
2263
+ let totalNodes = 0;
2264
+ matchesCell.forEach(([_cell, cellPath]) => {
2265
+ const textNodeEntry = editor.api.nodes({
2266
+ at: cellPath,
2267
+ match: (n) => TextApi.isText(n)
2268
+ });
2269
+ Array.from(textNodeEntry, (item) => item[0]).forEach((item) => {
2270
+ totalNodes++;
2271
+ const keys = Object.keys(item);
2272
+ if (keys.length === 1) return;
2273
+ keys.splice(keys.indexOf("text"), 1);
2274
+ keys.forEach((k) => {
2275
+ markCounts[k] = (markCounts[k] || 0) + 1;
2276
+ totalMarks[k] = item[k];
2277
+ });
2278
+ });
2279
+ });
2280
+ Object.keys(markCounts).forEach((mark) => {
2281
+ if (markCounts[mark] !== totalNodes) delete totalMarks[mark];
2282
+ });
2283
+ return totalMarks;
2284
+ };
2285
+ const result = apply();
2286
+ if (result) return result;
2287
+ return marks();
2288
+ } },
2289
+ transforms: {
2290
+ addMark(key, value) {
2291
+ const apply = () => {
2292
+ const { selection } = editor;
2293
+ if (!selection || editor.api.isCollapsed() || editor.meta.isNormalizing) return;
2294
+ const matchesCell = getTableGridAbove(editor, { format: "cell" });
2295
+ if (matchesCell.length <= 1) return;
2296
+ matchesCell.forEach(([_cell, cellPath]) => {
2297
+ editor.tf.setNodes({ [key]: value }, {
2298
+ at: cellPath,
2299
+ split: true,
2300
+ voids: true,
2301
+ match: (n) => TextApi.isText(n)
2302
+ });
2303
+ });
2304
+ return true;
2305
+ };
2306
+ if (apply()) return;
2307
+ return addMark(key, value);
2308
+ },
2309
+ removeMark(key) {
2310
+ const apply = () => {
2311
+ const { selection } = editor;
2312
+ if (!selection || editor.api.isCollapsed() || editor.meta.isNormalizing) return;
2313
+ const matchesCell = getTableGridAbove(editor, { format: "cell" });
2314
+ if (matchesCell.length <= 1) return;
2315
+ matchesCell.forEach(([_cell, cellPath]) => {
2316
+ editor.tf.setNodes({ [key]: null }, {
2317
+ at: cellPath,
2318
+ split: true,
2319
+ voids: true,
2320
+ match: (n) => TextApi.isText(n)
2321
+ });
2322
+ });
2323
+ return true;
2324
+ };
2325
+ if (apply()) return;
2326
+ return removeMark(key);
2327
+ },
2328
+ setNodes(props, options) {
2329
+ const apply = () => {
2330
+ const { selection } = editor;
2331
+ if (!selection || editor.api.isCollapsed() || editor.meta.isNormalizing) return;
2332
+ if (options?.at) {
2333
+ const range = editor.api.range(options.at);
2334
+ if (range && !RangeApi.includes(selection, range)) return;
2335
+ }
2336
+ const matchesCell = getTableGridAbove(editor, { format: "cell" });
2337
+ if (matchesCell.length <= 1) return;
2338
+ setNodes(props, {
2339
+ ...options,
2340
+ match: combineTransformMatchOptions(editor, (_, p) => matchesCell.some(([_$1, cellPath]) => PathApi.isCommon(cellPath, p)), options)
2341
+ });
2342
+ return true;
2343
+ };
2344
+ if (apply()) return;
2345
+ return setNodes(props, options);
2346
+ }
2347
+ }
2348
+ };
2349
+ };
2350
+
2351
+ //#endregion
2352
+ //#region src/lib/withTable.ts
2353
+ const withTable = (ctx) => {
2354
+ const { editor, tf: { selectAll, tab }, type } = ctx;
2355
+ const cellSelection = withTableCellSelection(ctx);
2356
+ return {
2357
+ api: {
2358
+ ...withGetFragmentTable(ctx).api,
2359
+ ...cellSelection.api
2360
+ },
2361
+ transforms: {
2362
+ selectAll: () => {
2363
+ const apply = () => {
2364
+ const table = editor.api.above({ match: { type } });
2365
+ if (!table) return;
2366
+ const [, tablePath] = table;
2367
+ editor.tf.select(tablePath);
2368
+ return true;
2369
+ };
2370
+ if (apply()) return true;
2371
+ return selectAll();
2372
+ },
2373
+ tab: (options) => {
2374
+ const apply = () => {
2375
+ if (editor.selection && editor.api.isExpanded()) {
2376
+ if (Array.from(editor.api.nodes({
2377
+ at: editor.selection,
2378
+ match: { type: getCellTypes(editor) }
2379
+ })).length > 1) {
2380
+ editor.tf.collapse({ edge: "end" });
2381
+ return true;
2382
+ }
2383
+ }
2384
+ const entries = getTableEntries(editor);
2385
+ if (!entries) return;
2386
+ const { cell, row } = entries;
2387
+ const [, cellPath] = cell;
2388
+ if (options.reverse) {
2389
+ const previousCell = getPreviousTableCell(editor, cell, cellPath, row);
2390
+ if (previousCell) {
2391
+ const [, previousCellPath] = previousCell;
2392
+ editor.tf.select(previousCellPath);
2393
+ }
2394
+ } else {
2395
+ const nextCell = getNextTableCell(editor, cell, cellPath, row);
2396
+ if (nextCell) {
2397
+ const [, nextCellPath] = nextCell;
2398
+ editor.tf.select(nextCellPath);
2399
+ }
2400
+ }
2401
+ return true;
2402
+ };
2403
+ if (apply()) return true;
2404
+ return tab(options);
2405
+ },
2406
+ ...withNormalizeTable(ctx).transforms,
2407
+ ...withDeleteTable(ctx).transforms,
2408
+ ...withInsertFragmentTable(ctx).transforms,
2409
+ ...withInsertTextTable(ctx).transforms,
2410
+ ...withApplyTable(ctx).transforms,
2411
+ ...withSetFragmentDataTable(ctx).transforms,
2412
+ ...cellSelection.transforms
2413
+ }
2414
+ };
2415
+ };
2416
+
2417
+ //#endregion
2418
+ //#region src/lib/BaseTablePlugin.ts
2419
+ const parse = ({ element, type }) => {
2420
+ const background = element.style.background || element.style.backgroundColor;
2421
+ if (background) return {
2422
+ background,
2423
+ type
2424
+ };
2425
+ return { type };
2426
+ };
2427
+ const BaseTableRowPlugin = createSlatePlugin({
2428
+ key: KEYS.tr,
2429
+ node: {
2430
+ isContainer: true,
2431
+ isElement: true,
2432
+ isStrictSiblings: true
2433
+ },
2434
+ parsers: { html: { deserializer: { rules: [{ validNodeName: "TR" }] } } }
2435
+ });
2436
+ const BaseTableCellPlugin = createSlatePlugin({
2437
+ key: KEYS.td,
2438
+ node: {
2439
+ dangerouslyAllowAttributes: ["colspan", "rowspan"],
2440
+ isContainer: true,
2441
+ isElement: true,
2442
+ isStrictSiblings: true,
2443
+ props: ({ element }) => ({
2444
+ colSpan: (element?.attributes)?.colspan,
2445
+ rowSpan: (element?.attributes)?.rowspan
2446
+ })
2447
+ },
2448
+ parsers: { html: { deserializer: {
2449
+ attributeNames: ["rowspan", "colspan"],
2450
+ parse,
2451
+ rules: [{ validNodeName: "TD" }]
2452
+ } } },
2453
+ rules: { merge: { removeEmpty: false } }
2454
+ });
2455
+ const BaseTableCellHeaderPlugin = createSlatePlugin({
2456
+ key: KEYS.th,
2457
+ node: {
2458
+ dangerouslyAllowAttributes: ["colspan", "rowspan"],
2459
+ isContainer: true,
2460
+ isElement: true,
2461
+ isStrictSiblings: true,
2462
+ props: ({ element }) => ({
2463
+ colSpan: (element?.attributes)?.colspan,
2464
+ rowSpan: (element?.attributes)?.rowspan
2465
+ })
2466
+ },
2467
+ parsers: { html: { deserializer: {
2468
+ attributeNames: ["rowspan", "colspan"],
2469
+ parse,
2470
+ rules: [{ validNodeName: "TH" }]
2471
+ } } },
2472
+ rules: { merge: { removeEmpty: false } }
2473
+ });
2474
+ /** Enables support for tables. */
2475
+ const BaseTablePlugin = createTSlatePlugin({
2476
+ key: KEYS.table,
2477
+ node: {
2478
+ isContainer: true,
2479
+ isElement: true
2480
+ },
2481
+ normalizeInitialValue: normalizeInitialValueTable,
2482
+ options: {
2483
+ _cellIndices: {},
2484
+ disableMerge: false,
2485
+ minColumnWidth: 48,
2486
+ selectedCells: null,
2487
+ selectedTables: null
2488
+ },
2489
+ parsers: { html: { deserializer: { rules: [{ validNodeName: "TABLE" }] } } },
2490
+ plugins: [
2491
+ BaseTableRowPlugin,
2492
+ BaseTableCellPlugin,
2493
+ BaseTableCellHeaderPlugin
2494
+ ]
2495
+ }).extendSelectors(({ getOptions }) => ({ cellIndices: (id) => getOptions()._cellIndices[id] })).extendEditorApi(({ editor }) => ({
2496
+ create: {
2497
+ table: bindFirst(getEmptyTableNode, editor),
2498
+ tableCell: bindFirst(getEmptyCellNode, editor),
2499
+ tableRow: bindFirst(getEmptyRowNode, editor)
2500
+ },
2501
+ table: {
2502
+ getCellBorders: bindFirst(getTableCellBorders, editor),
2503
+ getCellSize: bindFirst(getTableCellSize, editor),
2504
+ getColSpan,
2505
+ getRowSpan,
2506
+ getCellChildren: (cell) => cell.children
2507
+ }
2508
+ })).extendEditorTransforms(({ editor }) => ({
2509
+ insert: {
2510
+ table: bindFirst(insertTable, editor),
2511
+ tableColumn: bindFirst(insertTableColumn, editor),
2512
+ tableRow: bindFirst(insertTableRow, editor)
2513
+ },
2514
+ remove: {
2515
+ table: bindFirst(deleteTable, editor),
2516
+ tableColumn: bindFirst(deleteColumn, editor),
2517
+ tableRow: bindFirst(deleteRow, editor)
2518
+ },
2519
+ table: {
2520
+ merge: bindFirst(mergeTableCells, editor),
2521
+ split: bindFirst(splitTableCell, editor)
2522
+ }
2523
+ })).overrideEditor(withTable);
2524
+
2525
+ //#endregion
2526
+ //#region src/lib/constants.ts
2527
+ const KEY_SHIFT_EDGES = {
2528
+ "shift+down": "bottom",
2529
+ "shift+left": "left",
2530
+ "shift+right": "right",
2531
+ "shift+up": "top"
2532
+ };
2533
+
2534
+ //#endregion
2535
+ export { isSelectedCellBordersNone as $, mergeTableCells as A, getTableOverriddenColSizes as B, insertTableColumn as C, deleteColumn as D, deleteRow as E, deleteTableMergeRow as F, getCellIndicesWithSpans as G, getTableGridByRange as H, deleteRowWhenExpanded as I, getTableCellSize as J, getTableEntries as K, deleteTableMergeColumn as L, insertTableMergeRow as M, insertTableMergeColumn as N, normalizeInitialValueTable as O, getTableMergedColumnCount as P, isSelectedCellBorder as Q, getCellPath as R, insertTableRow as S, deleteTable as T, getTableMergeGridByRange as U, getTableGridAbove as V, findCellByIndexes as W, getTableAbove as X, getTableCellBorders as Y, getSelectedCellsBorders as Z, setTableMarginLeft as _, BaseTableRowPlugin as a, getNextTableCell as at, overrideSelectionFromCell as b, withSetFragmentDataTable as c, getCellRowIndexByPath as ct, withInsertFragmentTable as d, getColSpan as dt, isSelectedCellBordersOuter as et, withGetFragmentTable as f, getCellInPreviousTableRow as ft, setTableRowSize as g, getEmptyCellNode as gt, withApplyTable as h, getEmptyRowNode as ht, BaseTablePlugin as i, getPreviousTableCell as it, isTableRectangular as j, splitTableCell as k, withNormalizeTable as l, getCellIndices as lt, withDeleteTable as m, getEmptyTableNode as mt, BaseTableCellHeaderPlugin as n, getSelectedCellsBoundingBox as nt, withTable as o, getLeftTableCell as ot, preventDeleteTableCell as p, getCellInNextTableRow as pt, getTableColumnCount as q, BaseTableCellPlugin as r, getRowSpan as rt, withTableCellSelection as s, getCellTypes as st, KEY_SHIFT_EDGES as t, getTopTableCell as tt, withInsertTextTable as u, computeCellIndices as ut, setTableColSize as v, insertTable as w, moveSelectionFromCell as x, setBorderSize as y, deleteColumnWhenExpanded as z };
2536
+ //# sourceMappingURL=constants-B6Sm9BNa.js.map