@lexical/table 0.12.6 → 0.13.1

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.
@@ -6,256 +6,8 @@
6
6
  */
7
7
  'use strict';
8
8
 
9
- var lexical = require('lexical');
10
9
  var utils = require('@lexical/utils');
11
-
12
- /**
13
- * Copyright (c) Meta Platforms, Inc. and affiliates.
14
- *
15
- * This source code is licensed under the MIT license found in the
16
- * LICENSE file in the root directory of this source tree.
17
- *
18
- */
19
- class GridSelection extends lexical.INTERNAL_PointSelection {
20
- constructor(gridKey, anchor, focus) {
21
- super(anchor, focus);
22
- this.gridKey = gridKey;
23
- }
24
- getCachedNodes() {
25
- return this._cachedNodes;
26
- }
27
- setCachedNodes(nodes) {
28
- this._cachedNodes = nodes;
29
- }
30
- is(selection) {
31
- if (!$isGridSelection(selection)) {
32
- return false;
33
- }
34
- return this.gridKey === selection.gridKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
35
- }
36
- set(gridKey, anchorCellKey, focusCellKey) {
37
- this.dirty = true;
38
- this.gridKey = gridKey;
39
- this.anchor.key = anchorCellKey;
40
- this.focus.key = focusCellKey;
41
- this._cachedNodes = null;
42
- }
43
- clone() {
44
- return new GridSelection(this.gridKey, this.anchor, this.focus);
45
- }
46
- isCollapsed() {
47
- return false;
48
- }
49
- extract() {
50
- return this.getNodes();
51
- }
52
- insertRawText(text) {
53
- // Do nothing?
54
- }
55
- insertText() {
56
- // Do nothing?
57
- }
58
- insertNodes(nodes) {
59
- const focusNode = this.focus.getNode();
60
- if (!lexical.$isElementNode(focusNode)) {
61
- throw Error(`Expected GridSelection focus to be an ElementNode`);
62
- }
63
- const selection = lexical.$normalizeSelection__EXPERIMENTAL(focusNode.select(0, focusNode.getChildrenSize()));
64
- selection.insertNodes(nodes);
65
- }
66
-
67
- // TODO Deprecate this method. It's confusing when used with colspan|rowspan
68
- getShape() {
69
- const anchorCellNode = lexical.$getNodeByKey(this.anchor.key);
70
- if (!lexical.DEPRECATED_$isGridCellNode(anchorCellNode)) {
71
- throw Error(`Expected GridSelection anchor to be (or a child of) GridCellNode`);
72
- }
73
- const anchorCellNodeRect = lexical.DEPRECATED_$getGridCellNodeRect(anchorCellNode);
74
- if (!(anchorCellNodeRect !== null)) {
75
- throw Error(`getCellRect: expected to find AnchorNode`);
76
- }
77
- const focusCellNode = lexical.$getNodeByKey(this.focus.key);
78
- if (!lexical.DEPRECATED_$isGridCellNode(focusCellNode)) {
79
- throw Error(`Expected GridSelection focus to be (or a child of) GridCellNode`);
80
- }
81
- const focusCellNodeRect = lexical.DEPRECATED_$getGridCellNodeRect(focusCellNode);
82
- if (!(focusCellNodeRect !== null)) {
83
- throw Error(`getCellRect: expected to find focusCellNode`);
84
- }
85
- const startX = Math.min(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
86
- const stopX = Math.max(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
87
- const startY = Math.min(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
88
- const stopY = Math.max(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
89
- return {
90
- fromX: Math.min(startX, stopX),
91
- fromY: Math.min(startY, stopY),
92
- toX: Math.max(startX, stopX),
93
- toY: Math.max(startY, stopY)
94
- };
95
- }
96
- getNodes() {
97
- const cachedNodes = this._cachedNodes;
98
- if (cachedNodes !== null) {
99
- return cachedNodes;
100
- }
101
- const anchorNode = this.anchor.getNode();
102
- const focusNode = this.focus.getNode();
103
- const anchorCell = utils.$findMatchingParent(anchorNode, lexical.DEPRECATED_$isGridCellNode);
104
- // todo replace with triplet
105
- const focusCell = utils.$findMatchingParent(focusNode, lexical.DEPRECATED_$isGridCellNode);
106
- if (!lexical.DEPRECATED_$isGridCellNode(anchorCell)) {
107
- throw Error(`Expected GridSelection anchor to be (or a child of) GridCellNode`);
108
- }
109
- if (!lexical.DEPRECATED_$isGridCellNode(focusCell)) {
110
- throw Error(`Expected GridSelection focus to be (or a child of) GridCellNode`);
111
- }
112
- const anchorRow = anchorCell.getParent();
113
- if (!lexical.DEPRECATED_$isGridRowNode(anchorRow)) {
114
- throw Error(`Expected anchorCell to have a parent GridRowNode`);
115
- }
116
- const gridNode = anchorRow.getParent();
117
- if (!lexical.DEPRECATED_$isGridNode(gridNode)) {
118
- throw Error(`Expected tableNode to have a parent GridNode`);
119
- }
120
- const focusCellGrid = focusCell.getParents()[1];
121
- if (focusCellGrid !== gridNode) {
122
- if (!gridNode.isParentOf(focusCell)) {
123
- // focus is on higher Grid level than anchor
124
- const gridParent = gridNode.getParent();
125
- if (!(gridParent != null)) {
126
- throw Error(`Expected gridParent to have a parent`);
127
- }
128
- this.set(this.gridKey, gridParent.getKey(), focusCell.getKey());
129
- } else {
130
- // anchor is on higher Grid level than focus
131
- const focusCellParent = focusCellGrid.getParent();
132
- if (!(focusCellParent != null)) {
133
- throw Error(`Expected focusCellParent to have a parent`);
134
- }
135
- this.set(this.gridKey, focusCell.getKey(), focusCellParent.getKey());
136
- }
137
- return this.getNodes();
138
- }
139
-
140
- // TODO Mapping the whole Grid every time not efficient. We need to compute the entire state only
141
- // once (on load) and iterate on it as updates occur. However, to do this we need to have the
142
- // ability to store a state. Killing GridSelection and moving the logic to the plugin would make
143
- // this possible.
144
- const [map, cellAMap, cellBMap] = lexical.DEPRECATED_$computeGridMap(gridNode, anchorCell, focusCell);
145
- let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
146
- let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
147
- let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
148
- let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
149
- let exploredMinColumn = minColumn;
150
- let exploredMinRow = minRow;
151
- let exploredMaxColumn = minColumn;
152
- let exploredMaxRow = minRow;
153
- function expandBoundary(mapValue) {
154
- const {
155
- cell,
156
- startColumn: cellStartColumn,
157
- startRow: cellStartRow
158
- } = mapValue;
159
- minColumn = Math.min(minColumn, cellStartColumn);
160
- minRow = Math.min(minRow, cellStartRow);
161
- maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
162
- maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
163
- }
164
- while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
165
- if (minColumn < exploredMinColumn) {
166
- // Expand on the left
167
- const rowDiff = exploredMaxRow - exploredMinRow;
168
- const previousColumn = exploredMinColumn - 1;
169
- for (let i = 0; i <= rowDiff; i++) {
170
- expandBoundary(map[exploredMinRow + i][previousColumn]);
171
- }
172
- exploredMinColumn = previousColumn;
173
- }
174
- if (minRow < exploredMinRow) {
175
- // Expand on top
176
- const columnDiff = exploredMaxColumn - exploredMinColumn;
177
- const previousRow = exploredMinRow - 1;
178
- for (let i = 0; i <= columnDiff; i++) {
179
- expandBoundary(map[previousRow][exploredMinColumn + i]);
180
- }
181
- exploredMinRow = previousRow;
182
- }
183
- if (maxColumn > exploredMaxColumn) {
184
- // Expand on the right
185
- const rowDiff = exploredMaxRow - exploredMinRow;
186
- const nextColumn = exploredMaxColumn + 1;
187
- for (let i = 0; i <= rowDiff; i++) {
188
- expandBoundary(map[exploredMinRow + i][nextColumn]);
189
- }
190
- exploredMaxColumn = nextColumn;
191
- }
192
- if (maxRow > exploredMaxRow) {
193
- // Expand on the bottom
194
- const columnDiff = exploredMaxColumn - exploredMinColumn;
195
- const nextRow = exploredMaxRow + 1;
196
- for (let i = 0; i <= columnDiff; i++) {
197
- expandBoundary(map[nextRow][exploredMinColumn + i]);
198
- }
199
- exploredMaxRow = nextRow;
200
- }
201
- }
202
- const nodes = [gridNode];
203
- let lastRow = null;
204
- for (let i = minRow; i <= maxRow; i++) {
205
- for (let j = minColumn; j <= maxColumn; j++) {
206
- const {
207
- cell
208
- } = map[i][j];
209
- const currentRow = cell.getParent();
210
- if (!lexical.DEPRECATED_$isGridRowNode(currentRow)) {
211
- throw Error(`Expected GridCellNode parent to be a GridRowNode`);
212
- }
213
- if (currentRow !== lastRow) {
214
- nodes.push(currentRow);
215
- }
216
- nodes.push(cell, ...$getChildrenRecursively(cell));
217
- lastRow = currentRow;
218
- }
219
- }
220
- if (!lexical.isCurrentlyReadOnlyMode()) {
221
- this._cachedNodes = nodes;
222
- }
223
- return nodes;
224
- }
225
- getTextContent() {
226
- const nodes = this.getNodes();
227
- let textContent = '';
228
- for (let i = 0; i < nodes.length; i++) {
229
- textContent += nodes[i].getTextContent();
230
- }
231
- return textContent;
232
- }
233
- }
234
- function $isGridSelection(x) {
235
- return x instanceof GridSelection;
236
- }
237
- function $createGridSelection() {
238
- const anchor = lexical.$createPoint('root', 0, 'element');
239
- const focus = lexical.$createPoint('root', 0, 'element');
240
- return new GridSelection('root', anchor, focus);
241
- }
242
- function $getChildrenRecursively(node) {
243
- const nodes = [];
244
- const stack = [node];
245
- while (stack.length > 0) {
246
- const currentNode = stack.pop();
247
- if (!(currentNode !== undefined)) {
248
- throw Error(`Stack.length > 0; can't be undefined`);
249
- }
250
- if (lexical.$isElementNode(currentNode)) {
251
- stack.unshift(...currentNode.getChildren());
252
- }
253
- if (currentNode !== node) {
254
- nodes.push(currentNode);
255
- }
256
- }
257
- return nodes;
258
- }
10
+ var lexical = require('lexical');
259
11
 
260
12
  /**
261
13
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -281,7 +33,11 @@ const TableCellHeaderStates = {
281
33
  ROW: 1
282
34
  };
283
35
  /** @noInheritDoc */
284
- class TableCellNode extends lexical.DEPRECATED_GridCellNode {
36
+ class TableCellNode extends lexical.ElementNode {
37
+ /** @internal */
38
+
39
+ /** @internal */
40
+
285
41
  /** @internal */
286
42
 
287
43
  /** @internal */
@@ -318,7 +74,9 @@ class TableCellNode extends lexical.DEPRECATED_GridCellNode {
318
74
  return cellNode;
319
75
  }
320
76
  constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
321
- super(colSpan, key);
77
+ super(key);
78
+ this.__colSpan = colSpan;
79
+ this.__rowSpan = 1;
322
80
  this.__headerState = headerState;
323
81
  this.__width = width;
324
82
  this.__backgroundColor = null;
@@ -373,11 +131,27 @@ class TableCellNode extends lexical.DEPRECATED_GridCellNode {
373
131
  return {
374
132
  ...super.exportJSON(),
375
133
  backgroundColor: this.getBackgroundColor(),
134
+ colSpan: this.__colSpan,
376
135
  headerState: this.__headerState,
136
+ rowSpan: this.__rowSpan,
377
137
  type: 'tablecell',
378
138
  width: this.getWidth()
379
139
  };
380
140
  }
141
+ getColSpan() {
142
+ return this.__colSpan;
143
+ }
144
+ setColSpan(colSpan) {
145
+ this.getWritable().__colSpan = colSpan;
146
+ return this;
147
+ }
148
+ getRowSpan() {
149
+ return this.__rowSpan;
150
+ }
151
+ setRowSpan(rowSpan) {
152
+ this.getWritable().__rowSpan = rowSpan;
153
+ return this;
154
+ }
381
155
  getTag() {
382
156
  return this.hasHeader() ? 'th' : 'td';
383
157
  }
@@ -469,6 +243,15 @@ function $isTableCellNode(node) {
469
243
  return node instanceof TableCellNode;
470
244
  }
471
245
 
246
+ /**
247
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
248
+ *
249
+ * This source code is licensed under the MIT license found in the
250
+ * LICENSE file in the root directory of this source tree.
251
+ *
252
+ */
253
+ const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
254
+
472
255
  /**
473
256
  * Copyright (c) Meta Platforms, Inc. and affiliates.
474
257
  *
@@ -477,7 +260,7 @@ function $isTableCellNode(node) {
477
260
  *
478
261
  */
479
262
  /** @noInheritDoc */
480
- class TableRowNode extends lexical.DEPRECATED_GridRowNode {
263
+ class TableRowNode extends lexical.ElementNode {
481
264
  /** @internal */
482
265
 
483
266
  static getType() {
@@ -571,42 +354,967 @@ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !==
571
354
  * LICENSE file in the root directory of this source tree.
572
355
  *
573
356
  */
574
- const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
575
- class TableSelection {
576
- constructor(editor, tableNodeKey) {
577
- this.isHighlightingCells = false;
578
- this.anchorX = -1;
579
- this.anchorY = -1;
580
- this.focusX = -1;
581
- this.focusY = -1;
582
- this.listenersToRemove = new Set();
583
- this.tableNodeKey = tableNodeKey;
584
- this.editor = editor;
585
- this.grid = {
586
- cells: [],
587
- columns: 0,
588
- rows: 0
589
- };
590
- this.gridSelection = null;
591
- this.anchorCellNodeKey = null;
592
- this.focusCellNodeKey = null;
593
- this.anchorCell = null;
594
- this.focusCell = null;
595
- this.hasHijackedSelectionStyles = false;
596
- this.trackTableGrid();
597
- }
598
- getGrid() {
599
- return this.grid;
600
- }
601
- removeListeners() {
602
- Array.from(this.listenersToRemove).forEach(removeListener => removeListener());
603
- }
604
- trackTableGrid() {
605
- const observer = new MutationObserver(records => {
606
- this.editor.update(() => {
607
- let gridNeedsRedraw = false;
608
- for (let i = 0; i < records.length; i++) {
609
- const record = records[i];
357
+ function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders = true) {
358
+ const tableNode = $createTableNode();
359
+ for (let iRow = 0; iRow < rowCount; iRow++) {
360
+ const tableRowNode = $createTableRowNode();
361
+ for (let iColumn = 0; iColumn < columnCount; iColumn++) {
362
+ let headerState = TableCellHeaderStates.NO_STATUS;
363
+ if (typeof includeHeaders === 'object') {
364
+ if (iRow === 0 && includeHeaders.rows) headerState |= TableCellHeaderStates.ROW;
365
+ if (iColumn === 0 && includeHeaders.columns) headerState |= TableCellHeaderStates.COLUMN;
366
+ } else if (includeHeaders) {
367
+ if (iRow === 0) headerState |= TableCellHeaderStates.ROW;
368
+ if (iColumn === 0) headerState |= TableCellHeaderStates.COLUMN;
369
+ }
370
+ const tableCellNode = $createTableCellNode(headerState);
371
+ const paragraphNode = lexical.$createParagraphNode();
372
+ paragraphNode.append(lexical.$createTextNode());
373
+ tableCellNode.append(paragraphNode);
374
+ tableRowNode.append(tableCellNode);
375
+ }
376
+ tableNode.append(tableRowNode);
377
+ }
378
+ return tableNode;
379
+ }
380
+ function $getTableCellNodeFromLexicalNode(startingNode) {
381
+ const node = utils.$findMatchingParent(startingNode, n => $isTableCellNode(n));
382
+ if ($isTableCellNode(node)) {
383
+ return node;
384
+ }
385
+ return null;
386
+ }
387
+ function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
388
+ const node = utils.$findMatchingParent(startingNode, n => $isTableRowNode(n));
389
+ if ($isTableRowNode(node)) {
390
+ return node;
391
+ }
392
+ throw new Error('Expected table cell to be inside of table row.');
393
+ }
394
+ function $getTableNodeFromLexicalNodeOrThrow(startingNode) {
395
+ const node = utils.$findMatchingParent(startingNode, n => $isTableNode(n));
396
+ if ($isTableNode(node)) {
397
+ return node;
398
+ }
399
+ throw new Error('Expected table cell to be inside of table.');
400
+ }
401
+ function $getTableRowIndexFromTableCellNode(tableCellNode) {
402
+ const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
403
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableRowNode);
404
+ return tableNode.getChildren().findIndex(n => n.is(tableRowNode));
405
+ }
406
+ function $getTableColumnIndexFromTableCellNode(tableCellNode) {
407
+ const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
408
+ return tableRowNode.getChildren().findIndex(n => n.is(tableCellNode));
409
+ }
410
+ function $getTableCellSiblingsFromTableCellNode(tableCellNode, table) {
411
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
412
+ const {
413
+ x,
414
+ y
415
+ } = tableNode.getCordsFromCellNode(tableCellNode, table);
416
+ return {
417
+ above: tableNode.getCellNodeFromCords(x, y - 1, table),
418
+ below: tableNode.getCellNodeFromCords(x, y + 1, table),
419
+ left: tableNode.getCellNodeFromCords(x - 1, y, table),
420
+ right: tableNode.getCellNodeFromCords(x + 1, y, table)
421
+ };
422
+ }
423
+ function $removeTableRowAtIndex(tableNode, indexToDelete) {
424
+ const tableRows = tableNode.getChildren();
425
+ if (indexToDelete >= tableRows.length || indexToDelete < 0) {
426
+ throw new Error('Expected table cell to be inside of table row.');
427
+ }
428
+ const targetRowNode = tableRows[indexToDelete];
429
+ targetRowNode.remove();
430
+ return tableNode;
431
+ }
432
+ function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, table) {
433
+ const tableRows = tableNode.getChildren();
434
+ if (targetIndex >= tableRows.length || targetIndex < 0) {
435
+ throw new Error('Table row target index out of range');
436
+ }
437
+ const targetRowNode = tableRows[targetIndex];
438
+ if ($isTableRowNode(targetRowNode)) {
439
+ for (let r = 0; r < rowCount; r++) {
440
+ const tableRowCells = targetRowNode.getChildren();
441
+ const tableColumnCount = tableRowCells.length;
442
+ const newTableRowNode = $createTableRowNode();
443
+ for (let c = 0; c < tableColumnCount; c++) {
444
+ const tableCellFromTargetRow = tableRowCells[c];
445
+ if (!$isTableCellNode(tableCellFromTargetRow)) {
446
+ throw Error(`Expected table cell`);
447
+ }
448
+ const {
449
+ above,
450
+ below
451
+ } = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow, table);
452
+ let headerState = TableCellHeaderStates.NO_STATUS;
453
+ const width = above && above.getWidth() || below && below.getWidth() || undefined;
454
+ if (above && above.hasHeaderState(TableCellHeaderStates.COLUMN) || below && below.hasHeaderState(TableCellHeaderStates.COLUMN)) {
455
+ headerState |= TableCellHeaderStates.COLUMN;
456
+ }
457
+ const tableCellNode = $createTableCellNode(headerState, 1, width);
458
+ tableCellNode.append(lexical.$createParagraphNode());
459
+ newTableRowNode.append(tableCellNode);
460
+ }
461
+ if (shouldInsertAfter) {
462
+ targetRowNode.insertAfter(newTableRowNode);
463
+ } else {
464
+ targetRowNode.insertBefore(newTableRowNode);
465
+ }
466
+ }
467
+ } else {
468
+ throw new Error('Row before insertion index does not exist.');
469
+ }
470
+ return tableNode;
471
+ }
472
+ const getHeaderState = (currentState, possibleState) => {
473
+ if (currentState === TableCellHeaderStates.BOTH || currentState === possibleState) {
474
+ return possibleState;
475
+ }
476
+ return TableCellHeaderStates.NO_STATUS;
477
+ };
478
+ function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
479
+ const selection = lexical.$getSelection();
480
+ if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
481
+ throw Error(`Expected a RangeSelection or GridSelection`);
482
+ }
483
+ const focus = selection.focus.getNode();
484
+ const [focusCell,, grid] = $getNodeTriplet(focus);
485
+ const [gridMap, focusCellMap] = $computeTableMap(grid, focusCell, focusCell);
486
+ const columnCount = gridMap[0].length;
487
+ const {
488
+ startRow: focusStartRow
489
+ } = focusCellMap;
490
+ if (insertAfter) {
491
+ const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
492
+ const focusEndRowMap = gridMap[focusEndRow];
493
+ const newRow = $createTableRowNode();
494
+ for (let i = 0; i < columnCount; i++) {
495
+ const {
496
+ cell,
497
+ startRow
498
+ } = focusEndRowMap[i];
499
+ if (startRow + cell.__rowSpan - 1 <= focusEndRow) {
500
+ const currentCell = focusEndRowMap[i].cell;
501
+ const currentCellHeaderState = currentCell.__headerState;
502
+ const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.COLUMN);
503
+ newRow.append($createTableCellNode(headerState).append(lexical.$createParagraphNode()));
504
+ } else {
505
+ cell.setRowSpan(cell.__rowSpan + 1);
506
+ }
507
+ }
508
+ const focusEndRowNode = grid.getChildAtIndex(focusEndRow);
509
+ if (!$isTableRowNode(focusEndRowNode)) {
510
+ throw Error(`focusEndRow is not a TableRowNode`);
511
+ }
512
+ focusEndRowNode.insertAfter(newRow);
513
+ } else {
514
+ const focusStartRowMap = gridMap[focusStartRow];
515
+ const newRow = $createTableRowNode();
516
+ for (let i = 0; i < columnCount; i++) {
517
+ const {
518
+ cell,
519
+ startRow
520
+ } = focusStartRowMap[i];
521
+ if (startRow === focusStartRow) {
522
+ const currentCell = focusStartRowMap[i].cell;
523
+ const currentCellHeaderState = currentCell.__headerState;
524
+ const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.COLUMN);
525
+ newRow.append($createTableCellNode(headerState).append(lexical.$createParagraphNode()));
526
+ } else {
527
+ cell.setRowSpan(cell.__rowSpan + 1);
528
+ }
529
+ }
530
+ const focusStartRowNode = grid.getChildAtIndex(focusStartRow);
531
+ if (!$isTableRowNode(focusStartRowNode)) {
532
+ throw Error(`focusEndRow is not a TableRowNode`);
533
+ }
534
+ focusStartRowNode.insertBefore(newRow);
535
+ }
536
+ }
537
+ function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, columnCount, table) {
538
+ const tableRows = tableNode.getChildren();
539
+ const tableCellsToBeInserted = [];
540
+ for (let r = 0; r < tableRows.length; r++) {
541
+ const currentTableRowNode = tableRows[r];
542
+ if ($isTableRowNode(currentTableRowNode)) {
543
+ for (let c = 0; c < columnCount; c++) {
544
+ const tableRowChildren = currentTableRowNode.getChildren();
545
+ if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
546
+ throw new Error('Table column target index out of range');
547
+ }
548
+ const targetCell = tableRowChildren[targetIndex];
549
+ if (!$isTableCellNode(targetCell)) {
550
+ throw Error(`Expected table cell`);
551
+ }
552
+ const {
553
+ left,
554
+ right
555
+ } = $getTableCellSiblingsFromTableCellNode(targetCell, table);
556
+ let headerState = TableCellHeaderStates.NO_STATUS;
557
+ if (left && left.hasHeaderState(TableCellHeaderStates.ROW) || right && right.hasHeaderState(TableCellHeaderStates.ROW)) {
558
+ headerState |= TableCellHeaderStates.ROW;
559
+ }
560
+ const newTableCell = $createTableCellNode(headerState);
561
+ newTableCell.append(lexical.$createParagraphNode());
562
+ tableCellsToBeInserted.push({
563
+ newTableCell,
564
+ targetCell
565
+ });
566
+ }
567
+ }
568
+ }
569
+ tableCellsToBeInserted.forEach(({
570
+ newTableCell,
571
+ targetCell
572
+ }) => {
573
+ if (shouldInsertAfter) {
574
+ targetCell.insertAfter(newTableCell);
575
+ } else {
576
+ targetCell.insertBefore(newTableCell);
577
+ }
578
+ });
579
+ return tableNode;
580
+ }
581
+ function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
582
+ const selection = lexical.$getSelection();
583
+ if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
584
+ throw Error(`Expected a RangeSelection or GridSelection`);
585
+ }
586
+ const anchor = selection.anchor.getNode();
587
+ const focus = selection.focus.getNode();
588
+ const [anchorCell] = $getNodeTriplet(anchor);
589
+ const [focusCell,, grid] = $getNodeTriplet(focus);
590
+ const [gridMap, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
591
+ const rowCount = gridMap.length;
592
+ const startColumn = insertAfter ? Math.max(focusCellMap.startColumn, anchorCellMap.startColumn) : Math.min(focusCellMap.startColumn, anchorCellMap.startColumn);
593
+ const insertAfterColumn = insertAfter ? startColumn + focusCell.__colSpan - 1 : startColumn - 1;
594
+ const gridFirstChild = grid.getFirstChild();
595
+ if (!$isTableRowNode(gridFirstChild)) {
596
+ throw Error(`Expected firstTable child to be a row`);
597
+ }
598
+ let firstInsertedCell = null;
599
+ function $createTableCellNodeForInsertTableColumn(headerState = TableCellHeaderStates.NO_STATUS) {
600
+ const cell = $createTableCellNode(headerState).append(lexical.$createParagraphNode());
601
+ if (firstInsertedCell === null) {
602
+ firstInsertedCell = cell;
603
+ }
604
+ return cell;
605
+ }
606
+ let loopRow = gridFirstChild;
607
+ rowLoop: for (let i = 0; i < rowCount; i++) {
608
+ if (i !== 0) {
609
+ const currentRow = loopRow.getNextSibling();
610
+ if (!$isTableRowNode(currentRow)) {
611
+ throw Error(`Expected row nextSibling to be a row`);
612
+ }
613
+ loopRow = currentRow;
614
+ }
615
+ const rowMap = gridMap[i];
616
+ const currentCellHeaderState = rowMap[insertAfterColumn < 0 ? 0 : insertAfterColumn].cell.__headerState;
617
+ const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.ROW);
618
+ if (insertAfterColumn < 0) {
619
+ $insertFirst(loopRow, $createTableCellNodeForInsertTableColumn(headerState));
620
+ continue;
621
+ }
622
+ const {
623
+ cell: currentCell,
624
+ startColumn: currentStartColumn,
625
+ startRow: currentStartRow
626
+ } = rowMap[insertAfterColumn];
627
+ if (currentStartColumn + currentCell.__colSpan - 1 <= insertAfterColumn) {
628
+ let insertAfterCell = currentCell;
629
+ let insertAfterCellRowStart = currentStartRow;
630
+ let prevCellIndex = insertAfterColumn;
631
+ while (insertAfterCellRowStart !== i && insertAfterCell.__rowSpan > 1) {
632
+ prevCellIndex -= currentCell.__colSpan;
633
+ if (prevCellIndex >= 0) {
634
+ const {
635
+ cell: cell_,
636
+ startRow: startRow_
637
+ } = rowMap[prevCellIndex];
638
+ insertAfterCell = cell_;
639
+ insertAfterCellRowStart = startRow_;
640
+ } else {
641
+ loopRow.append($createTableCellNodeForInsertTableColumn(headerState));
642
+ continue rowLoop;
643
+ }
644
+ }
645
+ insertAfterCell.insertAfter($createTableCellNodeForInsertTableColumn(headerState));
646
+ } else {
647
+ currentCell.setColSpan(currentCell.__colSpan + 1);
648
+ }
649
+ }
650
+ if (firstInsertedCell !== null) {
651
+ $moveSelectionToCell(firstInsertedCell);
652
+ }
653
+ }
654
+ function $deleteTableColumn(tableNode, targetIndex) {
655
+ const tableRows = tableNode.getChildren();
656
+ for (let i = 0; i < tableRows.length; i++) {
657
+ const currentTableRowNode = tableRows[i];
658
+ if ($isTableRowNode(currentTableRowNode)) {
659
+ const tableRowChildren = currentTableRowNode.getChildren();
660
+ if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
661
+ throw new Error('Table column target index out of range');
662
+ }
663
+ tableRowChildren[targetIndex].remove();
664
+ }
665
+ }
666
+ return tableNode;
667
+ }
668
+ function $deleteTableRow__EXPERIMENTAL() {
669
+ const selection = lexical.$getSelection();
670
+ if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
671
+ throw Error(`Expected a RangeSelection or GridSelection`);
672
+ }
673
+ const anchor = selection.anchor.getNode();
674
+ const focus = selection.focus.getNode();
675
+ const [anchorCell,, grid] = $getNodeTriplet(anchor);
676
+ const [focusCell] = $getNodeTriplet(focus);
677
+ const [gridMap, anchorCellMap, focusCellMap] = $computeTableMap(grid, anchorCell, focusCell);
678
+ const {
679
+ startRow: anchorStartRow
680
+ } = anchorCellMap;
681
+ const {
682
+ startRow: focusStartRow
683
+ } = focusCellMap;
684
+ const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
685
+ if (gridMap.length === focusEndRow - anchorStartRow + 1) {
686
+ // Empty grid
687
+ grid.remove();
688
+ return;
689
+ }
690
+ const columnCount = gridMap[0].length;
691
+ const nextRow = gridMap[focusEndRow + 1];
692
+ const nextRowNode = grid.getChildAtIndex(focusEndRow + 1);
693
+ for (let row = focusEndRow; row >= anchorStartRow; row--) {
694
+ for (let column = columnCount - 1; column >= 0; column--) {
695
+ const {
696
+ cell,
697
+ startRow: cellStartRow,
698
+ startColumn: cellStartColumn
699
+ } = gridMap[row][column];
700
+ if (cellStartColumn !== column) {
701
+ // Don't repeat work for the same Cell
702
+ continue;
703
+ }
704
+ // Rows overflowing top have to be trimmed
705
+ if (row === anchorStartRow && cellStartRow < anchorStartRow) {
706
+ cell.setRowSpan(cell.__rowSpan - (cellStartRow - anchorStartRow));
707
+ }
708
+ // Rows overflowing bottom have to be trimmed and moved to the next row
709
+ if (cellStartRow >= anchorStartRow && cellStartRow + cell.__rowSpan - 1 > focusEndRow) {
710
+ cell.setRowSpan(cell.__rowSpan - (focusEndRow - cellStartRow + 1));
711
+ if (!(nextRowNode !== null)) {
712
+ throw Error(`Expected nextRowNode not to be null`);
713
+ }
714
+ if (column === 0) {
715
+ $insertFirst(nextRowNode, cell);
716
+ } else {
717
+ const {
718
+ cell: previousCell
719
+ } = nextRow[column - 1];
720
+ previousCell.insertAfter(cell);
721
+ }
722
+ }
723
+ }
724
+ const rowNode = grid.getChildAtIndex(row);
725
+ if (!$isTableRowNode(rowNode)) {
726
+ throw Error(`Expected GridNode childAtIndex(${String(row)}) to be RowNode`);
727
+ }
728
+ rowNode.remove();
729
+ }
730
+ if (nextRow !== undefined) {
731
+ const {
732
+ cell
733
+ } = nextRow[0];
734
+ $moveSelectionToCell(cell);
735
+ } else {
736
+ const previousRow = gridMap[anchorStartRow - 1];
737
+ const {
738
+ cell
739
+ } = previousRow[0];
740
+ $moveSelectionToCell(cell);
741
+ }
742
+ }
743
+ function $deleteTableColumn__EXPERIMENTAL() {
744
+ const selection = lexical.$getSelection();
745
+ if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
746
+ throw Error(`Expected a RangeSelection or GridSelection`);
747
+ }
748
+ const anchor = selection.anchor.getNode();
749
+ const focus = selection.focus.getNode();
750
+ const [anchorCell,, grid] = $getNodeTriplet(anchor);
751
+ const [focusCell] = $getNodeTriplet(focus);
752
+ const [gridMap, anchorCellMap, focusCellMap] = $computeTableMap(grid, anchorCell, focusCell);
753
+ const {
754
+ startColumn: anchorStartColumn
755
+ } = anchorCellMap;
756
+ const {
757
+ startRow: focusStartRow,
758
+ startColumn: focusStartColumn
759
+ } = focusCellMap;
760
+ const startColumn = Math.min(anchorStartColumn, focusStartColumn);
761
+ const endColumn = Math.max(anchorStartColumn + anchorCell.__colSpan - 1, focusStartColumn + focusCell.__colSpan - 1);
762
+ const selectedColumnCount = endColumn - startColumn + 1;
763
+ const columnCount = gridMap[0].length;
764
+ if (columnCount === endColumn - startColumn + 1) {
765
+ // Empty grid
766
+ grid.selectPrevious();
767
+ grid.remove();
768
+ return;
769
+ }
770
+ const rowCount = gridMap.length;
771
+ for (let row = 0; row < rowCount; row++) {
772
+ for (let column = startColumn; column <= endColumn; column++) {
773
+ const {
774
+ cell,
775
+ startColumn: cellStartColumn
776
+ } = gridMap[row][column];
777
+ if (cellStartColumn < startColumn) {
778
+ if (column === startColumn) {
779
+ const overflowLeft = startColumn - cellStartColumn;
780
+ // Overflowing left
781
+ cell.setColSpan(cell.__colSpan -
782
+ // Possible overflow right too
783
+ Math.min(selectedColumnCount, cell.__colSpan - overflowLeft));
784
+ }
785
+ } else if (cellStartColumn + cell.__colSpan - 1 > endColumn) {
786
+ if (column === endColumn) {
787
+ // Overflowing right
788
+ const inSelectedArea = endColumn - cellStartColumn + 1;
789
+ cell.setColSpan(cell.__colSpan - inSelectedArea);
790
+ }
791
+ } else {
792
+ cell.remove();
793
+ }
794
+ }
795
+ }
796
+ const focusRowMap = gridMap[focusStartRow];
797
+ const nextColumn = focusRowMap[focusStartColumn + focusCell.__colSpan];
798
+ if (nextColumn !== undefined) {
799
+ const {
800
+ cell
801
+ } = nextColumn;
802
+ $moveSelectionToCell(cell);
803
+ } else {
804
+ const previousRow = focusRowMap[focusStartColumn - 1];
805
+ const {
806
+ cell
807
+ } = previousRow;
808
+ $moveSelectionToCell(cell);
809
+ }
810
+ }
811
+ function $moveSelectionToCell(cell) {
812
+ const firstDescendant = cell.getFirstDescendant();
813
+ if (firstDescendant == null) {
814
+ cell.selectStart();
815
+ } else {
816
+ firstDescendant.getParentOrThrow().selectStart();
817
+ }
818
+ }
819
+ function $insertFirst(parent, node) {
820
+ const firstChild = parent.getFirstChild();
821
+ if (firstChild !== null) {
822
+ firstChild.insertBefore(node);
823
+ } else {
824
+ parent.append(node);
825
+ }
826
+ }
827
+ function $unmergeCell() {
828
+ const selection = lexical.$getSelection();
829
+ if (!(lexical.$isRangeSelection(selection) || $isTableSelection(selection))) {
830
+ throw Error(`Expected a RangeSelection or GridSelection`);
831
+ }
832
+ const anchor = selection.anchor.getNode();
833
+ const [cell, row, grid] = $getNodeTriplet(anchor);
834
+ const colSpan = cell.__colSpan;
835
+ const rowSpan = cell.__rowSpan;
836
+ if (colSpan > 1) {
837
+ for (let i = 1; i < colSpan; i++) {
838
+ cell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
839
+ }
840
+ cell.setColSpan(1);
841
+ }
842
+ if (rowSpan > 1) {
843
+ const [map, cellMap] = $computeTableMap(grid, cell, cell);
844
+ const {
845
+ startColumn,
846
+ startRow
847
+ } = cellMap;
848
+ let currentRowNode;
849
+ for (let i = 1; i < rowSpan; i++) {
850
+ const currentRow = startRow + i;
851
+ const currentRowMap = map[currentRow];
852
+ currentRowNode = (currentRowNode || row).getNextSibling();
853
+ if (!$isTableRowNode(currentRowNode)) {
854
+ throw Error(`Expected row next sibling to be a row`);
855
+ }
856
+ let insertAfterCell = null;
857
+ for (let column = 0; column < startColumn; column++) {
858
+ const currentCellMap = currentRowMap[column];
859
+ const currentCell = currentCellMap.cell;
860
+ if (currentCellMap.startRow === currentRow) {
861
+ insertAfterCell = currentCell;
862
+ }
863
+ if (currentCell.__colSpan > 1) {
864
+ column += currentCell.__colSpan - 1;
865
+ }
866
+ }
867
+ if (insertAfterCell === null) {
868
+ for (let j = 0; j < colSpan; j++) {
869
+ $insertFirst(currentRowNode, $createTableCellNode(TableCellHeaderStates.NO_STATUS));
870
+ }
871
+ } else {
872
+ for (let j = 0; j < colSpan; j++) {
873
+ insertAfterCell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
874
+ }
875
+ }
876
+ }
877
+ cell.setRowSpan(1);
878
+ }
879
+ }
880
+ function $computeTableMap(grid, cellA, cellB) {
881
+ const tableMap = [];
882
+ let cellAValue = null;
883
+ let cellBValue = null;
884
+ function write(startRow, startColumn, cell) {
885
+ const value = {
886
+ cell,
887
+ startColumn,
888
+ startRow
889
+ };
890
+ const rowSpan = cell.__rowSpan;
891
+ const colSpan = cell.__colSpan;
892
+ for (let i = 0; i < rowSpan; i++) {
893
+ if (tableMap[startRow + i] === undefined) {
894
+ tableMap[startRow + i] = [];
895
+ }
896
+ for (let j = 0; j < colSpan; j++) {
897
+ tableMap[startRow + i][startColumn + j] = value;
898
+ }
899
+ }
900
+ if (cellA.is(cell)) {
901
+ cellAValue = value;
902
+ }
903
+ if (cellB.is(cell)) {
904
+ cellBValue = value;
905
+ }
906
+ }
907
+ function isEmpty(row, column) {
908
+ return tableMap[row] === undefined || tableMap[row][column] === undefined;
909
+ }
910
+ const gridChildren = grid.getChildren();
911
+ for (let i = 0; i < gridChildren.length; i++) {
912
+ const row = gridChildren[i];
913
+ if (!$isTableRowNode(row)) {
914
+ throw Error(`Expected GridNode children to be TableRowNode`);
915
+ }
916
+ const rowChildren = row.getChildren();
917
+ let j = 0;
918
+ for (const cell of rowChildren) {
919
+ if (!$isTableCellNode(cell)) {
920
+ throw Error(`Expected TableRowNode children to be TableCellNode`);
921
+ }
922
+ while (!isEmpty(i, j)) {
923
+ j++;
924
+ }
925
+ write(i, j, cell);
926
+ j += cell.__colSpan;
927
+ }
928
+ }
929
+ if (!(cellAValue !== null)) {
930
+ throw Error(`Anchor not found in Grid`);
931
+ }
932
+ if (!(cellBValue !== null)) {
933
+ throw Error(`Focus not found in Grid`);
934
+ }
935
+ return [tableMap, cellAValue, cellBValue];
936
+ }
937
+ function $getNodeTriplet(source) {
938
+ let cell;
939
+ if (source instanceof TableCellNode) {
940
+ cell = source;
941
+ } else if ('__type' in source) {
942
+ const cell_ = utils.$findMatchingParent(source, $isTableCellNode);
943
+ if (!$isTableCellNode(cell_)) {
944
+ throw Error(`Expected to find a parent TableCellNode`);
945
+ }
946
+ cell = cell_;
947
+ } else {
948
+ const cell_ = utils.$findMatchingParent(source.getNode(), $isTableCellNode);
949
+ if (!$isTableCellNode(cell_)) {
950
+ throw Error(`Expected to find a parent TableCellNode`);
951
+ }
952
+ cell = cell_;
953
+ }
954
+ const row = cell.getParent();
955
+ if (!$isTableRowNode(row)) {
956
+ throw Error(`Expected TableCellNode to have a parent TableRowNode`);
957
+ }
958
+ const grid = row.getParent();
959
+ if (!$isTableNode(grid)) {
960
+ throw Error(`Expected TableRowNode to have a parent GridNode`);
961
+ }
962
+ return [cell, row, grid];
963
+ }
964
+ function $getTableCellNodeRect(tableCellNode) {
965
+ const [cellNode,, gridNode] = $getNodeTriplet(tableCellNode);
966
+ const rows = gridNode.getChildren();
967
+ const rowCount = rows.length;
968
+ const columnCount = rows[0].getChildren().length;
969
+
970
+ // Create a matrix of the same size as the table to track the position of each cell
971
+ const cellMatrix = new Array(rowCount);
972
+ for (let i = 0; i < rowCount; i++) {
973
+ cellMatrix[i] = new Array(columnCount);
974
+ }
975
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
976
+ const row = rows[rowIndex];
977
+ const cells = row.getChildren();
978
+ let columnIndex = 0;
979
+ for (let cellIndex = 0; cellIndex < cells.length; cellIndex++) {
980
+ // Find the next available position in the matrix, skip the position of merged cells
981
+ while (cellMatrix[rowIndex][columnIndex]) {
982
+ columnIndex++;
983
+ }
984
+ const cell = cells[cellIndex];
985
+ const rowSpan = cell.__rowSpan || 1;
986
+ const colSpan = cell.__colSpan || 1;
987
+
988
+ // Put the cell into the corresponding position in the matrix
989
+ for (let i = 0; i < rowSpan; i++) {
990
+ for (let j = 0; j < colSpan; j++) {
991
+ cellMatrix[rowIndex + i][columnIndex + j] = cell;
992
+ }
993
+ }
994
+
995
+ // Return to the original index, row span and column span of the cell.
996
+ if (cellNode === cell) {
997
+ return {
998
+ colSpan,
999
+ columnIndex,
1000
+ rowIndex,
1001
+ rowSpan
1002
+ };
1003
+ }
1004
+ columnIndex += colSpan;
1005
+ }
1006
+ }
1007
+ return null;
1008
+ }
1009
+
1010
+ /**
1011
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1012
+ *
1013
+ * This source code is licensed under the MIT license found in the
1014
+ * LICENSE file in the root directory of this source tree.
1015
+ *
1016
+ */
1017
+ class TableSelection {
1018
+ constructor(tableKey, anchor, focus) {
1019
+ this.anchor = anchor;
1020
+ this.focus = focus;
1021
+ anchor._selection = this;
1022
+ focus._selection = this;
1023
+ this._cachedNodes = null;
1024
+ this.dirty = false;
1025
+ this.tableKey = tableKey;
1026
+ }
1027
+ getStartEndPoints() {
1028
+ return [this.anchor, this.focus];
1029
+ }
1030
+
1031
+ /**
1032
+ * Returns whether the Selection is "backwards", meaning the focus
1033
+ * logically precedes the anchor in the EditorState.
1034
+ * @returns true if the Selection is backwards, false otherwise.
1035
+ */
1036
+ isBackward() {
1037
+ return this.focus.isBefore(this.anchor);
1038
+ }
1039
+ getCachedNodes() {
1040
+ return this._cachedNodes;
1041
+ }
1042
+ setCachedNodes(nodes) {
1043
+ this._cachedNodes = nodes;
1044
+ }
1045
+ is(selection) {
1046
+ if (!$isTableSelection(selection)) {
1047
+ return false;
1048
+ }
1049
+ return this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
1050
+ }
1051
+ set(tableKey, anchorCellKey, focusCellKey) {
1052
+ this.dirty = true;
1053
+ this.tableKey = tableKey;
1054
+ this.anchor.key = anchorCellKey;
1055
+ this.focus.key = focusCellKey;
1056
+ this._cachedNodes = null;
1057
+ }
1058
+ clone() {
1059
+ return new TableSelection(this.tableKey, this.anchor, this.focus);
1060
+ }
1061
+ isCollapsed() {
1062
+ return false;
1063
+ }
1064
+ extract() {
1065
+ return this.getNodes();
1066
+ }
1067
+ insertRawText(text) {
1068
+ // Do nothing?
1069
+ }
1070
+ insertText() {
1071
+ // Do nothing?
1072
+ }
1073
+ insertNodes(nodes) {
1074
+ const focusNode = this.focus.getNode();
1075
+ if (!lexical.$isElementNode(focusNode)) {
1076
+ throw Error(`Expected TableSelection focus to be an ElementNode`);
1077
+ }
1078
+ const selection = lexical.$normalizeSelection__EXPERIMENTAL(focusNode.select(0, focusNode.getChildrenSize()));
1079
+ selection.insertNodes(nodes);
1080
+ }
1081
+
1082
+ // TODO Deprecate this method. It's confusing when used with colspan|rowspan
1083
+ getShape() {
1084
+ const anchorCellNode = lexical.$getNodeByKey(this.anchor.key);
1085
+ if (!$isTableCellNode(anchorCellNode)) {
1086
+ throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
1087
+ }
1088
+ const anchorCellNodeRect = $getTableCellNodeRect(anchorCellNode);
1089
+ if (!(anchorCellNodeRect !== null)) {
1090
+ throw Error(`getCellRect: expected to find AnchorNode`);
1091
+ }
1092
+ const focusCellNode = lexical.$getNodeByKey(this.focus.key);
1093
+ if (!$isTableCellNode(focusCellNode)) {
1094
+ throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
1095
+ }
1096
+ const focusCellNodeRect = $getTableCellNodeRect(focusCellNode);
1097
+ if (!(focusCellNodeRect !== null)) {
1098
+ throw Error(`getCellRect: expected to find focusCellNode`);
1099
+ }
1100
+ const startX = Math.min(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
1101
+ const stopX = Math.max(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
1102
+ const startY = Math.min(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
1103
+ const stopY = Math.max(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
1104
+ return {
1105
+ fromX: Math.min(startX, stopX),
1106
+ fromY: Math.min(startY, stopY),
1107
+ toX: Math.max(startX, stopX),
1108
+ toY: Math.max(startY, stopY)
1109
+ };
1110
+ }
1111
+ getNodes() {
1112
+ const cachedNodes = this._cachedNodes;
1113
+ if (cachedNodes !== null) {
1114
+ return cachedNodes;
1115
+ }
1116
+ const anchorNode = this.anchor.getNode();
1117
+ const focusNode = this.focus.getNode();
1118
+ const anchorCell = utils.$findMatchingParent(anchorNode, $isTableCellNode);
1119
+ // todo replace with triplet
1120
+ const focusCell = utils.$findMatchingParent(focusNode, $isTableCellNode);
1121
+ if (!$isTableCellNode(anchorCell)) {
1122
+ throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
1123
+ }
1124
+ if (!$isTableCellNode(focusCell)) {
1125
+ throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
1126
+ }
1127
+ const anchorRow = anchorCell.getParent();
1128
+ if (!$isTableRowNode(anchorRow)) {
1129
+ throw Error(`Expected anchorCell to have a parent TableRowNode`);
1130
+ }
1131
+ const tableNode = anchorRow.getParent();
1132
+ if (!$isTableNode(tableNode)) {
1133
+ throw Error(`Expected tableNode to have a parent TableNode`);
1134
+ }
1135
+ const focusCellGrid = focusCell.getParents()[1];
1136
+ if (focusCellGrid !== tableNode) {
1137
+ if (!tableNode.isParentOf(focusCell)) {
1138
+ // focus is on higher Grid level than anchor
1139
+ const gridParent = tableNode.getParent();
1140
+ if (!(gridParent != null)) {
1141
+ throw Error(`Expected gridParent to have a parent`);
1142
+ }
1143
+ this.set(this.tableKey, gridParent.getKey(), focusCell.getKey());
1144
+ } else {
1145
+ // anchor is on higher Grid level than focus
1146
+ const focusCellParent = focusCellGrid.getParent();
1147
+ if (!(focusCellParent != null)) {
1148
+ throw Error(`Expected focusCellParent to have a parent`);
1149
+ }
1150
+ this.set(this.tableKey, focusCell.getKey(), focusCellParent.getKey());
1151
+ }
1152
+ return this.getNodes();
1153
+ }
1154
+
1155
+ // TODO Mapping the whole Grid every time not efficient. We need to compute the entire state only
1156
+ // once (on load) and iterate on it as updates occur. However, to do this we need to have the
1157
+ // ability to store a state. Killing TableSelection and moving the logic to the plugin would make
1158
+ // this possible.
1159
+ const [map, cellAMap, cellBMap] = $computeTableMap(tableNode, anchorCell, focusCell);
1160
+ let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
1161
+ let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
1162
+ let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
1163
+ let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
1164
+ let exploredMinColumn = minColumn;
1165
+ let exploredMinRow = minRow;
1166
+ let exploredMaxColumn = minColumn;
1167
+ let exploredMaxRow = minRow;
1168
+ function expandBoundary(mapValue) {
1169
+ const {
1170
+ cell,
1171
+ startColumn: cellStartColumn,
1172
+ startRow: cellStartRow
1173
+ } = mapValue;
1174
+ minColumn = Math.min(minColumn, cellStartColumn);
1175
+ minRow = Math.min(minRow, cellStartRow);
1176
+ maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
1177
+ maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
1178
+ }
1179
+ while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
1180
+ if (minColumn < exploredMinColumn) {
1181
+ // Expand on the left
1182
+ const rowDiff = exploredMaxRow - exploredMinRow;
1183
+ const previousColumn = exploredMinColumn - 1;
1184
+ for (let i = 0; i <= rowDiff; i++) {
1185
+ expandBoundary(map[exploredMinRow + i][previousColumn]);
1186
+ }
1187
+ exploredMinColumn = previousColumn;
1188
+ }
1189
+ if (minRow < exploredMinRow) {
1190
+ // Expand on top
1191
+ const columnDiff = exploredMaxColumn - exploredMinColumn;
1192
+ const previousRow = exploredMinRow - 1;
1193
+ for (let i = 0; i <= columnDiff; i++) {
1194
+ expandBoundary(map[previousRow][exploredMinColumn + i]);
1195
+ }
1196
+ exploredMinRow = previousRow;
1197
+ }
1198
+ if (maxColumn > exploredMaxColumn) {
1199
+ // Expand on the right
1200
+ const rowDiff = exploredMaxRow - exploredMinRow;
1201
+ const nextColumn = exploredMaxColumn + 1;
1202
+ for (let i = 0; i <= rowDiff; i++) {
1203
+ expandBoundary(map[exploredMinRow + i][nextColumn]);
1204
+ }
1205
+ exploredMaxColumn = nextColumn;
1206
+ }
1207
+ if (maxRow > exploredMaxRow) {
1208
+ // Expand on the bottom
1209
+ const columnDiff = exploredMaxColumn - exploredMinColumn;
1210
+ const nextRow = exploredMaxRow + 1;
1211
+ for (let i = 0; i <= columnDiff; i++) {
1212
+ expandBoundary(map[nextRow][exploredMinColumn + i]);
1213
+ }
1214
+ exploredMaxRow = nextRow;
1215
+ }
1216
+ }
1217
+ const nodes = [tableNode];
1218
+ let lastRow = null;
1219
+ for (let i = minRow; i <= maxRow; i++) {
1220
+ for (let j = minColumn; j <= maxColumn; j++) {
1221
+ const {
1222
+ cell
1223
+ } = map[i][j];
1224
+ const currentRow = cell.getParent();
1225
+ if (!$isTableRowNode(currentRow)) {
1226
+ throw Error(`Expected TableCellNode parent to be a TableRowNode`);
1227
+ }
1228
+ if (currentRow !== lastRow) {
1229
+ nodes.push(currentRow);
1230
+ }
1231
+ nodes.push(cell, ...$getChildrenRecursively(cell));
1232
+ lastRow = currentRow;
1233
+ }
1234
+ }
1235
+ if (!lexical.isCurrentlyReadOnlyMode()) {
1236
+ this._cachedNodes = nodes;
1237
+ }
1238
+ return nodes;
1239
+ }
1240
+ getTextContent() {
1241
+ const nodes = this.getNodes();
1242
+ let textContent = '';
1243
+ for (let i = 0; i < nodes.length; i++) {
1244
+ textContent += nodes[i].getTextContent();
1245
+ }
1246
+ return textContent;
1247
+ }
1248
+ }
1249
+ function $isTableSelection(x) {
1250
+ return x instanceof TableSelection;
1251
+ }
1252
+ function $createTableSelection() {
1253
+ const anchor = lexical.$createPoint('root', 0, 'element');
1254
+ const focus = lexical.$createPoint('root', 0, 'element');
1255
+ return new TableSelection('root', anchor, focus);
1256
+ }
1257
+ function $getChildrenRecursively(node) {
1258
+ const nodes = [];
1259
+ const stack = [node];
1260
+ while (stack.length > 0) {
1261
+ const currentNode = stack.pop();
1262
+ if (!(currentNode !== undefined)) {
1263
+ throw Error(`Stack.length > 0; can't be undefined`);
1264
+ }
1265
+ if (lexical.$isElementNode(currentNode)) {
1266
+ stack.unshift(...currentNode.getChildren());
1267
+ }
1268
+ if (currentNode !== node) {
1269
+ nodes.push(currentNode);
1270
+ }
1271
+ }
1272
+ return nodes;
1273
+ }
1274
+
1275
+ /**
1276
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1277
+ *
1278
+ * This source code is licensed under the MIT license found in the
1279
+ * LICENSE file in the root directory of this source tree.
1280
+ *
1281
+ */
1282
+ const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
1283
+ class TableObserver {
1284
+ constructor(editor, tableNodeKey) {
1285
+ this.isHighlightingCells = false;
1286
+ this.anchorX = -1;
1287
+ this.anchorY = -1;
1288
+ this.focusX = -1;
1289
+ this.focusY = -1;
1290
+ this.listenersToRemove = new Set();
1291
+ this.tableNodeKey = tableNodeKey;
1292
+ this.editor = editor;
1293
+ this.table = {
1294
+ columns: 0,
1295
+ domRows: [],
1296
+ rows: 0
1297
+ };
1298
+ this.tableSelection = null;
1299
+ this.anchorCellNodeKey = null;
1300
+ this.focusCellNodeKey = null;
1301
+ this.anchorCell = null;
1302
+ this.focusCell = null;
1303
+ this.hasHijackedSelectionStyles = false;
1304
+ this.trackTable();
1305
+ }
1306
+ getTable() {
1307
+ return this.table;
1308
+ }
1309
+ removeListeners() {
1310
+ Array.from(this.listenersToRemove).forEach(removeListener => removeListener());
1311
+ }
1312
+ trackTable() {
1313
+ const observer = new MutationObserver(records => {
1314
+ this.editor.update(() => {
1315
+ let gridNeedsRedraw = false;
1316
+ for (let i = 0; i < records.length; i++) {
1317
+ const record = records[i];
610
1318
  const target = record.target;
611
1319
  const nodeName = target.nodeName;
612
1320
  if (nodeName === 'TABLE' || nodeName === 'TR') {
@@ -621,7 +1329,7 @@ class TableSelection {
621
1329
  if (!tableElement) {
622
1330
  throw new Error('Expected to find TableElement in DOM');
623
1331
  }
624
- this.grid = getTableGrid(tableElement);
1332
+ this.table = getTable(tableElement);
625
1333
  });
626
1334
  });
627
1335
  this.editor.update(() => {
@@ -629,7 +1337,7 @@ class TableSelection {
629
1337
  if (!tableElement) {
630
1338
  throw new Error('Expected to find TableElement in DOM');
631
1339
  }
632
- this.grid = getTableGrid(tableElement);
1340
+ this.table = getTable(tableElement);
633
1341
  observer.observe(tableElement, {
634
1342
  childList: true,
635
1343
  subtree: true
@@ -643,7 +1351,7 @@ class TableSelection {
643
1351
  this.anchorY = -1;
644
1352
  this.focusX = -1;
645
1353
  this.focusY = -1;
646
- this.gridSelection = null;
1354
+ this.tableSelection = null;
647
1355
  this.anchorCellNodeKey = null;
648
1356
  this.focusCellNodeKey = null;
649
1357
  this.anchorCell = null;
@@ -659,7 +1367,7 @@ class TableSelection {
659
1367
  if (!tableElement) {
660
1368
  throw new Error('Expected to find TableElement in DOM');
661
1369
  }
662
- const grid = getTableGrid(tableElement);
1370
+ const grid = getTable(tableElement);
663
1371
  $updateDOMForSelection(editor, grid, null);
664
1372
  lexical.$setSelection(null);
665
1373
  editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
@@ -688,18 +1396,18 @@ class TableSelection {
688
1396
  this.hasHijackedSelectionStyles = true;
689
1397
  });
690
1398
  }
691
- updateTableGridSelection(selection) {
692
- if (selection != null && selection.gridKey === this.tableNodeKey) {
1399
+ updateTableTableSelection(selection) {
1400
+ if (selection !== null && selection.tableKey === this.tableNodeKey) {
693
1401
  const editor = this.editor;
694
- this.gridSelection = selection;
1402
+ this.tableSelection = selection;
695
1403
  this.isHighlightingCells = true;
696
1404
  this.disableHighlightStyle();
697
- $updateDOMForSelection(editor, this.grid, this.gridSelection);
1405
+ $updateDOMForSelection(editor, this.table, this.tableSelection);
698
1406
  } else if (selection == null) {
699
1407
  this.clearHighlight();
700
1408
  } else {
701
- this.tableNodeKey = selection.gridKey;
702
- this.updateTableGridSelection(selection);
1409
+ this.tableNodeKey = selection.tableKey;
1410
+ this.updateTableTableSelection(selection);
703
1411
  }
704
1412
  }
705
1413
  setFocusCellForSelection(cell, ignoreStart = false) {
@@ -733,14 +1441,14 @@ class TableSelection {
733
1441
  this.focusY = cellY;
734
1442
  if (this.isHighlightingCells) {
735
1443
  const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
736
- if (this.gridSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
1444
+ if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
737
1445
  const focusNodeKey = focusTableCellNode.getKey();
738
- this.gridSelection = this.gridSelection.clone() || $createGridSelection();
1446
+ this.tableSelection = this.tableSelection.clone() || $createTableSelection();
739
1447
  this.focusCellNodeKey = focusNodeKey;
740
- this.gridSelection.set(this.tableNodeKey, this.anchorCellNodeKey, this.focusCellNodeKey);
741
- lexical.$setSelection(this.gridSelection);
1448
+ this.tableSelection.set(this.tableNodeKey, this.anchorCellNodeKey, this.focusCellNodeKey);
1449
+ lexical.$setSelection(this.tableSelection);
742
1450
  editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
743
- $updateDOMForSelection(editor, this.grid, this.gridSelection);
1451
+ $updateDOMForSelection(editor, this.table, this.tableSelection);
744
1452
  }
745
1453
  }
746
1454
  });
@@ -754,7 +1462,7 @@ class TableSelection {
754
1462
  const anchorTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
755
1463
  if ($isTableCellNode(anchorTableCellNode)) {
756
1464
  const anchorNodeKey = anchorTableCellNode.getKey();
757
- this.gridSelection = this.gridSelection != null ? this.gridSelection.clone() : $createGridSelection();
1465
+ this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
758
1466
  this.anchorCellNodeKey = anchorNodeKey;
759
1467
  }
760
1468
  });
@@ -762,7 +1470,7 @@ class TableSelection {
762
1470
  formatCells(type) {
763
1471
  this.editor.update(() => {
764
1472
  const selection = lexical.$getSelection();
765
- if (!$isGridSelection(selection)) {
1473
+ if (!$isTableSelection(selection)) {
766
1474
  {
767
1475
  throw Error(`Expected grid selection`);
768
1476
  }
@@ -789,13 +1497,13 @@ class TableSelection {
789
1497
  throw new Error('Expected TableNode.');
790
1498
  }
791
1499
  const selection = lexical.$getSelection();
792
- if (!$isGridSelection(selection)) {
1500
+ if (!$isTableSelection(selection)) {
793
1501
  {
794
1502
  throw Error(`Expected grid selection`);
795
1503
  }
796
1504
  }
797
1505
  const selectedNodes = selection.getNodes().filter($isTableCellNode);
798
- if (selectedNodes.length === this.grid.columns * this.grid.rows) {
1506
+ if (selectedNodes.length === this.table.columns * this.table.rows) {
799
1507
  tableNode.selectPrevious();
800
1508
  // Delete entire table
801
1509
  tableNode.remove();
@@ -816,7 +1524,7 @@ class TableSelection {
816
1524
  });
817
1525
  }
818
1526
  });
819
- $updateDOMForSelection(editor, this.grid, null);
1527
+ $updateDOMForSelection(editor, this.table, null);
820
1528
  lexical.$setSelection(null);
821
1529
  editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
822
1530
  });
@@ -836,9 +1544,9 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
836
1544
  if (rootElement === null) {
837
1545
  throw new Error('No root element.');
838
1546
  }
839
- const tableSelection = new TableSelection(editor, tableNode.getKey());
1547
+ const tableObserver = new TableObserver(editor, tableNode.getKey());
840
1548
  const editorWindow = editor._window || window;
841
- attachTableSelectionToTableElement(tableElement, tableSelection);
1549
+ attachTableObserverToTableElement(tableElement, tableObserver);
842
1550
  tableElement.addEventListener('mousedown', event => {
843
1551
  setTimeout(() => {
844
1552
  if (event.button !== 0) {
@@ -847,20 +1555,20 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
847
1555
  if (!editorWindow) {
848
1556
  return;
849
1557
  }
850
- const anchorCell = getCellFromTarget(event.target);
1558
+ const anchorCell = getDOMCellFromTarget(event.target);
851
1559
  if (anchorCell !== null) {
852
1560
  stopEvent(event);
853
- tableSelection.setAnchorCellForSelection(anchorCell);
1561
+ tableObserver.setAnchorCellForSelection(anchorCell);
854
1562
  }
855
1563
  const onMouseUp = () => {
856
1564
  editorWindow.removeEventListener('mouseup', onMouseUp);
857
1565
  editorWindow.removeEventListener('mousemove', onMouseMove);
858
1566
  };
859
1567
  const onMouseMove = moveEvent => {
860
- const focusCell = getCellFromTarget(moveEvent.target);
861
- if (focusCell !== null && (tableSelection.anchorX !== focusCell.x || tableSelection.anchorY !== focusCell.y)) {
1568
+ const focusCell = getDOMCellFromTarget(moveEvent.target);
1569
+ if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
862
1570
  moveEvent.preventDefault();
863
- tableSelection.setFocusCellForSelection(focusCell);
1571
+ tableObserver.setFocusCellForSelection(focusCell);
864
1572
  }
865
1573
  };
866
1574
  editorWindow.addEventListener('mouseup', onMouseUp);
@@ -873,1346 +1581,826 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
873
1581
  if (event.button !== 0) {
874
1582
  return;
875
1583
  }
876
- editor.update(() => {
877
- const selection = lexical.$getSelection();
878
- const target = event.target;
879
- if ($isGridSelection(selection) && selection.gridKey === tableSelection.tableNodeKey && rootElement.contains(target)) {
880
- tableSelection.clearHighlight();
881
- }
882
- });
883
- };
884
- editorWindow.addEventListener('mousedown', mouseDownCallback);
885
- tableSelection.listenersToRemove.add(() => editorWindow.removeEventListener('mousedown', mouseDownCallback));
886
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => $handleArrowKey(editor, event, 'down', tableNode, tableSelection), lexical.COMMAND_PRIORITY_HIGH));
887
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => $handleArrowKey(editor, event, 'up', tableNode, tableSelection), lexical.COMMAND_PRIORITY_HIGH));
888
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, event => $handleArrowKey(editor, event, 'backward', tableNode, tableSelection), lexical.COMMAND_PRIORITY_HIGH));
889
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_RIGHT_COMMAND, event => $handleArrowKey(editor, event, 'forward', tableNode, tableSelection), lexical.COMMAND_PRIORITY_HIGH));
890
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, event => {
891
- const selection = lexical.$getSelection();
892
- if ($isGridSelection(selection)) {
893
- const focusCellNode = utils.$findMatchingParent(selection.focus.getNode(), $isTableCellNode);
894
- if ($isTableCellNode(focusCellNode)) {
895
- stopEvent(event);
896
- focusCellNode.selectEnd();
897
- return true;
898
- }
899
- }
900
- return false;
901
- }, lexical.COMMAND_PRIORITY_HIGH));
902
- const deleteTextHandler = command => () => {
903
- const selection = lexical.$getSelection();
904
- if (!$isSelectionInTable(selection, tableNode)) {
905
- return false;
906
- }
907
- if ($isGridSelection(selection)) {
908
- tableSelection.clearText();
909
- return true;
910
- } else if (lexical.$isRangeSelection(selection)) {
911
- const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
912
- if (!$isTableCellNode(tableCellNode)) {
913
- return false;
914
- }
915
- const anchorNode = selection.anchor.getNode();
916
- const focusNode = selection.focus.getNode();
917
- const isAnchorInside = tableNode.isParentOf(anchorNode);
918
- const isFocusInside = tableNode.isParentOf(focusNode);
919
- const selectionContainsPartialTable = isAnchorInside && !isFocusInside || isFocusInside && !isAnchorInside;
920
- if (selectionContainsPartialTable) {
921
- tableSelection.clearText();
922
- return true;
923
- }
924
- const nearestElementNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
925
- const topLevelCellElementNode = nearestElementNode && utils.$findMatchingParent(nearestElementNode, n => lexical.$isElementNode(n) && $isTableCellNode(n.getParent()));
926
- if (!lexical.$isElementNode(topLevelCellElementNode) || !lexical.$isElementNode(nearestElementNode)) {
927
- return false;
928
- }
929
- if (command === lexical.DELETE_LINE_COMMAND && topLevelCellElementNode.getPreviousSibling() === null) {
930
- // TODO: Fix Delete Line in Table Cells.
931
- return true;
932
- }
933
- if (command === lexical.DELETE_CHARACTER_COMMAND || command === lexical.DELETE_WORD_COMMAND) {
934
- if (selection.isCollapsed() && selection.anchor.offset === 0) {
935
- if (nearestElementNode !== topLevelCellElementNode) {
936
- const children = nearestElementNode.getChildren();
937
- const newParagraphNode = lexical.$createParagraphNode();
938
- children.forEach(child => newParagraphNode.append(child));
939
- nearestElementNode.replace(newParagraphNode);
940
- nearestElementNode.getWritable().__parent = tableCellNode.getKey();
941
- return true;
942
- }
943
- }
944
- }
945
- }
946
- return false;
947
- };
948
- [lexical.DELETE_WORD_COMMAND, lexical.DELETE_LINE_COMMAND, lexical.DELETE_CHARACTER_COMMAND].forEach(command => {
949
- tableSelection.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), lexical.COMMAND_PRIORITY_CRITICAL));
950
- });
951
- const deleteCellHandler = event => {
952
- const selection = lexical.$getSelection();
953
- if (!$isSelectionInTable(selection, tableNode)) {
954
- return false;
955
- }
956
- if ($isGridSelection(selection)) {
957
- event.preventDefault();
958
- event.stopPropagation();
959
- tableSelection.clearText();
960
- return true;
961
- } else if (lexical.$isRangeSelection(selection)) {
962
- const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
963
- if (!$isTableCellNode(tableCellNode)) {
964
- return false;
1584
+ editor.update(() => {
1585
+ const selection = lexical.$getSelection();
1586
+ const target = event.target;
1587
+ if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey && rootElement.contains(target)) {
1588
+ tableObserver.clearHighlight();
965
1589
  }
966
- }
967
- return false;
1590
+ });
968
1591
  };
969
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
970
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_DELETE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
971
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.FORMAT_TEXT_COMMAND, payload => {
1592
+ editorWindow.addEventListener('mousedown', mouseDownCallback);
1593
+ tableObserver.listenersToRemove.add(() => editorWindow.removeEventListener('mousedown', mouseDownCallback));
1594
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => $handleArrowKey(editor, event, 'down', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1595
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => $handleArrowKey(editor, event, 'up', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1596
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, event => $handleArrowKey(editor, event, 'backward', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1597
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_RIGHT_COMMAND, event => $handleArrowKey(editor, event, 'forward', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1598
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, event => {
972
1599
  const selection = lexical.$getSelection();
973
- if (!$isSelectionInTable(selection, tableNode)) {
974
- return false;
975
- }
976
- if ($isGridSelection(selection)) {
977
- tableSelection.formatCells(payload);
978
- return true;
979
- } else if (lexical.$isRangeSelection(selection)) {
980
- const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
981
- if (!$isTableCellNode(tableCellNode)) {
982
- return false;
1600
+ if ($isTableSelection(selection)) {
1601
+ const focusCellNode = utils.$findMatchingParent(selection.focus.getNode(), $isTableCellNode);
1602
+ if ($isTableCellNode(focusCellNode)) {
1603
+ stopEvent(event);
1604
+ focusCellNode.selectEnd();
1605
+ return true;
983
1606
  }
984
1607
  }
985
1608
  return false;
986
- }, lexical.COMMAND_PRIORITY_CRITICAL));
987
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.CONTROLLED_TEXT_INSERTION_COMMAND, payload => {
1609
+ }, lexical.COMMAND_PRIORITY_HIGH));
1610
+ const deleteTextHandler = command => () => {
988
1611
  const selection = lexical.$getSelection();
989
1612
  if (!$isSelectionInTable(selection, tableNode)) {
990
1613
  return false;
991
1614
  }
992
- if ($isGridSelection(selection)) {
993
- tableSelection.clearHighlight();
994
- return false;
1615
+ if ($isTableSelection(selection)) {
1616
+ tableObserver.clearText();
1617
+ return true;
995
1618
  } else if (lexical.$isRangeSelection(selection)) {
996
1619
  const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
997
1620
  if (!$isTableCellNode(tableCellNode)) {
998
1621
  return false;
999
1622
  }
1000
- }
1001
- return false;
1002
- }, lexical.COMMAND_PRIORITY_CRITICAL));
1003
- if (hasTabHandler) {
1004
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_TAB_COMMAND, event => {
1005
- const selection = lexical.$getSelection();
1006
- if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
1007
- return false;
1008
- }
1009
- const tableCellNode = $findCellNode(selection.anchor.getNode());
1010
- if (tableCellNode === null) {
1011
- return false;
1012
- }
1013
- stopEvent(event);
1014
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
1015
- selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, !event.shiftKey ? 'forward' : 'backward');
1016
- return true;
1017
- }, lexical.COMMAND_PRIORITY_CRITICAL));
1018
- }
1019
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.FOCUS_COMMAND, payload => {
1020
- return tableNode.isSelected();
1021
- }, lexical.COMMAND_PRIORITY_HIGH));
1022
- function getCellFromCellNode(tableCellNode) {
1023
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
1024
- return tableNode.getCellFromCordsOrThrow(currentCords.x, currentCords.y, tableSelection.grid);
1025
- }
1026
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, selectionPayload => {
1027
- const {
1028
- nodes,
1029
- selection
1030
- } = selectionPayload;
1031
- const isPointSelection = lexical.$INTERNAL_isPointSelection(selection);
1032
- const isRangeSelection = lexical.$isRangeSelection(selection);
1033
- const isSelectionInsideOfGrid = isRangeSelection && utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n)) !== null && utils.$findMatchingParent(selection.focus.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n)) !== null || isPointSelection && !isRangeSelection;
1034
- if (nodes.length !== 1 || !lexical.DEPRECATED_$isGridNode(nodes[0]) || !isSelectionInsideOfGrid) {
1035
- return false;
1036
- }
1037
- const {
1038
- anchor
1039
- } = selection;
1040
- const newGrid = nodes[0];
1041
- const newGridRows = newGrid.getChildren();
1042
- const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
1043
- const newRowCount = newGrid.getChildrenSize();
1044
- const gridCellNode = utils.$findMatchingParent(anchor.getNode(), n => lexical.DEPRECATED_$isGridCellNode(n));
1045
- const gridRowNode = gridCellNode && utils.$findMatchingParent(gridCellNode, n => lexical.DEPRECATED_$isGridRowNode(n));
1046
- const gridNode = gridRowNode && utils.$findMatchingParent(gridRowNode, n => lexical.DEPRECATED_$isGridNode(n));
1047
- if (!lexical.DEPRECATED_$isGridCellNode(gridCellNode) || !lexical.DEPRECATED_$isGridRowNode(gridRowNode) || !lexical.DEPRECATED_$isGridNode(gridNode)) {
1048
- return false;
1049
- }
1050
- const startY = gridRowNode.getIndexWithinParent();
1051
- const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
1052
- const startX = gridCellNode.getIndexWithinParent();
1053
- const stopX = Math.min(gridRowNode.getChildrenSize() - 1, startX + newColumnCount - 1);
1054
- const fromX = Math.min(startX, stopX);
1055
- const fromY = Math.min(startY, stopY);
1056
- const toX = Math.max(startX, stopX);
1057
- const toY = Math.max(startY, stopY);
1058
- const gridRowNodes = gridNode.getChildren();
1059
- let newRowIdx = 0;
1060
- let newAnchorCellKey;
1061
- let newFocusCellKey;
1062
- for (let r = fromY; r <= toY; r++) {
1063
- const currentGridRowNode = gridRowNodes[r];
1064
- if (!lexical.DEPRECATED_$isGridRowNode(currentGridRowNode)) {
1065
- return false;
1066
- }
1067
- const newGridRowNode = newGridRows[newRowIdx];
1068
- if (!lexical.DEPRECATED_$isGridRowNode(newGridRowNode)) {
1069
- return false;
1070
- }
1071
- const gridCellNodes = currentGridRowNode.getChildren();
1072
- const newGridCellNodes = newGridRowNode.getChildren();
1073
- let newColumnIdx = 0;
1074
- for (let c = fromX; c <= toX; c++) {
1075
- const currentGridCellNode = gridCellNodes[c];
1076
- if (!lexical.DEPRECATED_$isGridCellNode(currentGridCellNode)) {
1077
- return false;
1078
- }
1079
- const newGridCellNode = newGridCellNodes[newColumnIdx];
1080
- if (!lexical.DEPRECATED_$isGridCellNode(newGridCellNode)) {
1081
- return false;
1082
- }
1083
- if (r === fromY && c === fromX) {
1084
- newAnchorCellKey = currentGridCellNode.getKey();
1085
- } else if (r === toY && c === toX) {
1086
- newFocusCellKey = currentGridCellNode.getKey();
1087
- }
1088
- const originalChildren = currentGridCellNode.getChildren();
1089
- newGridCellNode.getChildren().forEach(child => {
1090
- if (lexical.$isTextNode(child)) {
1091
- const paragraphNode = lexical.$createParagraphNode();
1092
- paragraphNode.append(child);
1093
- currentGridCellNode.append(child);
1094
- } else {
1095
- currentGridCellNode.append(child);
1096
- }
1097
- });
1098
- originalChildren.forEach(n => n.remove());
1099
- newColumnIdx++;
1100
- }
1101
- newRowIdx++;
1102
- }
1103
- if (newAnchorCellKey && newFocusCellKey) {
1104
- const newGridSelection = $createGridSelection();
1105
- newGridSelection.set(nodes[0].getKey(), newAnchorCellKey, newFocusCellKey);
1106
- lexical.$setSelection(newGridSelection);
1107
- }
1108
- return true;
1109
- }, lexical.COMMAND_PRIORITY_CRITICAL));
1110
- tableSelection.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_CHANGE_COMMAND, () => {
1111
- const selection = lexical.$getSelection();
1112
- const prevSelection = lexical.$getPreviousSelection();
1113
- if (lexical.$isRangeSelection(selection)) {
1114
- const {
1115
- anchor,
1116
- focus
1117
- } = selection;
1118
- const anchorNode = anchor.getNode();
1119
- const focusNode = focus.getNode();
1120
- // Using explicit comparison with table node to ensure it's not a nested table
1121
- // as in that case we'll leave selection resolving to that table
1122
- const anchorCellNode = $findCellNode(anchorNode);
1123
- const focusCellNode = $findCellNode(focusNode);
1124
- const isAnchorInside = anchorCellNode && tableNode.is($findTableNode(anchorCellNode));
1125
- const isFocusInside = focusCellNode && tableNode.is($findTableNode(focusCellNode));
1126
- const isPartialyWithinTable = isAnchorInside !== isFocusInside;
1127
- const isWithinTable = isAnchorInside && isFocusInside;
1128
- const isBackward = selection.isBackward();
1129
- if (isPartialyWithinTable) {
1130
- const newSelection = selection.clone();
1131
- newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
1132
- lexical.$setSelection(newSelection);
1133
- $addHighlightStyleToTable(editor, tableSelection);
1134
- } else if (isWithinTable) {
1135
- // Handle case when selection spans across multiple cells but still
1136
- // has range selection, then we convert it into grid selection
1137
- if (!anchorCellNode.is(focusCellNode)) {
1138
- tableSelection.setAnchorCellForSelection(getCellFromCellNode(anchorCellNode));
1139
- tableSelection.setFocusCellForSelection(getCellFromCellNode(focusCellNode), true);
1140
- }
1141
- }
1142
- }
1143
- if (selection && !selection.is(prevSelection) && ($isGridSelection(selection) || $isGridSelection(prevSelection)) && tableSelection.gridSelection && !tableSelection.gridSelection.is(prevSelection)) {
1144
- if ($isGridSelection(selection) && selection.gridKey === tableSelection.tableNodeKey) {
1145
- tableSelection.updateTableGridSelection(selection);
1146
- } else if (!$isGridSelection(selection) && $isGridSelection(prevSelection) && prevSelection.gridKey === tableSelection.tableNodeKey) {
1147
- tableSelection.updateTableGridSelection(null);
1148
- }
1149
- return false;
1150
- }
1151
- if (tableSelection.hasHijackedSelectionStyles && !tableNode.isSelected()) {
1152
- $removeHighlightStyleToTable(editor, tableSelection);
1153
- } else if (!tableSelection.hasHijackedSelectionStyles && tableNode.isSelected()) {
1154
- $addHighlightStyleToTable(editor, tableSelection);
1155
- }
1156
- return false;
1157
- }, lexical.COMMAND_PRIORITY_CRITICAL));
1158
- return tableSelection;
1159
- }
1160
- function attachTableSelectionToTableElement(tableElement, tableSelection) {
1161
- tableElement[LEXICAL_ELEMENT_KEY] = tableSelection;
1162
- }
1163
- function getTableSelectionFromTableElement(tableElement) {
1164
- return tableElement[LEXICAL_ELEMENT_KEY];
1165
- }
1166
- function getCellFromTarget(node) {
1167
- let currentNode = node;
1168
- while (currentNode != null) {
1169
- const nodeName = currentNode.nodeName;
1170
- if (nodeName === 'TD' || nodeName === 'TH') {
1171
- // @ts-expect-error: internal field
1172
- const cell = currentNode._cell;
1173
- if (cell === undefined) {
1174
- return null;
1175
- }
1176
- return cell;
1177
- }
1178
- currentNode = currentNode.parentNode;
1179
- }
1180
- return null;
1181
- }
1182
- function getTableGrid(tableElement) {
1183
- const cells = [];
1184
- const grid = {
1185
- cells,
1186
- columns: 0,
1187
- rows: 0
1188
- };
1189
- let currentNode = tableElement.firstChild;
1190
- let x = 0;
1191
- let y = 0;
1192
- cells.length = 0;
1193
- while (currentNode != null) {
1194
- const nodeMame = currentNode.nodeName;
1195
- if (nodeMame === 'TD' || nodeMame === 'TH') {
1196
- const elem = currentNode;
1197
- const cell = {
1198
- elem,
1199
- hasBackgroundColor: elem.style.backgroundColor !== '',
1200
- highlighted: false,
1201
- x,
1202
- y
1203
- };
1204
-
1205
- // @ts-expect-error: internal field
1206
- currentNode._cell = cell;
1207
- let row = cells[y];
1208
- if (row === undefined) {
1209
- row = cells[y] = [];
1210
- }
1211
- row[x] = cell;
1212
- } else {
1213
- const child = currentNode.firstChild;
1214
- if (child != null) {
1215
- currentNode = child;
1216
- continue;
1217
- }
1218
- }
1219
- const sibling = currentNode.nextSibling;
1220
- if (sibling != null) {
1221
- x++;
1222
- currentNode = sibling;
1223
- continue;
1224
- }
1225
- const parent = currentNode.parentNode;
1226
- if (parent != null) {
1227
- const parentSibling = parent.nextSibling;
1228
- if (parentSibling == null) {
1229
- break;
1230
- }
1231
- y++;
1232
- x = 0;
1233
- currentNode = parentSibling;
1234
- }
1235
- }
1236
- grid.columns = x + 1;
1237
- grid.rows = y + 1;
1238
- return grid;
1239
- }
1240
- function $updateDOMForSelection(editor, grid, selection) {
1241
- const selectedCellNodes = new Set(selection ? selection.getNodes() : []);
1242
- $forEachGridCell(grid, (cell, lexicalNode) => {
1243
- const elem = cell.elem;
1244
- if (selectedCellNodes.has(lexicalNode)) {
1245
- cell.highlighted = true;
1246
- $addHighlightToDOM(editor, cell);
1247
- } else {
1248
- cell.highlighted = false;
1249
- $removeHighlightFromDOM(editor, cell);
1250
- if (!elem.getAttribute('style')) {
1251
- elem.removeAttribute('style');
1623
+ const anchorNode = selection.anchor.getNode();
1624
+ const focusNode = selection.focus.getNode();
1625
+ const isAnchorInside = tableNode.isParentOf(anchorNode);
1626
+ const isFocusInside = tableNode.isParentOf(focusNode);
1627
+ const selectionContainsPartialTable = isAnchorInside && !isFocusInside || isFocusInside && !isAnchorInside;
1628
+ if (selectionContainsPartialTable) {
1629
+ tableObserver.clearText();
1630
+ return true;
1252
1631
  }
1253
- }
1254
- });
1255
- }
1256
- function $forEachGridCell(grid, cb) {
1257
- const {
1258
- cells
1259
- } = grid;
1260
- for (let y = 0; y < cells.length; y++) {
1261
- const row = cells[y];
1262
- if (!row) {
1263
- continue;
1264
- }
1265
- for (let x = 0; x < row.length; x++) {
1266
- const cell = row[x];
1267
- if (!cell) {
1268
- continue;
1632
+ const nearestElementNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
1633
+ const topLevelCellElementNode = nearestElementNode && utils.$findMatchingParent(nearestElementNode, n => lexical.$isElementNode(n) && $isTableCellNode(n.getParent()));
1634
+ if (!lexical.$isElementNode(topLevelCellElementNode) || !lexical.$isElementNode(nearestElementNode)) {
1635
+ return false;
1269
1636
  }
1270
- const lexicalNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1271
- if (lexicalNode !== null) {
1272
- cb(cell, lexicalNode, {
1273
- x,
1274
- y
1275
- });
1637
+ if (command === lexical.DELETE_LINE_COMMAND && topLevelCellElementNode.getPreviousSibling() === null) {
1638
+ // TODO: Fix Delete Line in Table Cells.
1639
+ return true;
1640
+ }
1641
+ if (command === lexical.DELETE_CHARACTER_COMMAND || command === lexical.DELETE_WORD_COMMAND) {
1642
+ if (selection.isCollapsed() && selection.anchor.offset === 0) {
1643
+ if (nearestElementNode !== topLevelCellElementNode) {
1644
+ const children = nearestElementNode.getChildren();
1645
+ const newParagraphNode = lexical.$createParagraphNode();
1646
+ children.forEach(child => newParagraphNode.append(child));
1647
+ nearestElementNode.replace(newParagraphNode);
1648
+ nearestElementNode.getWritable().__parent = tableCellNode.getKey();
1649
+ return true;
1650
+ }
1651
+ }
1276
1652
  }
1277
1653
  }
1278
- }
1279
- }
1280
- function $addHighlightStyleToTable(editor, tableSelection) {
1281
- tableSelection.disableHighlightStyle();
1282
- $forEachGridCell(tableSelection.grid, cell => {
1283
- cell.highlighted = true;
1284
- $addHighlightToDOM(editor, cell);
1654
+ return false;
1655
+ };
1656
+ [lexical.DELETE_WORD_COMMAND, lexical.DELETE_LINE_COMMAND, lexical.DELETE_CHARACTER_COMMAND].forEach(command => {
1657
+ tableObserver.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), lexical.COMMAND_PRIORITY_CRITICAL));
1285
1658
  });
1286
- }
1287
- function $removeHighlightStyleToTable(editor, tableSelection) {
1288
- tableSelection.enableHighlightStyle();
1289
- $forEachGridCell(tableSelection.grid, cell => {
1290
- const elem = cell.elem;
1291
- cell.highlighted = false;
1292
- $removeHighlightFromDOM(editor, cell);
1293
- if (!elem.getAttribute('style')) {
1294
- elem.removeAttribute('style');
1659
+ const deleteCellHandler = event => {
1660
+ const selection = lexical.$getSelection();
1661
+ if (!$isSelectionInTable(selection, tableNode)) {
1662
+ return false;
1295
1663
  }
1296
- });
1297
- }
1298
- const selectGridNodeInDirection = (tableSelection, tableNode, x, y, direction) => {
1299
- const isForward = direction === 'forward';
1300
- switch (direction) {
1301
- case 'backward':
1302
- case 'forward':
1303
- if (x !== (isForward ? tableSelection.grid.columns - 1 : 0)) {
1304
- selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableSelection.grid), isForward);
1305
- } else {
1306
- if (y !== (isForward ? tableSelection.grid.rows - 1 : 0)) {
1307
- selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : tableSelection.grid.columns - 1, y + (isForward ? 1 : -1), tableSelection.grid), isForward);
1308
- } else if (!isForward) {
1309
- tableNode.selectPrevious();
1310
- } else {
1311
- tableNode.selectNext();
1312
- }
1313
- }
1664
+ if ($isTableSelection(selection)) {
1665
+ event.preventDefault();
1666
+ event.stopPropagation();
1667
+ tableObserver.clearText();
1314
1668
  return true;
1315
- case 'up':
1316
- if (y !== 0) {
1317
- selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableSelection.grid), false);
1318
- } else {
1319
- tableNode.selectPrevious();
1669
+ } else if (lexical.$isRangeSelection(selection)) {
1670
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
1671
+ if (!$isTableCellNode(tableCellNode)) {
1672
+ return false;
1320
1673
  }
1674
+ }
1675
+ return false;
1676
+ };
1677
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1678
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_DELETE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1679
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.FORMAT_TEXT_COMMAND, payload => {
1680
+ const selection = lexical.$getSelection();
1681
+ if (!$isSelectionInTable(selection, tableNode)) {
1682
+ return false;
1683
+ }
1684
+ if ($isTableSelection(selection)) {
1685
+ tableObserver.formatCells(payload);
1321
1686
  return true;
1322
- case 'down':
1323
- if (y !== tableSelection.grid.rows - 1) {
1324
- selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableSelection.grid), true);
1325
- } else {
1326
- tableNode.selectNext();
1687
+ } else if (lexical.$isRangeSelection(selection)) {
1688
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
1689
+ if (!$isTableCellNode(tableCellNode)) {
1690
+ return false;
1327
1691
  }
1328
- return true;
1329
- default:
1692
+ }
1693
+ return false;
1694
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1695
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.CONTROLLED_TEXT_INSERTION_COMMAND, payload => {
1696
+ const selection = lexical.$getSelection();
1697
+ if (!$isSelectionInTable(selection, tableNode)) {
1330
1698
  return false;
1331
- }
1332
- };
1333
- const adjustFocusNodeInDirection = (tableSelection, tableNode, x, y, direction) => {
1334
- const isForward = direction === 'forward';
1335
- switch (direction) {
1336
- case 'backward':
1337
- case 'forward':
1338
- if (x !== (isForward ? tableSelection.grid.columns - 1 : 0)) {
1339
- tableSelection.setFocusCellForSelection(tableNode.getCellFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableSelection.grid));
1699
+ }
1700
+ if ($isTableSelection(selection)) {
1701
+ tableObserver.clearHighlight();
1702
+ return false;
1703
+ } else if (lexical.$isRangeSelection(selection)) {
1704
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
1705
+ if (!$isTableCellNode(tableCellNode)) {
1706
+ return false;
1340
1707
  }
1341
- return true;
1342
- case 'up':
1343
- if (y !== 0) {
1344
- tableSelection.setFocusCellForSelection(tableNode.getCellFromCordsOrThrow(x, y - 1, tableSelection.grid));
1345
- return true;
1346
- } else {
1708
+ }
1709
+ return false;
1710
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1711
+ if (hasTabHandler) {
1712
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_TAB_COMMAND, event => {
1713
+ const selection = lexical.$getSelection();
1714
+ if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
1347
1715
  return false;
1348
1716
  }
1349
- case 'down':
1350
- if (y !== tableSelection.grid.rows - 1) {
1351
- tableSelection.setFocusCellForSelection(tableNode.getCellFromCordsOrThrow(x, y + 1, tableSelection.grid));
1352
- return true;
1353
- } else {
1717
+ const tableCellNode = $findCellNode(selection.anchor.getNode());
1718
+ if (tableCellNode === null) {
1354
1719
  return false;
1355
1720
  }
1356
- default:
1357
- return false;
1358
- }
1359
- };
1360
- function $isSelectionInTable(selection, tableNode) {
1361
- if (lexical.$isRangeSelection(selection) || $isGridSelection(selection)) {
1362
- const isAnchorInside = tableNode.isParentOf(selection.anchor.getNode());
1363
- const isFocusInside = tableNode.isParentOf(selection.focus.getNode());
1364
- return isAnchorInside && isFocusInside;
1365
- }
1366
- return false;
1367
- }
1368
- function selectTableCellNode(tableCell, fromStart) {
1369
- if (fromStart) {
1370
- tableCell.selectStart();
1371
- } else {
1372
- tableCell.selectEnd();
1373
- }
1374
- }
1375
- const BROWSER_BLUE_RGB = '172,206,247';
1376
- function $addHighlightToDOM(editor, cell) {
1377
- const element = cell.elem;
1378
- const node = lexical.$getNearestNodeFromDOMNode(element);
1379
- if (!$isTableCellNode(node)) {
1380
- throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
1381
- }
1382
- const backgroundColor = node.getBackgroundColor();
1383
- if (backgroundColor === null) {
1384
- element.style.setProperty('background-color', `rgb(${BROWSER_BLUE_RGB})`);
1385
- } else {
1386
- element.style.setProperty('background-image', `linear-gradient(to right, rgba(${BROWSER_BLUE_RGB},0.85), rgba(${BROWSER_BLUE_RGB},0.85))`);
1387
- }
1388
- element.style.setProperty('caret-color', 'transparent');
1389
- }
1390
- function $removeHighlightFromDOM(editor, cell) {
1391
- const element = cell.elem;
1392
- const node = lexical.$getNearestNodeFromDOMNode(element);
1393
- if (!$isTableCellNode(node)) {
1394
- throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
1395
- }
1396
- const backgroundColor = node.getBackgroundColor();
1397
- if (backgroundColor === null) {
1398
- element.style.removeProperty('background-color');
1721
+ stopEvent(event);
1722
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
1723
+ selectTableNodeInDirection(tableObserver, tableNode, currentCords.x, currentCords.y, !event.shiftKey ? 'forward' : 'backward');
1724
+ return true;
1725
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1399
1726
  }
1400
- element.style.removeProperty('background-image');
1401
- element.style.removeProperty('caret-color');
1402
- }
1403
- function $findCellNode(node) {
1404
- const cellNode = utils.$findMatchingParent(node, $isTableCellNode);
1405
- return $isTableCellNode(cellNode) ? cellNode : null;
1406
- }
1407
- function $findTableNode(node) {
1408
- const tableNode = utils.$findMatchingParent(node, $isTableNode);
1409
- return $isTableNode(tableNode) ? tableNode : null;
1410
- }
1411
- function $handleArrowKey(editor, event, direction, tableNode, tableSelection) {
1412
- const selection = lexical.$getSelection();
1413
- if (!$isSelectionInTable(selection, tableNode)) {
1414
- return false;
1727
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.FOCUS_COMMAND, payload => {
1728
+ return tableNode.isSelected();
1729
+ }, lexical.COMMAND_PRIORITY_HIGH));
1730
+ function getObserverCellFromCellNode(tableCellNode) {
1731
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
1732
+ return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
1415
1733
  }
1416
- if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
1417
- // Horizontal move between cels seem to work well without interruption
1418
- // so just exit early, and handle vertical moves
1419
- if (direction === 'backward' || direction === 'forward') {
1420
- return false;
1421
- }
1734
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, selectionPayload => {
1422
1735
  const {
1423
- anchor,
1424
- focus
1425
- } = selection;
1426
- const anchorCellNode = utils.$findMatchingParent(anchor.getNode(), $isTableCellNode);
1427
- const focusCellNode = utils.$findMatchingParent(focus.getNode(), $isTableCellNode);
1428
- if (!$isTableCellNode(anchorCellNode) || !anchorCellNode.is(focusCellNode)) {
1736
+ nodes,
1737
+ selection
1738
+ } = selectionPayload;
1739
+ const anchorAndFocus = selection.getStartEndPoints();
1740
+ const isTableSelection = $isTableSelection(selection);
1741
+ const isRangeSelection = lexical.$isRangeSelection(selection);
1742
+ const isSelectionInsideOfGrid = isRangeSelection && utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n)) !== null && utils.$findMatchingParent(selection.focus.getNode(), n => $isTableCellNode(n)) !== null || isTableSelection;
1743
+ if (nodes.length !== 1 || !$isTableNode(nodes[0]) || !isSelectionInsideOfGrid || anchorAndFocus === null) {
1429
1744
  return false;
1430
1745
  }
1431
- const anchorCellTable = $findTableNode(anchorCellNode);
1432
- if (anchorCellTable !== tableNode && anchorCellTable != null) {
1433
- const anchorCellTableElement = editor.getElementByKey(anchorCellTable.getKey());
1434
- if (anchorCellTableElement != null) {
1435
- tableSelection.grid = getTableGrid(anchorCellTableElement);
1436
- return $handleArrowKey(editor, event, direction, anchorCellTable, tableSelection);
1437
- }
1438
- }
1439
- const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
1440
- const anchorDOM = editor.getElementByKey(anchor.key);
1441
- if (anchorDOM == null || anchorCellDom == null) {
1746
+ const [anchor] = anchorAndFocus;
1747
+ const newGrid = nodes[0];
1748
+ const newGridRows = newGrid.getChildren();
1749
+ const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
1750
+ const newRowCount = newGrid.getChildrenSize();
1751
+ const gridCellNode = utils.$findMatchingParent(anchor.getNode(), n => $isTableCellNode(n));
1752
+ const gridRowNode = gridCellNode && utils.$findMatchingParent(gridCellNode, n => $isTableRowNode(n));
1753
+ const gridNode = gridRowNode && utils.$findMatchingParent(gridRowNode, n => $isTableNode(n));
1754
+ if (!$isTableCellNode(gridCellNode) || !$isTableRowNode(gridRowNode) || !$isTableNode(gridNode)) {
1442
1755
  return false;
1443
1756
  }
1444
- let edgeSelectionRect;
1445
- if (anchor.type === 'element') {
1446
- edgeSelectionRect = anchorDOM.getBoundingClientRect();
1447
- } else {
1448
- const domSelection = window.getSelection();
1449
- if (domSelection === null || domSelection.rangeCount === 0) {
1757
+ const startY = gridRowNode.getIndexWithinParent();
1758
+ const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
1759
+ const startX = gridCellNode.getIndexWithinParent();
1760
+ const stopX = Math.min(gridRowNode.getChildrenSize() - 1, startX + newColumnCount - 1);
1761
+ const fromX = Math.min(startX, stopX);
1762
+ const fromY = Math.min(startY, stopY);
1763
+ const toX = Math.max(startX, stopX);
1764
+ const toY = Math.max(startY, stopY);
1765
+ const gridRowNodes = gridNode.getChildren();
1766
+ let newRowIdx = 0;
1767
+ let newAnchorCellKey;
1768
+ let newFocusCellKey;
1769
+ for (let r = fromY; r <= toY; r++) {
1770
+ const currentGridRowNode = gridRowNodes[r];
1771
+ if (!$isTableRowNode(currentGridRowNode)) {
1450
1772
  return false;
1451
1773
  }
1452
- const range = domSelection.getRangeAt(0);
1453
- edgeSelectionRect = range.getBoundingClientRect();
1454
- }
1455
- const edgeChild = direction === 'up' ? anchorCellNode.getFirstChild() : anchorCellNode.getLastChild();
1456
- if (edgeChild == null) {
1457
- return false;
1774
+ const newGridRowNode = newGridRows[newRowIdx];
1775
+ if (!$isTableRowNode(newGridRowNode)) {
1776
+ return false;
1777
+ }
1778
+ const gridCellNodes = currentGridRowNode.getChildren();
1779
+ const newGridCellNodes = newGridRowNode.getChildren();
1780
+ let newColumnIdx = 0;
1781
+ for (let c = fromX; c <= toX; c++) {
1782
+ const currentGridCellNode = gridCellNodes[c];
1783
+ if (!$isTableCellNode(currentGridCellNode)) {
1784
+ return false;
1785
+ }
1786
+ const newGridCellNode = newGridCellNodes[newColumnIdx];
1787
+ if (!$isTableCellNode(newGridCellNode)) {
1788
+ return false;
1789
+ }
1790
+ if (r === fromY && c === fromX) {
1791
+ newAnchorCellKey = currentGridCellNode.getKey();
1792
+ } else if (r === toY && c === toX) {
1793
+ newFocusCellKey = currentGridCellNode.getKey();
1794
+ }
1795
+ const originalChildren = currentGridCellNode.getChildren();
1796
+ newGridCellNode.getChildren().forEach(child => {
1797
+ if (lexical.$isTextNode(child)) {
1798
+ const paragraphNode = lexical.$createParagraphNode();
1799
+ paragraphNode.append(child);
1800
+ currentGridCellNode.append(child);
1801
+ } else {
1802
+ currentGridCellNode.append(child);
1803
+ }
1804
+ });
1805
+ originalChildren.forEach(n => n.remove());
1806
+ newColumnIdx++;
1807
+ }
1808
+ newRowIdx++;
1458
1809
  }
1459
- const edgeChildDOM = editor.getElementByKey(edgeChild.__key);
1460
- if (edgeChildDOM == null) {
1461
- return false;
1810
+ if (newAnchorCellKey && newFocusCellKey) {
1811
+ const newTableSelection = $createTableSelection();
1812
+ newTableSelection.set(nodes[0].getKey(), newAnchorCellKey, newFocusCellKey);
1813
+ lexical.$setSelection(newTableSelection);
1462
1814
  }
1463
- const edgeRect = edgeChildDOM.getBoundingClientRect();
1464
- const isExiting = direction === 'up' ? edgeRect.top > edgeSelectionRect.top - edgeSelectionRect.height : edgeSelectionRect.bottom + edgeSelectionRect.height > edgeRect.bottom;
1465
- if (isExiting) {
1466
- stopEvent(event);
1467
- const cords = tableNode.getCordsFromCellNode(anchorCellNode, tableSelection.grid);
1468
- if (event.shiftKey) {
1469
- const cell = tableNode.getCellFromCordsOrThrow(cords.x, cords.y, tableSelection.grid);
1470
- tableSelection.setAnchorCellForSelection(cell);
1471
- tableSelection.setFocusCellForSelection(cell, true);
1472
- } else {
1473
- return selectGridNodeInDirection(tableSelection, tableNode, cords.x, cords.y, direction);
1815
+ return true;
1816
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1817
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_CHANGE_COMMAND, () => {
1818
+ const selection = lexical.$getSelection();
1819
+ const prevSelection = lexical.$getPreviousSelection();
1820
+ if (lexical.$isRangeSelection(selection)) {
1821
+ const {
1822
+ anchor,
1823
+ focus
1824
+ } = selection;
1825
+ const anchorNode = anchor.getNode();
1826
+ const focusNode = focus.getNode();
1827
+ // Using explicit comparison with table node to ensure it's not a nested table
1828
+ // as in that case we'll leave selection resolving to that table
1829
+ const anchorCellNode = $findCellNode(anchorNode);
1830
+ const focusCellNode = $findCellNode(focusNode);
1831
+ const isAnchorInside = anchorCellNode && tableNode.is($findTableNode(anchorCellNode));
1832
+ const isFocusInside = focusCellNode && tableNode.is($findTableNode(focusCellNode));
1833
+ const isPartialyWithinTable = isAnchorInside !== isFocusInside;
1834
+ const isWithinTable = isAnchorInside && isFocusInside;
1835
+ const isBackward = selection.isBackward();
1836
+ if (isPartialyWithinTable) {
1837
+ const newSelection = selection.clone();
1838
+ newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
1839
+ lexical.$setSelection(newSelection);
1840
+ $addHighlightStyleToTable(editor, tableObserver);
1841
+ } else if (isWithinTable) {
1842
+ // Handle case when selection spans across multiple cells but still
1843
+ // has range selection, then we convert it into grid selection
1844
+ if (!anchorCellNode.is(focusCellNode)) {
1845
+ tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
1846
+ tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
1847
+ }
1474
1848
  }
1475
- return true;
1476
1849
  }
1477
- } else if ($isGridSelection(selection)) {
1478
- const {
1479
- anchor,
1480
- focus
1481
- } = selection;
1482
- const anchorCellNode = utils.$findMatchingParent(anchor.getNode(), $isTableCellNode);
1483
- const focusCellNode = utils.$findMatchingParent(focus.getNode(), $isTableCellNode);
1484
- const [tableNodeFromSelection] = selection.getNodes();
1485
- const tableElement = editor.getElementByKey(tableNodeFromSelection.getKey());
1486
- if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableNode(tableNodeFromSelection) || tableElement == null) {
1850
+ if (selection && !selection.is(prevSelection) && ($isTableSelection(selection) || $isTableSelection(prevSelection)) && tableObserver.tableSelection && !tableObserver.tableSelection.is(prevSelection)) {
1851
+ if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
1852
+ tableObserver.updateTableTableSelection(selection);
1853
+ } else if (!$isTableSelection(selection) && $isTableSelection(prevSelection) && prevSelection.tableKey === tableObserver.tableNodeKey) {
1854
+ tableObserver.updateTableTableSelection(null);
1855
+ }
1487
1856
  return false;
1488
1857
  }
1489
- tableSelection.updateTableGridSelection(selection);
1490
- const grid = getTableGrid(tableElement);
1491
- const cordsAnchor = tableNode.getCordsFromCellNode(anchorCellNode, grid);
1492
- const anchorCell = tableNode.getCellFromCordsOrThrow(cordsAnchor.x, cordsAnchor.y, grid);
1493
- tableSelection.setAnchorCellForSelection(anchorCell);
1494
- stopEvent(event);
1495
- if (event.shiftKey) {
1496
- const cords = tableNode.getCordsFromCellNode(focusCellNode, grid);
1497
- return adjustFocusNodeInDirection(tableSelection, tableNodeFromSelection, cords.x, cords.y, direction);
1498
- } else {
1499
- focusCellNode.selectEnd();
1858
+ if (tableObserver.hasHijackedSelectionStyles && !tableNode.isSelected()) {
1859
+ $removeHighlightStyleToTable(editor, tableObserver);
1860
+ } else if (!tableObserver.hasHijackedSelectionStyles && tableNode.isSelected()) {
1861
+ $addHighlightStyleToTable(editor, tableObserver);
1500
1862
  }
1501
- return true;
1502
- }
1503
- return false;
1863
+ return false;
1864
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1865
+ return tableObserver;
1504
1866
  }
1505
- function stopEvent(event) {
1506
- event.preventDefault();
1507
- event.stopImmediatePropagation();
1508
- event.stopPropagation();
1867
+ function attachTableObserverToTableElement(tableElement, tableObserver) {
1868
+ tableElement[LEXICAL_ELEMENT_KEY] = tableObserver;
1509
1869
  }
1510
-
1511
- /**
1512
- * Copyright (c) Meta Platforms, Inc. and affiliates.
1513
- *
1514
- * This source code is licensed under the MIT license found in the
1515
- * LICENSE file in the root directory of this source tree.
1516
- *
1517
- */
1518
- /** @noInheritDoc */
1519
- class TableNode extends lexical.DEPRECATED_GridNode {
1520
- /** @internal */
1521
-
1522
- static getType() {
1523
- return 'table';
1524
- }
1525
- static clone(node) {
1526
- return new TableNode(node.__key);
1527
- }
1528
- static importDOM() {
1529
- return {
1530
- table: _node => ({
1531
- conversion: convertTableElement,
1532
- priority: 1
1533
- })
1534
- };
1535
- }
1536
- static importJSON(_serializedNode) {
1537
- return $createTableNode();
1538
- }
1539
- constructor(key) {
1540
- super(key);
1541
- }
1542
- exportJSON() {
1543
- return {
1544
- ...super.exportJSON(),
1545
- type: 'table',
1546
- version: 1
1547
- };
1548
- }
1549
- createDOM(config, editor) {
1550
- const tableElement = document.createElement('table');
1551
- utils.addClassNamesToElement(tableElement, config.theme.table);
1552
- return tableElement;
1553
- }
1554
- updateDOM() {
1555
- return false;
1556
- }
1557
- exportDOM(editor) {
1558
- return {
1559
- ...super.exportDOM(editor),
1560
- after: tableElement => {
1561
- if (tableElement) {
1562
- const newElement = tableElement.cloneNode();
1563
- const colGroup = document.createElement('colgroup');
1564
- const tBody = document.createElement('tbody');
1565
- if (utils.isHTMLElement(tableElement)) {
1566
- tBody.append(...tableElement.children);
1567
- }
1568
- const firstRow = this.getFirstChildOrThrow();
1569
- if (!$isTableRowNode(firstRow)) {
1570
- throw new Error('Expected to find row node.');
1571
- }
1572
- const colCount = firstRow.getChildrenSize();
1573
- for (let i = 0; i < colCount; i++) {
1574
- const col = document.createElement('col');
1575
- colGroup.append(col);
1576
- }
1577
- newElement.replaceChildren(colGroup, tBody);
1578
- return newElement;
1579
- }
1870
+ function getTableObserverFromTableElement(tableElement) {
1871
+ return tableElement[LEXICAL_ELEMENT_KEY];
1872
+ }
1873
+ function getDOMCellFromTarget(node) {
1874
+ let currentNode = node;
1875
+ while (currentNode != null) {
1876
+ const nodeName = currentNode.nodeName;
1877
+ if (nodeName === 'TD' || nodeName === 'TH') {
1878
+ // @ts-expect-error: internal field
1879
+ const cell = currentNode._cell;
1880
+ if (cell === undefined) {
1881
+ return null;
1580
1882
  }
1581
- };
1883
+ return cell;
1884
+ }
1885
+ currentNode = currentNode.parentNode;
1582
1886
  }
1887
+ return null;
1888
+ }
1889
+ function getTable(tableElement) {
1890
+ const domRows = [];
1891
+ const grid = {
1892
+ columns: 0,
1893
+ domRows,
1894
+ rows: 0
1895
+ };
1896
+ let currentNode = tableElement.firstChild;
1897
+ let x = 0;
1898
+ let y = 0;
1899
+ domRows.length = 0;
1900
+ while (currentNode != null) {
1901
+ const nodeMame = currentNode.nodeName;
1902
+ if (nodeMame === 'TD' || nodeMame === 'TH') {
1903
+ const elem = currentNode;
1904
+ const cell = {
1905
+ elem,
1906
+ hasBackgroundColor: elem.style.backgroundColor !== '',
1907
+ highlighted: false,
1908
+ x,
1909
+ y
1910
+ };
1583
1911
 
1584
- // TODO 0.10 deprecate
1585
- canExtractContents() {
1586
- return false;
1587
- }
1588
- canBeEmpty() {
1589
- return false;
1590
- }
1591
- isShadowRoot() {
1592
- return true;
1593
- }
1594
- getCordsFromCellNode(tableCellNode, grid) {
1595
- const {
1596
- rows,
1597
- cells
1598
- } = grid;
1599
- for (let y = 0; y < rows; y++) {
1600
- const row = cells[y];
1601
- if (row == null) {
1602
- continue;
1912
+ // @ts-expect-error: internal field
1913
+ currentNode._cell = cell;
1914
+ let row = domRows[y];
1915
+ if (row === undefined) {
1916
+ row = domRows[y] = [];
1603
1917
  }
1604
- const x = row.findIndex(cell => {
1605
- if (!cell) return;
1606
- const {
1607
- elem
1608
- } = cell;
1609
- const cellNode = lexical.$getNearestNodeFromDOMNode(elem);
1610
- return cellNode === tableCellNode;
1611
- });
1612
- if (x !== -1) {
1613
- return {
1614
- x,
1615
- y
1616
- };
1918
+ row[x] = cell;
1919
+ } else {
1920
+ const child = currentNode.firstChild;
1921
+ if (child != null) {
1922
+ currentNode = child;
1923
+ continue;
1617
1924
  }
1618
1925
  }
1619
- throw new Error('Cell not found in table.');
1620
- }
1621
- getCellFromCords(x, y, grid) {
1622
- const {
1623
- cells
1624
- } = grid;
1625
- const row = cells[y];
1626
- if (row == null) {
1627
- return null;
1628
- }
1629
- const cell = row[x];
1630
- if (cell == null) {
1631
- return null;
1926
+ const sibling = currentNode.nextSibling;
1927
+ if (sibling != null) {
1928
+ x++;
1929
+ currentNode = sibling;
1930
+ continue;
1632
1931
  }
1633
- return cell;
1634
- }
1635
- getCellFromCordsOrThrow(x, y, grid) {
1636
- const cell = this.getCellFromCords(x, y, grid);
1637
- if (!cell) {
1638
- throw new Error('Cell not found at cords.');
1932
+ const parent = currentNode.parentNode;
1933
+ if (parent != null) {
1934
+ const parentSibling = parent.nextSibling;
1935
+ if (parentSibling == null) {
1936
+ break;
1937
+ }
1938
+ y++;
1939
+ x = 0;
1940
+ currentNode = parentSibling;
1639
1941
  }
1640
- return cell;
1641
1942
  }
1642
- getCellNodeFromCords(x, y, grid) {
1643
- const cell = this.getCellFromCords(x, y, grid);
1644
- if (cell == null) {
1645
- return null;
1943
+ grid.columns = x + 1;
1944
+ grid.rows = y + 1;
1945
+ return grid;
1946
+ }
1947
+ function $updateDOMForSelection(editor, table, selection) {
1948
+ const selectedCellNodes = new Set(selection ? selection.getNodes() : []);
1949
+ $forEachTableCell(table, (cell, lexicalNode) => {
1950
+ const elem = cell.elem;
1951
+ if (selectedCellNodes.has(lexicalNode)) {
1952
+ cell.highlighted = true;
1953
+ $addHighlightToDOM(editor, cell);
1954
+ } else {
1955
+ cell.highlighted = false;
1956
+ $removeHighlightFromDOM(editor, cell);
1957
+ if (!elem.getAttribute('style')) {
1958
+ elem.removeAttribute('style');
1959
+ }
1646
1960
  }
1647
- const node = lexical.$getNearestNodeFromDOMNode(cell.elem);
1648
- if ($isTableCellNode(node)) {
1649
- return node;
1961
+ });
1962
+ }
1963
+ function $forEachTableCell(grid, cb) {
1964
+ const {
1965
+ domRows
1966
+ } = grid;
1967
+ for (let y = 0; y < domRows.length; y++) {
1968
+ const row = domRows[y];
1969
+ if (!row) {
1970
+ continue;
1650
1971
  }
1651
- return null;
1652
- }
1653
- getCellNodeFromCordsOrThrow(x, y, grid) {
1654
- const node = this.getCellNodeFromCords(x, y, grid);
1655
- if (!node) {
1656
- throw new Error('Node at cords not TableCellNode.');
1972
+ for (let x = 0; x < row.length; x++) {
1973
+ const cell = row[x];
1974
+ if (!cell) {
1975
+ continue;
1976
+ }
1977
+ const lexicalNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1978
+ if (lexicalNode !== null) {
1979
+ cb(cell, lexicalNode, {
1980
+ x,
1981
+ y
1982
+ });
1983
+ }
1657
1984
  }
1658
- return node;
1659
- }
1660
- canSelectBefore() {
1661
- return true;
1662
- }
1663
- canIndent() {
1664
- return false;
1665
1985
  }
1666
1986
  }
1667
- function $getElementGridForTableNode(editor, tableNode) {
1668
- const tableElement = editor.getElementByKey(tableNode.getKey());
1669
- if (tableElement == null) {
1670
- throw new Error('Table Element Not Found');
1671
- }
1672
- return getTableGrid(tableElement);
1673
- }
1674
- function convertTableElement(_domNode) {
1675
- return {
1676
- node: $createTableNode()
1677
- };
1678
- }
1679
- function $createTableNode() {
1680
- return lexical.$applyNodeReplacement(new TableNode());
1987
+ function $addHighlightStyleToTable(editor, tableSelection) {
1988
+ tableSelection.disableHighlightStyle();
1989
+ $forEachTableCell(tableSelection.table, cell => {
1990
+ cell.highlighted = true;
1991
+ $addHighlightToDOM(editor, cell);
1992
+ });
1681
1993
  }
1682
- function $isTableNode(node) {
1683
- return node instanceof TableNode;
1994
+ function $removeHighlightStyleToTable(editor, tableObserver) {
1995
+ tableObserver.enableHighlightStyle();
1996
+ $forEachTableCell(tableObserver.table, cell => {
1997
+ const elem = cell.elem;
1998
+ cell.highlighted = false;
1999
+ $removeHighlightFromDOM(editor, cell);
2000
+ if (!elem.getAttribute('style')) {
2001
+ elem.removeAttribute('style');
2002
+ }
2003
+ });
1684
2004
  }
1685
-
1686
- /**
1687
- * Copyright (c) Meta Platforms, Inc. and affiliates.
1688
- *
1689
- * This source code is licensed under the MIT license found in the
1690
- * LICENSE file in the root directory of this source tree.
1691
- *
1692
- */
1693
- function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders = true) {
1694
- const tableNode = $createTableNode();
1695
- for (let iRow = 0; iRow < rowCount; iRow++) {
1696
- const tableRowNode = $createTableRowNode();
1697
- for (let iColumn = 0; iColumn < columnCount; iColumn++) {
1698
- let headerState = TableCellHeaderStates.NO_STATUS;
1699
- if (typeof includeHeaders === 'object') {
1700
- if (iRow === 0 && includeHeaders.rows) headerState |= TableCellHeaderStates.ROW;
1701
- if (iColumn === 0 && includeHeaders.columns) headerState |= TableCellHeaderStates.COLUMN;
1702
- } else if (includeHeaders) {
1703
- if (iRow === 0) headerState |= TableCellHeaderStates.ROW;
1704
- if (iColumn === 0) headerState |= TableCellHeaderStates.COLUMN;
2005
+ const selectTableNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
2006
+ const isForward = direction === 'forward';
2007
+ switch (direction) {
2008
+ case 'backward':
2009
+ case 'forward':
2010
+ if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
2011
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table), isForward);
2012
+ } else {
2013
+ if (y !== (isForward ? tableObserver.table.rows - 1 : 0)) {
2014
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : tableObserver.table.columns - 1, y + (isForward ? 1 : -1), tableObserver.table), isForward);
2015
+ } else if (!isForward) {
2016
+ tableNode.selectPrevious();
2017
+ } else {
2018
+ tableNode.selectNext();
2019
+ }
1705
2020
  }
1706
- const tableCellNode = $createTableCellNode(headerState);
1707
- const paragraphNode = lexical.$createParagraphNode();
1708
- paragraphNode.append(lexical.$createTextNode());
1709
- tableCellNode.append(paragraphNode);
1710
- tableRowNode.append(tableCellNode);
1711
- }
1712
- tableNode.append(tableRowNode);
2021
+ return true;
2022
+ case 'up':
2023
+ if (y !== 0) {
2024
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableObserver.table), false);
2025
+ } else {
2026
+ tableNode.selectPrevious();
2027
+ }
2028
+ return true;
2029
+ case 'down':
2030
+ if (y !== tableObserver.table.rows - 1) {
2031
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableObserver.table), true);
2032
+ } else {
2033
+ tableNode.selectNext();
2034
+ }
2035
+ return true;
2036
+ default:
2037
+ return false;
1713
2038
  }
1714
- return tableNode;
1715
- }
1716
- function $getTableCellNodeFromLexicalNode(startingNode) {
1717
- const node = utils.$findMatchingParent(startingNode, n => $isTableCellNode(n));
1718
- if ($isTableCellNode(node)) {
1719
- return node;
2039
+ };
2040
+ const adjustFocusNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
2041
+ const isForward = direction === 'forward';
2042
+ switch (direction) {
2043
+ case 'backward':
2044
+ case 'forward':
2045
+ if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
2046
+ tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table));
2047
+ }
2048
+ return true;
2049
+ case 'up':
2050
+ if (y !== 0) {
2051
+ tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y - 1, tableObserver.table));
2052
+ return true;
2053
+ } else {
2054
+ return false;
2055
+ }
2056
+ case 'down':
2057
+ if (y !== tableObserver.table.rows - 1) {
2058
+ tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y + 1, tableObserver.table));
2059
+ return true;
2060
+ } else {
2061
+ return false;
2062
+ }
2063
+ default:
2064
+ return false;
1720
2065
  }
1721
- return null;
1722
- }
1723
- function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
1724
- const node = utils.$findMatchingParent(startingNode, n => $isTableRowNode(n));
1725
- if ($isTableRowNode(node)) {
1726
- return node;
2066
+ };
2067
+ function $isSelectionInTable(selection, tableNode) {
2068
+ if (lexical.$isRangeSelection(selection) || $isTableSelection(selection)) {
2069
+ const isAnchorInside = tableNode.isParentOf(selection.anchor.getNode());
2070
+ const isFocusInside = tableNode.isParentOf(selection.focus.getNode());
2071
+ return isAnchorInside && isFocusInside;
1727
2072
  }
1728
- throw new Error('Expected table cell to be inside of table row.');
2073
+ return false;
1729
2074
  }
1730
- function $getTableNodeFromLexicalNodeOrThrow(startingNode) {
1731
- const node = utils.$findMatchingParent(startingNode, n => $isTableNode(n));
1732
- if ($isTableNode(node)) {
1733
- return node;
2075
+ function selectTableCellNode(tableCell, fromStart) {
2076
+ if (fromStart) {
2077
+ tableCell.selectStart();
2078
+ } else {
2079
+ tableCell.selectEnd();
1734
2080
  }
1735
- throw new Error('Expected table cell to be inside of table.');
1736
2081
  }
1737
- function $getTableRowIndexFromTableCellNode(tableCellNode) {
1738
- const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
1739
- const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableRowNode);
1740
- return tableNode.getChildren().findIndex(n => n.is(tableRowNode));
2082
+ const BROWSER_BLUE_RGB = '172,206,247';
2083
+ function $addHighlightToDOM(editor, cell) {
2084
+ const element = cell.elem;
2085
+ const node = lexical.$getNearestNodeFromDOMNode(element);
2086
+ if (!$isTableCellNode(node)) {
2087
+ throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
2088
+ }
2089
+ const backgroundColor = node.getBackgroundColor();
2090
+ if (backgroundColor === null) {
2091
+ element.style.setProperty('background-color', `rgb(${BROWSER_BLUE_RGB})`);
2092
+ } else {
2093
+ element.style.setProperty('background-image', `linear-gradient(to right, rgba(${BROWSER_BLUE_RGB},0.85), rgba(${BROWSER_BLUE_RGB},0.85))`);
2094
+ }
2095
+ element.style.setProperty('caret-color', 'transparent');
1741
2096
  }
1742
- function $getTableColumnIndexFromTableCellNode(tableCellNode) {
1743
- const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
1744
- return tableRowNode.getChildren().findIndex(n => n.is(tableCellNode));
2097
+ function $removeHighlightFromDOM(editor, cell) {
2098
+ const element = cell.elem;
2099
+ const node = lexical.$getNearestNodeFromDOMNode(element);
2100
+ if (!$isTableCellNode(node)) {
2101
+ throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
2102
+ }
2103
+ const backgroundColor = node.getBackgroundColor();
2104
+ if (backgroundColor === null) {
2105
+ element.style.removeProperty('background-color');
2106
+ }
2107
+ element.style.removeProperty('background-image');
2108
+ element.style.removeProperty('caret-color');
1745
2109
  }
1746
- function $getTableCellSiblingsFromTableCellNode(tableCellNode, grid) {
1747
- const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
1748
- const {
1749
- x,
1750
- y
1751
- } = tableNode.getCordsFromCellNode(tableCellNode, grid);
1752
- return {
1753
- above: tableNode.getCellNodeFromCords(x, y - 1, grid),
1754
- below: tableNode.getCellNodeFromCords(x, y + 1, grid),
1755
- left: tableNode.getCellNodeFromCords(x - 1, y, grid),
1756
- right: tableNode.getCellNodeFromCords(x + 1, y, grid)
1757
- };
2110
+ function $findCellNode(node) {
2111
+ const cellNode = utils.$findMatchingParent(node, $isTableCellNode);
2112
+ return $isTableCellNode(cellNode) ? cellNode : null;
1758
2113
  }
1759
- function $removeTableRowAtIndex(tableNode, indexToDelete) {
1760
- const tableRows = tableNode.getChildren();
1761
- if (indexToDelete >= tableRows.length || indexToDelete < 0) {
1762
- throw new Error('Expected table cell to be inside of table row.');
1763
- }
1764
- const targetRowNode = tableRows[indexToDelete];
1765
- targetRowNode.remove();
1766
- return tableNode;
2114
+ function $findTableNode(node) {
2115
+ const tableNode = utils.$findMatchingParent(node, $isTableNode);
2116
+ return $isTableNode(tableNode) ? tableNode : null;
1767
2117
  }
1768
- function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, grid) {
1769
- const tableRows = tableNode.getChildren();
1770
- if (targetIndex >= tableRows.length || targetIndex < 0) {
1771
- throw new Error('Table row target index out of range');
2118
+ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2119
+ const selection = lexical.$getSelection();
2120
+ if (!$isSelectionInTable(selection, tableNode)) {
2121
+ return false;
1772
2122
  }
1773
- const targetRowNode = tableRows[targetIndex];
1774
- if ($isTableRowNode(targetRowNode)) {
1775
- for (let r = 0; r < rowCount; r++) {
1776
- const tableRowCells = targetRowNode.getChildren();
1777
- const tableColumnCount = tableRowCells.length;
1778
- const newTableRowNode = $createTableRowNode();
1779
- for (let c = 0; c < tableColumnCount; c++) {
1780
- const tableCellFromTargetRow = tableRowCells[c];
1781
- if (!$isTableCellNode(tableCellFromTargetRow)) {
1782
- throw Error(`Expected table cell`);
1783
- }
1784
- const {
1785
- above,
1786
- below
1787
- } = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow, grid);
1788
- let headerState = TableCellHeaderStates.NO_STATUS;
1789
- const width = above && above.getWidth() || below && below.getWidth() || undefined;
1790
- if (above && above.hasHeaderState(TableCellHeaderStates.COLUMN) || below && below.hasHeaderState(TableCellHeaderStates.COLUMN)) {
1791
- headerState |= TableCellHeaderStates.COLUMN;
1792
- }
1793
- const tableCellNode = $createTableCellNode(headerState, 1, width);
1794
- tableCellNode.append(lexical.$createParagraphNode());
1795
- newTableRowNode.append(tableCellNode);
1796
- }
1797
- if (shouldInsertAfter) {
1798
- targetRowNode.insertAfter(newTableRowNode);
1799
- } else {
1800
- targetRowNode.insertBefore(newTableRowNode);
1801
- }
2123
+ if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
2124
+ // Horizontal move between cels seem to work well without interruption
2125
+ // so just exit early, and handle vertical moves
2126
+ if (direction === 'backward' || direction === 'forward') {
2127
+ return false;
2128
+ }
2129
+ const {
2130
+ anchor,
2131
+ focus
2132
+ } = selection;
2133
+ const anchorCellNode = utils.$findMatchingParent(anchor.getNode(), $isTableCellNode);
2134
+ const focusCellNode = utils.$findMatchingParent(focus.getNode(), $isTableCellNode);
2135
+ if (!$isTableCellNode(anchorCellNode) || !anchorCellNode.is(focusCellNode)) {
2136
+ return false;
1802
2137
  }
1803
- } else {
1804
- throw new Error('Row before insertion index does not exist.');
1805
- }
1806
- return tableNode;
1807
- }
1808
- function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
1809
- const selection = lexical.$getSelection();
1810
- if (!lexical.$INTERNAL_isPointSelection(selection)) {
1811
- throw Error(`Expected a INTERNAL_PointSelection`);
1812
- }
1813
- const focus = selection.focus.getNode();
1814
- const [focusCell,, grid] = lexical.DEPRECATED_$getNodeTriplet(focus);
1815
- const [gridMap, focusCellMap] = lexical.DEPRECATED_$computeGridMap(grid, focusCell, focusCell);
1816
- const columnCount = gridMap[0].length;
1817
- const {
1818
- startRow: focusStartRow
1819
- } = focusCellMap;
1820
- if (insertAfter) {
1821
- const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
1822
- const focusEndRowMap = gridMap[focusEndRow];
1823
- const newRow = $createTableRowNode();
1824
- for (let i = 0; i < columnCount; i++) {
1825
- const {
1826
- cell,
1827
- startRow
1828
- } = focusEndRowMap[i];
1829
- if (startRow + cell.__rowSpan - 1 <= focusEndRow) {
1830
- newRow.append($createTableCellNode(TableCellHeaderStates.NO_STATUS).append(lexical.$createParagraphNode()));
1831
- } else {
1832
- cell.setRowSpan(cell.__rowSpan + 1);
2138
+ const anchorCellTable = $findTableNode(anchorCellNode);
2139
+ if (anchorCellTable !== tableNode && anchorCellTable != null) {
2140
+ const anchorCellTableElement = editor.getElementByKey(anchorCellTable.getKey());
2141
+ if (anchorCellTableElement != null) {
2142
+ tableObserver.table = getTable(anchorCellTableElement);
2143
+ return $handleArrowKey(editor, event, direction, anchorCellTable, tableObserver);
1833
2144
  }
1834
2145
  }
1835
- const focusEndRowNode = grid.getChildAtIndex(focusEndRow);
1836
- if (!lexical.DEPRECATED_$isGridRowNode(focusEndRowNode)) {
1837
- throw Error(`focusEndRow is not a GridRowNode`);
2146
+ const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
2147
+ const anchorDOM = editor.getElementByKey(anchor.key);
2148
+ if (anchorDOM == null || anchorCellDom == null) {
2149
+ return false;
1838
2150
  }
1839
- focusEndRowNode.insertAfter(newRow);
1840
- } else {
1841
- const focusStartRowMap = gridMap[focusStartRow];
1842
- const newRow = $createTableRowNode();
1843
- for (let i = 0; i < columnCount; i++) {
1844
- const {
1845
- cell,
1846
- startRow
1847
- } = focusStartRowMap[i];
1848
- if (startRow === focusStartRow) {
1849
- newRow.append($createTableCellNode(TableCellHeaderStates.NO_STATUS).append(lexical.$createParagraphNode()));
1850
- } else {
1851
- cell.setRowSpan(cell.__rowSpan + 1);
2151
+ let edgeSelectionRect;
2152
+ if (anchor.type === 'element') {
2153
+ edgeSelectionRect = anchorDOM.getBoundingClientRect();
2154
+ } else {
2155
+ const domSelection = window.getSelection();
2156
+ if (domSelection === null || domSelection.rangeCount === 0) {
2157
+ return false;
1852
2158
  }
2159
+ const range = domSelection.getRangeAt(0);
2160
+ edgeSelectionRect = range.getBoundingClientRect();
1853
2161
  }
1854
- const focusStartRowNode = grid.getChildAtIndex(focusStartRow);
1855
- if (!lexical.DEPRECATED_$isGridRowNode(focusStartRowNode)) {
1856
- throw Error(`focusEndRow is not a GridRowNode`);
2162
+ const edgeChild = direction === 'up' ? anchorCellNode.getFirstChild() : anchorCellNode.getLastChild();
2163
+ if (edgeChild == null) {
2164
+ return false;
1857
2165
  }
1858
- focusStartRowNode.insertBefore(newRow);
1859
- }
1860
- }
1861
- function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, columnCount, grid) {
1862
- const tableRows = tableNode.getChildren();
1863
- const tableCellsToBeInserted = [];
1864
- for (let r = 0; r < tableRows.length; r++) {
1865
- const currentTableRowNode = tableRows[r];
1866
- if ($isTableRowNode(currentTableRowNode)) {
1867
- for (let c = 0; c < columnCount; c++) {
1868
- const tableRowChildren = currentTableRowNode.getChildren();
1869
- if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
1870
- throw new Error('Table column target index out of range');
1871
- }
1872
- const targetCell = tableRowChildren[targetIndex];
1873
- if (!$isTableCellNode(targetCell)) {
1874
- throw Error(`Expected table cell`);
1875
- }
1876
- const {
1877
- left,
1878
- right
1879
- } = $getTableCellSiblingsFromTableCellNode(targetCell, grid);
1880
- let headerState = TableCellHeaderStates.NO_STATUS;
1881
- if (left && left.hasHeaderState(TableCellHeaderStates.ROW) || right && right.hasHeaderState(TableCellHeaderStates.ROW)) {
1882
- headerState |= TableCellHeaderStates.ROW;
1883
- }
1884
- const newTableCell = $createTableCellNode(headerState);
1885
- newTableCell.append(lexical.$createParagraphNode());
1886
- tableCellsToBeInserted.push({
1887
- newTableCell,
1888
- targetCell
1889
- });
2166
+ const edgeChildDOM = editor.getElementByKey(edgeChild.__key);
2167
+ if (edgeChildDOM == null) {
2168
+ return false;
2169
+ }
2170
+ const edgeRect = edgeChildDOM.getBoundingClientRect();
2171
+ const isExiting = direction === 'up' ? edgeRect.top > edgeSelectionRect.top - edgeSelectionRect.height : edgeSelectionRect.bottom + edgeSelectionRect.height > edgeRect.bottom;
2172
+ if (isExiting) {
2173
+ stopEvent(event);
2174
+ const cords = tableNode.getCordsFromCellNode(anchorCellNode, tableObserver.table);
2175
+ if (event.shiftKey) {
2176
+ const cell = tableNode.getDOMCellFromCordsOrThrow(cords.x, cords.y, tableObserver.table);
2177
+ tableObserver.setAnchorCellForSelection(cell);
2178
+ tableObserver.setFocusCellForSelection(cell, true);
2179
+ } else {
2180
+ return selectTableNodeInDirection(tableObserver, tableNode, cords.x, cords.y, direction);
1890
2181
  }
2182
+ return true;
1891
2183
  }
1892
- }
1893
- tableCellsToBeInserted.forEach(({
1894
- newTableCell,
1895
- targetCell
1896
- }) => {
1897
- if (shouldInsertAfter) {
1898
- targetCell.insertAfter(newTableCell);
2184
+ } else if ($isTableSelection(selection)) {
2185
+ const {
2186
+ anchor,
2187
+ focus
2188
+ } = selection;
2189
+ const anchorCellNode = utils.$findMatchingParent(anchor.getNode(), $isTableCellNode);
2190
+ const focusCellNode = utils.$findMatchingParent(focus.getNode(), $isTableCellNode);
2191
+ const [tableNodeFromSelection] = selection.getNodes();
2192
+ const tableElement = editor.getElementByKey(tableNodeFromSelection.getKey());
2193
+ if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableNode(tableNodeFromSelection) || tableElement == null) {
2194
+ return false;
2195
+ }
2196
+ tableObserver.updateTableTableSelection(selection);
2197
+ const grid = getTable(tableElement);
2198
+ const cordsAnchor = tableNode.getCordsFromCellNode(anchorCellNode, grid);
2199
+ const anchorCell = tableNode.getDOMCellFromCordsOrThrow(cordsAnchor.x, cordsAnchor.y, grid);
2200
+ tableObserver.setAnchorCellForSelection(anchorCell);
2201
+ stopEvent(event);
2202
+ if (event.shiftKey) {
2203
+ const cords = tableNode.getCordsFromCellNode(focusCellNode, grid);
2204
+ return adjustFocusNodeInDirection(tableObserver, tableNodeFromSelection, cords.x, cords.y, direction);
1899
2205
  } else {
1900
- targetCell.insertBefore(newTableCell);
2206
+ focusCellNode.selectEnd();
1901
2207
  }
1902
- });
1903
- return tableNode;
2208
+ return true;
2209
+ }
2210
+ return false;
1904
2211
  }
1905
- function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
1906
- const selection = lexical.$getSelection();
1907
- if (!lexical.$INTERNAL_isPointSelection(selection)) {
1908
- throw Error(`Expected a PointSeleciton`);
2212
+ function stopEvent(event) {
2213
+ event.preventDefault();
2214
+ event.stopImmediatePropagation();
2215
+ event.stopPropagation();
2216
+ }
2217
+
2218
+ /**
2219
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
2220
+ *
2221
+ * This source code is licensed under the MIT license found in the
2222
+ * LICENSE file in the root directory of this source tree.
2223
+ *
2224
+ */
2225
+ /** @noInheritDoc */
2226
+ class TableNode extends lexical.ElementNode {
2227
+ static getType() {
2228
+ return 'table';
1909
2229
  }
1910
- const anchor = selection.anchor.getNode();
1911
- const focus = selection.focus.getNode();
1912
- const [anchorCell] = lexical.DEPRECATED_$getNodeTriplet(anchor);
1913
- const [focusCell,, grid] = lexical.DEPRECATED_$getNodeTriplet(focus);
1914
- const [gridMap, focusCellMap, anchorCellMap] = lexical.DEPRECATED_$computeGridMap(grid, focusCell, anchorCell);
1915
- const rowCount = gridMap.length;
1916
- const startColumn = insertAfter ? Math.max(focusCellMap.startColumn, anchorCellMap.startColumn) : Math.min(focusCellMap.startColumn, anchorCellMap.startColumn);
1917
- const insertAfterColumn = insertAfter ? startColumn + focusCell.__colSpan - 1 : startColumn - 1;
1918
- const gridFirstChild = grid.getFirstChild();
1919
- if (!lexical.DEPRECATED_$isGridRowNode(gridFirstChild)) {
1920
- throw Error(`Expected firstTable child to be a row`);
2230
+ static clone(node) {
2231
+ return new TableNode(node.__key);
1921
2232
  }
1922
- let firstInsertedCell = null;
1923
- function $createTableCellNodeForInsertTableColumn() {
1924
- const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS).append(lexical.$createParagraphNode());
1925
- if (firstInsertedCell === null) {
1926
- firstInsertedCell = cell;
1927
- }
1928
- return cell;
2233
+ static importDOM() {
2234
+ return {
2235
+ table: _node => ({
2236
+ conversion: convertTableElement,
2237
+ priority: 1
2238
+ })
2239
+ };
1929
2240
  }
1930
- let loopRow = gridFirstChild;
1931
- rowLoop: for (let i = 0; i < rowCount; i++) {
1932
- if (i !== 0) {
1933
- const currentRow = loopRow.getNextSibling();
1934
- if (!lexical.DEPRECATED_$isGridRowNode(currentRow)) {
1935
- throw Error(`Expected row nextSibling to be a row`);
1936
- }
1937
- loopRow = currentRow;
1938
- }
1939
- const rowMap = gridMap[i];
1940
- if (insertAfterColumn < 0) {
1941
- $insertFirst(loopRow, $createTableCellNodeForInsertTableColumn());
1942
- continue;
1943
- }
1944
- const {
1945
- cell: currentCell,
1946
- startColumn: currentStartColumn,
1947
- startRow: currentStartRow
1948
- } = rowMap[insertAfterColumn];
1949
- if (currentStartColumn + currentCell.__colSpan - 1 <= insertAfterColumn) {
1950
- let insertAfterCell = currentCell;
1951
- let insertAfterCellRowStart = currentStartRow;
1952
- let prevCellIndex = insertAfterColumn;
1953
- while (insertAfterCellRowStart !== i && insertAfterCell.__rowSpan > 1) {
1954
- prevCellIndex -= currentCell.__colSpan;
1955
- if (prevCellIndex >= 0) {
1956
- const {
1957
- cell: cell_,
1958
- startRow: startRow_
1959
- } = rowMap[prevCellIndex];
1960
- insertAfterCell = cell_;
1961
- insertAfterCellRowStart = startRow_;
1962
- } else {
1963
- loopRow.append($createTableCellNodeForInsertTableColumn());
1964
- continue rowLoop;
1965
- }
1966
- }
1967
- insertAfterCell.insertAfter($createTableCellNodeForInsertTableColumn());
1968
- } else {
1969
- currentCell.setColSpan(currentCell.__colSpan + 1);
1970
- }
2241
+ static importJSON(_serializedNode) {
2242
+ return $createTableNode();
1971
2243
  }
1972
- if (firstInsertedCell !== null) {
1973
- $moveSelectionToCell(firstInsertedCell);
2244
+ constructor(key) {
2245
+ super(key);
1974
2246
  }
1975
- }
1976
- function $deleteTableColumn(tableNode, targetIndex) {
1977
- const tableRows = tableNode.getChildren();
1978
- for (let i = 0; i < tableRows.length; i++) {
1979
- const currentTableRowNode = tableRows[i];
1980
- if ($isTableRowNode(currentTableRowNode)) {
1981
- const tableRowChildren = currentTableRowNode.getChildren();
1982
- if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
1983
- throw new Error('Table column target index out of range');
2247
+ exportJSON() {
2248
+ return {
2249
+ ...super.exportJSON(),
2250
+ type: 'table',
2251
+ version: 1
2252
+ };
2253
+ }
2254
+ createDOM(config, editor) {
2255
+ const tableElement = document.createElement('table');
2256
+ utils.addClassNamesToElement(tableElement, config.theme.table);
2257
+ return tableElement;
2258
+ }
2259
+ updateDOM() {
2260
+ return false;
2261
+ }
2262
+ exportDOM(editor) {
2263
+ return {
2264
+ ...super.exportDOM(editor),
2265
+ after: tableElement => {
2266
+ if (tableElement) {
2267
+ const newElement = tableElement.cloneNode();
2268
+ const colGroup = document.createElement('colgroup');
2269
+ const tBody = document.createElement('tbody');
2270
+ if (utils.isHTMLElement(tableElement)) {
2271
+ tBody.append(...tableElement.children);
2272
+ }
2273
+ const firstRow = this.getFirstChildOrThrow();
2274
+ if (!$isTableRowNode(firstRow)) {
2275
+ throw new Error('Expected to find row node.');
2276
+ }
2277
+ const colCount = firstRow.getChildrenSize();
2278
+ for (let i = 0; i < colCount; i++) {
2279
+ const col = document.createElement('col');
2280
+ colGroup.append(col);
2281
+ }
2282
+ newElement.replaceChildren(colGroup, tBody);
2283
+ return newElement;
2284
+ }
1984
2285
  }
1985
- tableRowChildren[targetIndex].remove();
1986
- }
2286
+ };
1987
2287
  }
1988
- return tableNode;
1989
- }
1990
- function $deleteTableRow__EXPERIMENTAL() {
1991
- const selection = lexical.$getSelection();
1992
- if (!lexical.$INTERNAL_isPointSelection(selection)) {
1993
- throw Error(`Expected a INTERNAL_PointSelection`);
2288
+
2289
+ // TODO 0.10 deprecate
2290
+ canExtractContents() {
2291
+ return false;
1994
2292
  }
1995
- const anchor = selection.anchor.getNode();
1996
- const focus = selection.focus.getNode();
1997
- const [anchorCell,, grid] = lexical.DEPRECATED_$getNodeTriplet(anchor);
1998
- const [focusCell] = lexical.DEPRECATED_$getNodeTriplet(focus);
1999
- const [gridMap, anchorCellMap, focusCellMap] = lexical.DEPRECATED_$computeGridMap(grid, anchorCell, focusCell);
2000
- const {
2001
- startRow: anchorStartRow
2002
- } = anchorCellMap;
2003
- const {
2004
- startRow: focusStartRow
2005
- } = focusCellMap;
2006
- const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
2007
- if (gridMap.length === focusEndRow - anchorStartRow + 1) {
2008
- // Empty grid
2009
- grid.remove();
2010
- return;
2293
+ canBeEmpty() {
2294
+ return false;
2011
2295
  }
2012
- const columnCount = gridMap[0].length;
2013
- const nextRow = gridMap[focusEndRow + 1];
2014
- const nextRowNode = grid.getChildAtIndex(focusEndRow + 1);
2015
- for (let row = focusEndRow; row >= anchorStartRow; row--) {
2016
- for (let column = columnCount - 1; column >= 0; column--) {
2017
- const {
2018
- cell,
2019
- startRow: cellStartRow,
2020
- startColumn: cellStartColumn
2021
- } = gridMap[row][column];
2022
- if (cellStartColumn !== column) {
2023
- // Don't repeat work for the same Cell
2296
+ isShadowRoot() {
2297
+ return true;
2298
+ }
2299
+ getCordsFromCellNode(tableCellNode, table) {
2300
+ const {
2301
+ rows,
2302
+ domRows
2303
+ } = table;
2304
+ for (let y = 0; y < rows; y++) {
2305
+ const row = domRows[y];
2306
+ if (row == null) {
2024
2307
  continue;
2025
2308
  }
2026
- // Rows overflowing top have to be trimmed
2027
- if (row === anchorStartRow && cellStartRow < anchorStartRow) {
2028
- cell.setRowSpan(cell.__rowSpan - (cellStartRow - anchorStartRow));
2029
- }
2030
- // Rows overflowing bottom have to be trimmed and moved to the next row
2031
- if (cellStartRow >= anchorStartRow && cellStartRow + cell.__rowSpan - 1 > focusEndRow) {
2032
- cell.setRowSpan(cell.__rowSpan - (focusEndRow - cellStartRow + 1));
2033
- if (!(nextRowNode !== null)) {
2034
- throw Error(`Expected nextRowNode not to be null`);
2035
- }
2036
- if (column === 0) {
2037
- $insertFirst(nextRowNode, cell);
2038
- } else {
2039
- const {
2040
- cell: previousCell
2041
- } = nextRow[column - 1];
2042
- previousCell.insertAfter(cell);
2043
- }
2309
+ const x = row.findIndex(cell => {
2310
+ if (!cell) return;
2311
+ const {
2312
+ elem
2313
+ } = cell;
2314
+ const cellNode = lexical.$getNearestNodeFromDOMNode(elem);
2315
+ return cellNode === tableCellNode;
2316
+ });
2317
+ if (x !== -1) {
2318
+ return {
2319
+ x,
2320
+ y
2321
+ };
2044
2322
  }
2045
2323
  }
2046
- const rowNode = grid.getChildAtIndex(row);
2047
- if (!lexical.DEPRECATED_$isGridRowNode(rowNode)) {
2048
- throw Error(`Expected GridNode childAtIndex(${String(row)}) to be RowNode`);
2049
- }
2050
- rowNode.remove();
2324
+ throw new Error('Cell not found in table.');
2051
2325
  }
2052
- if (nextRow !== undefined) {
2053
- const {
2054
- cell
2055
- } = nextRow[0];
2056
- $moveSelectionToCell(cell);
2057
- } else {
2058
- const previousRow = gridMap[anchorStartRow - 1];
2326
+ getDOMCellFromCords(x, y, table) {
2059
2327
  const {
2060
- cell
2061
- } = previousRow[0];
2062
- $moveSelectionToCell(cell);
2328
+ domRows
2329
+ } = table;
2330
+ const row = domRows[y];
2331
+ if (row == null) {
2332
+ return null;
2333
+ }
2334
+ const cell = row[x];
2335
+ if (cell == null) {
2336
+ return null;
2337
+ }
2338
+ return cell;
2063
2339
  }
2064
- }
2065
- function $deleteTableColumn__EXPERIMENTAL() {
2066
- const selection = lexical.$getSelection();
2067
- if (!lexical.$INTERNAL_isPointSelection(selection)) {
2068
- throw Error(`Expected a INTERNAL_PointSelection`);
2340
+ getDOMCellFromCordsOrThrow(x, y, table) {
2341
+ const cell = this.getDOMCellFromCords(x, y, table);
2342
+ if (!cell) {
2343
+ throw new Error('Cell not found at cords.');
2344
+ }
2345
+ return cell;
2069
2346
  }
2070
- const anchor = selection.anchor.getNode();
2071
- const focus = selection.focus.getNode();
2072
- const [anchorCell,, grid] = lexical.DEPRECATED_$getNodeTriplet(anchor);
2073
- const [focusCell] = lexical.DEPRECATED_$getNodeTriplet(focus);
2074
- const [gridMap, anchorCellMap, focusCellMap] = lexical.DEPRECATED_$computeGridMap(grid, anchorCell, focusCell);
2075
- const {
2076
- startColumn: anchorStartColumn
2077
- } = anchorCellMap;
2078
- const {
2079
- startRow: focusStartRow,
2080
- startColumn: focusStartColumn
2081
- } = focusCellMap;
2082
- const startColumn = Math.min(anchorStartColumn, focusStartColumn);
2083
- const endColumn = Math.max(anchorStartColumn + anchorCell.__colSpan - 1, focusStartColumn + focusCell.__colSpan - 1);
2084
- const selectedColumnCount = endColumn - startColumn + 1;
2085
- const columnCount = gridMap[0].length;
2086
- if (columnCount === endColumn - startColumn + 1) {
2087
- // Empty grid
2088
- grid.selectPrevious();
2089
- grid.remove();
2090
- return;
2347
+ getCellNodeFromCords(x, y, table) {
2348
+ const cell = this.getDOMCellFromCords(x, y, table);
2349
+ if (cell == null) {
2350
+ return null;
2351
+ }
2352
+ const node = lexical.$getNearestNodeFromDOMNode(cell.elem);
2353
+ if ($isTableCellNode(node)) {
2354
+ return node;
2355
+ }
2356
+ return null;
2091
2357
  }
2092
- const rowCount = gridMap.length;
2093
- for (let row = 0; row < rowCount; row++) {
2094
- for (let column = startColumn; column <= endColumn; column++) {
2095
- const {
2096
- cell,
2097
- startColumn: cellStartColumn
2098
- } = gridMap[row][column];
2099
- if (cellStartColumn < startColumn) {
2100
- if (column === startColumn) {
2101
- const overflowLeft = startColumn - cellStartColumn;
2102
- // Overflowing left
2103
- cell.setColSpan(cell.__colSpan -
2104
- // Possible overflow right too
2105
- Math.min(selectedColumnCount, cell.__colSpan - overflowLeft));
2106
- }
2107
- } else if (cellStartColumn + cell.__colSpan - 1 > endColumn) {
2108
- if (column === endColumn) {
2109
- // Overflowing right
2110
- const inSelectedArea = endColumn - cellStartColumn + 1;
2111
- cell.setColSpan(cell.__colSpan - inSelectedArea);
2112
- }
2113
- } else {
2114
- cell.remove();
2115
- }
2358
+ getCellNodeFromCordsOrThrow(x, y, table) {
2359
+ const node = this.getCellNodeFromCords(x, y, table);
2360
+ if (!node) {
2361
+ throw new Error('Node at cords not TableCellNode.');
2116
2362
  }
2363
+ return node;
2117
2364
  }
2118
- const focusRowMap = gridMap[focusStartRow];
2119
- const nextColumn = focusRowMap[focusStartColumn + focusCell.__colSpan];
2120
- if (nextColumn !== undefined) {
2121
- const {
2122
- cell
2123
- } = nextColumn;
2124
- $moveSelectionToCell(cell);
2125
- } else {
2126
- const previousRow = focusRowMap[focusStartColumn - 1];
2127
- const {
2128
- cell
2129
- } = previousRow;
2130
- $moveSelectionToCell(cell);
2365
+ canSelectBefore() {
2366
+ return true;
2131
2367
  }
2132
- }
2133
- function $moveSelectionToCell(cell) {
2134
- const firstDescendant = cell.getFirstDescendant();
2135
- if (firstDescendant == null) {
2136
- cell.selectStart();
2137
- } else {
2138
- firstDescendant.getParentOrThrow().selectStart();
2368
+ canIndent() {
2369
+ return false;
2139
2370
  }
2140
2371
  }
2141
- function $insertFirst(parent, node) {
2142
- const firstChild = parent.getFirstChild();
2143
- if (firstChild !== null) {
2144
- firstChild.insertBefore(node);
2145
- } else {
2146
- parent.append(node);
2372
+ function $getElementForTableNode(editor, tableNode) {
2373
+ const tableElement = editor.getElementByKey(tableNode.getKey());
2374
+ if (tableElement == null) {
2375
+ throw new Error('Table Element Not Found');
2147
2376
  }
2377
+ return getTable(tableElement);
2148
2378
  }
2149
- function $unmergeCell() {
2150
- const selection = lexical.$getSelection();
2151
- if (!lexical.$INTERNAL_isPointSelection(selection)) {
2152
- throw Error(`Expected a INTERNAL_PointSelection`);
2153
- }
2154
- const anchor = selection.anchor.getNode();
2155
- const [cell, row, grid] = lexical.DEPRECATED_$getNodeTriplet(anchor);
2156
- const colSpan = cell.__colSpan;
2157
- const rowSpan = cell.__rowSpan;
2158
- if (colSpan > 1) {
2159
- for (let i = 1; i < colSpan; i++) {
2160
- cell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
2161
- }
2162
- cell.setColSpan(1);
2163
- }
2164
- if (rowSpan > 1) {
2165
- const [map, cellMap] = lexical.DEPRECATED_$computeGridMap(grid, cell, cell);
2166
- const {
2167
- startColumn,
2168
- startRow
2169
- } = cellMap;
2170
- let currentRowNode;
2171
- for (let i = 1; i < rowSpan; i++) {
2172
- const currentRow = startRow + i;
2173
- const currentRowMap = map[currentRow];
2174
- currentRowNode = (currentRowNode || row).getNextSibling();
2175
- if (!lexical.DEPRECATED_$isGridRowNode(currentRowNode)) {
2176
- throw Error(`Expected row next sibling to be a row`);
2177
- }
2178
- let insertAfterCell = null;
2179
- for (let column = 0; column < startColumn; column++) {
2180
- const currentCellMap = currentRowMap[column];
2181
- const currentCell = currentCellMap.cell;
2182
- if (currentCellMap.startRow === currentRow) {
2183
- insertAfterCell = currentCell;
2184
- }
2185
- if (currentCell.__colSpan > 1) {
2186
- column += currentCell.__colSpan - 1;
2187
- }
2188
- }
2189
- if (insertAfterCell === null) {
2190
- for (let j = 0; j < colSpan; j++) {
2191
- $insertFirst(currentRowNode, $createTableCellNode(TableCellHeaderStates.NO_STATUS));
2192
- }
2193
- } else {
2194
- for (let j = 0; j < colSpan; j++) {
2195
- insertAfterCell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
2196
- }
2197
- }
2198
- }
2199
- cell.setRowSpan(1);
2200
- }
2379
+ function convertTableElement(_domNode) {
2380
+ return {
2381
+ node: $createTableNode()
2382
+ };
2383
+ }
2384
+ function $createTableNode() {
2385
+ return lexical.$applyNodeReplacement(new TableNode());
2386
+ }
2387
+ function $isTableNode(node) {
2388
+ return node instanceof TableNode;
2201
2389
  }
2202
2390
 
2203
- /** @module @lexical/table */
2204
- const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
2205
-
2206
- exports.$createGridSelection = $createGridSelection;
2391
+ exports.$computeTableMap = $computeTableMap;
2207
2392
  exports.$createTableCellNode = $createTableCellNode;
2208
2393
  exports.$createTableNode = $createTableNode;
2209
2394
  exports.$createTableNodeWithDimensions = $createTableNodeWithDimensions;
2210
2395
  exports.$createTableRowNode = $createTableRowNode;
2396
+ exports.$createTableSelection = $createTableSelection;
2211
2397
  exports.$deleteTableColumn = $deleteTableColumn;
2212
2398
  exports.$deleteTableColumn__EXPERIMENTAL = $deleteTableColumn__EXPERIMENTAL;
2213
2399
  exports.$deleteTableRow__EXPERIMENTAL = $deleteTableRow__EXPERIMENTAL;
2214
- exports.$getElementGridForTableNode = $getElementGridForTableNode;
2400
+ exports.$getElementForTableNode = $getElementForTableNode;
2401
+ exports.$getNodeTriplet = $getNodeTriplet;
2215
2402
  exports.$getTableCellNodeFromLexicalNode = $getTableCellNodeFromLexicalNode;
2403
+ exports.$getTableCellNodeRect = $getTableCellNodeRect;
2216
2404
  exports.$getTableColumnIndexFromTableCellNode = $getTableColumnIndexFromTableCellNode;
2217
2405
  exports.$getTableNodeFromLexicalNodeOrThrow = $getTableNodeFromLexicalNodeOrThrow;
2218
2406
  exports.$getTableRowIndexFromTableCellNode = $getTableRowIndexFromTableCellNode;
@@ -2221,18 +2409,18 @@ exports.$insertTableColumn = $insertTableColumn;
2221
2409
  exports.$insertTableColumn__EXPERIMENTAL = $insertTableColumn__EXPERIMENTAL;
2222
2410
  exports.$insertTableRow = $insertTableRow;
2223
2411
  exports.$insertTableRow__EXPERIMENTAL = $insertTableRow__EXPERIMENTAL;
2224
- exports.$isGridSelection = $isGridSelection;
2225
2412
  exports.$isTableCellNode = $isTableCellNode;
2226
2413
  exports.$isTableNode = $isTableNode;
2227
2414
  exports.$isTableRowNode = $isTableRowNode;
2415
+ exports.$isTableSelection = $isTableSelection;
2228
2416
  exports.$removeTableRowAtIndex = $removeTableRowAtIndex;
2229
2417
  exports.$unmergeCell = $unmergeCell;
2230
2418
  exports.INSERT_TABLE_COMMAND = INSERT_TABLE_COMMAND;
2231
2419
  exports.TableCellHeaderStates = TableCellHeaderStates;
2232
2420
  exports.TableCellNode = TableCellNode;
2233
2421
  exports.TableNode = TableNode;
2422
+ exports.TableObserver = TableObserver;
2234
2423
  exports.TableRowNode = TableRowNode;
2235
- exports.TableSelection = TableSelection;
2236
2424
  exports.applyTableHandlers = applyTableHandlers;
2237
- exports.getCellFromTarget = getCellFromTarget;
2238
- exports.getTableSelectionFromTableElement = getTableSelectionFromTableElement;
2425
+ exports.getDOMCellFromTarget = getDOMCellFromTarget;
2426
+ exports.getTableObserverFromTableElement = getTableObserverFromTableElement;