@lexical/table 0.12.6 → 0.13.0

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