@lexical/table 0.20.1-nightly.20241121.0 → 0.20.1-nightly.20241125.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.
@@ -106,31 +106,24 @@ class TableCellNode extends lexical.ElementNode {
106
106
  return element;
107
107
  }
108
108
  exportDOM(editor) {
109
- const {
110
- element
111
- } = super.exportDOM(editor);
112
- if (element) {
113
- const element_ = element;
114
- element_.style.border = '1px solid black';
109
+ const output = super.exportDOM(editor);
110
+ if (output.element) {
111
+ const element = output.element;
112
+ element.style.border = '1px solid black';
115
113
  if (this.__colSpan > 1) {
116
- element_.colSpan = this.__colSpan;
114
+ element.colSpan = this.__colSpan;
117
115
  }
118
116
  if (this.__rowSpan > 1) {
119
- element_.rowSpan = this.__rowSpan;
117
+ element.rowSpan = this.__rowSpan;
120
118
  }
121
- element_.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;
122
- element_.style.verticalAlign = 'top';
123
- element_.style.textAlign = 'start';
124
- const backgroundColor = this.getBackgroundColor();
125
- if (backgroundColor !== null) {
126
- element_.style.backgroundColor = backgroundColor;
127
- } else if (this.hasHeader()) {
128
- element_.style.backgroundColor = '#f2f3f5';
119
+ element.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;
120
+ element.style.verticalAlign = 'top';
121
+ element.style.textAlign = 'start';
122
+ if (this.__backgroundColor === null && this.hasHeader()) {
123
+ element.style.backgroundColor = '#f2f3f5';
129
124
  }
130
125
  }
131
- return {
132
- element
133
- };
126
+ return output;
134
127
  }
135
128
  exportJSON() {
136
129
  return {
@@ -298,6 +291,18 @@ const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
298
291
 
299
292
  const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
300
293
 
294
+ /**
295
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
296
+ *
297
+ * This source code is licensed under the MIT license found in the
298
+ * LICENSE file in the root directory of this source tree.
299
+ *
300
+ */
301
+
302
+ const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
303
+ const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
304
+ CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
305
+
301
306
  /**
302
307
  * Copyright (c) Meta Platforms, Inc. and affiliates.
303
308
  *
@@ -982,8 +987,8 @@ function $unmergeCell() {
982
987
  cell.setRowSpan(1);
983
988
  }
984
989
  }
985
- function $computeTableMap(grid, cellA, cellB) {
986
- const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(grid, cellA, cellB);
990
+ function $computeTableMap(tableNode, cellA, cellB) {
991
+ const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(tableNode, cellA, cellB);
987
992
  if (!(cellAValue !== null)) {
988
993
  throw Error(`Anchor not found in Table`);
989
994
  }
@@ -992,7 +997,7 @@ function $computeTableMap(grid, cellA, cellB) {
992
997
  }
993
998
  return [tableMap, cellAValue, cellBValue];
994
999
  }
995
- function $computeTableMapSkipCellCheck(grid, cellA, cellB) {
1000
+ function $computeTableMapSkipCellCheck(tableNode, cellA, cellB) {
996
1001
  const tableMap = [];
997
1002
  let cellAValue = null;
998
1003
  let cellBValue = null;
@@ -1003,7 +1008,7 @@ function $computeTableMapSkipCellCheck(grid, cellA, cellB) {
1003
1008
  }
1004
1009
  return row;
1005
1010
  }
1006
- const gridChildren = grid.getChildren();
1011
+ const gridChildren = tableNode.getChildren();
1007
1012
  for (let rowIdx = 0; rowIdx < gridChildren.length; rowIdx++) {
1008
1013
  const row = gridChildren[rowIdx];
1009
1014
  if (!$isTableRowNode(row)) {
@@ -1079,6 +1084,99 @@ function $getNodeTriplet(source) {
1079
1084
  }
1080
1085
  return [cell, row, grid];
1081
1086
  }
1087
+ function $computeTableCellRectSpans(map, boundary) {
1088
+ const {
1089
+ minColumn,
1090
+ maxColumn,
1091
+ minRow,
1092
+ maxRow
1093
+ } = boundary;
1094
+ let topSpan = 1;
1095
+ let leftSpan = 1;
1096
+ let rightSpan = 1;
1097
+ let bottomSpan = 1;
1098
+ const topRow = map[minRow];
1099
+ const bottomRow = map[maxRow];
1100
+ for (let col = minColumn; col <= maxColumn; col++) {
1101
+ topSpan = Math.max(topSpan, topRow[col].cell.__rowSpan);
1102
+ bottomSpan = Math.max(bottomSpan, bottomRow[col].cell.__rowSpan);
1103
+ }
1104
+ for (let row = minRow; row <= maxRow; row++) {
1105
+ leftSpan = Math.max(leftSpan, map[row][minColumn].cell.__colSpan);
1106
+ rightSpan = Math.max(rightSpan, map[row][maxColumn].cell.__colSpan);
1107
+ }
1108
+ return {
1109
+ bottomSpan,
1110
+ leftSpan,
1111
+ rightSpan,
1112
+ topSpan
1113
+ };
1114
+ }
1115
+ function $computeTableCellRectBoundary(map, cellAMap, cellBMap) {
1116
+ let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
1117
+ let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
1118
+ let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
1119
+ let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
1120
+ let exploredMinColumn = minColumn;
1121
+ let exploredMinRow = minRow;
1122
+ let exploredMaxColumn = minColumn;
1123
+ let exploredMaxRow = minRow;
1124
+ function expandBoundary(mapValue) {
1125
+ const {
1126
+ cell,
1127
+ startColumn: cellStartColumn,
1128
+ startRow: cellStartRow
1129
+ } = mapValue;
1130
+ minColumn = Math.min(minColumn, cellStartColumn);
1131
+ minRow = Math.min(minRow, cellStartRow);
1132
+ maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
1133
+ maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
1134
+ }
1135
+ while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
1136
+ if (minColumn < exploredMinColumn) {
1137
+ // Expand on the left
1138
+ const rowDiff = exploredMaxRow - exploredMinRow;
1139
+ const previousColumn = exploredMinColumn - 1;
1140
+ for (let i = 0; i <= rowDiff; i++) {
1141
+ expandBoundary(map[exploredMinRow + i][previousColumn]);
1142
+ }
1143
+ exploredMinColumn = previousColumn;
1144
+ }
1145
+ if (minRow < exploredMinRow) {
1146
+ // Expand on top
1147
+ const columnDiff = exploredMaxColumn - exploredMinColumn;
1148
+ const previousRow = exploredMinRow - 1;
1149
+ for (let i = 0; i <= columnDiff; i++) {
1150
+ expandBoundary(map[previousRow][exploredMinColumn + i]);
1151
+ }
1152
+ exploredMinRow = previousRow;
1153
+ }
1154
+ if (maxColumn > exploredMaxColumn) {
1155
+ // Expand on the right
1156
+ const rowDiff = exploredMaxRow - exploredMinRow;
1157
+ const nextColumn = exploredMaxColumn + 1;
1158
+ for (let i = 0; i <= rowDiff; i++) {
1159
+ expandBoundary(map[exploredMinRow + i][nextColumn]);
1160
+ }
1161
+ exploredMaxColumn = nextColumn;
1162
+ }
1163
+ if (maxRow > exploredMaxRow) {
1164
+ // Expand on the bottom
1165
+ const columnDiff = exploredMaxColumn - exploredMinColumn;
1166
+ const nextRow = exploredMaxRow + 1;
1167
+ for (let i = 0; i <= columnDiff; i++) {
1168
+ expandBoundary(map[nextRow][exploredMinColumn + i]);
1169
+ }
1170
+ exploredMaxRow = nextRow;
1171
+ }
1172
+ }
1173
+ return {
1174
+ maxColumn,
1175
+ maxRow,
1176
+ minColumn,
1177
+ minRow
1178
+ };
1179
+ }
1082
1180
  function $getTableCellNodeRect(tableCellNode) {
1083
1181
  const [cellNode,, gridNode] = $getNodeTriplet(tableCellNode);
1084
1182
  const rows = gridNode.getChildren();
@@ -1133,6 +1231,38 @@ function $getTableCellNodeRect(tableCellNode) {
1133
1231
  *
1134
1232
  */
1135
1233
 
1234
+ function $getCellNodes(tableSelection) {
1235
+ const [[anchorNode, anchorCell, anchorRow, anchorTable], [focusNode, focusCell, focusRow, focusTable]] = ['anchor', 'focus'].map(k => {
1236
+ const node = tableSelection[k].getNode();
1237
+ const cellNode = utils.$findMatchingParent(node, $isTableCellNode);
1238
+ if (!$isTableCellNode(cellNode)) {
1239
+ throw Error(`Expected TableSelection ${k} to be (or a child of) TableCellNode, got key ${node.getKey()} of type ${node.getType()}`);
1240
+ }
1241
+ const rowNode = cellNode.getParent();
1242
+ if (!$isTableRowNode(rowNode)) {
1243
+ throw Error(`Expected TableSelection ${k} cell parent to be a TableRowNode`);
1244
+ }
1245
+ const tableNode = rowNode.getParent();
1246
+ if (!$isTableNode(tableNode)) {
1247
+ throw Error(`Expected TableSelection ${k} row parent to be a TableNode`);
1248
+ }
1249
+ return [node, cellNode, rowNode, tableNode];
1250
+ });
1251
+ // TODO: nested tables may violate this
1252
+ if (!anchorTable.is(focusTable)) {
1253
+ throw Error(`Expected TableSelection anchor and focus to be in the same table`);
1254
+ }
1255
+ return {
1256
+ anchorCell,
1257
+ anchorNode,
1258
+ anchorRow,
1259
+ anchorTable,
1260
+ focusCell,
1261
+ focusNode,
1262
+ focusRow,
1263
+ focusTable
1264
+ };
1265
+ }
1136
1266
  class TableSelection {
1137
1267
  constructor(tableKey, anchor, focus) {
1138
1268
  this.anchor = anchor;
@@ -1147,6 +1277,17 @@ class TableSelection {
1147
1277
  return [this.anchor, this.focus];
1148
1278
  }
1149
1279
 
1280
+ /**
1281
+ * {@link $createTableSelection} unfortunately makes it very easy to create
1282
+ * nonsense selections, so we have a method to see if the selection probably
1283
+ * makes sense.
1284
+ *
1285
+ * @returns true if the TableSelection is (probably) valid
1286
+ */
1287
+ isValid() {
1288
+ return this.tableKey !== 'root' && this.anchor.key !== 'root' && this.anchor.type === 'element' && this.focus.key !== 'root' && this.focus.type === 'element';
1289
+ }
1290
+
1150
1291
  /**
1151
1292
  * Returns whether the Selection is "backwards", meaning the focus
1152
1293
  * logically precedes the anchor in the EditorState.
@@ -1162,20 +1303,18 @@ class TableSelection {
1162
1303
  this._cachedNodes = nodes;
1163
1304
  }
1164
1305
  is(selection) {
1165
- if (!$isTableSelection(selection)) {
1166
- return false;
1167
- }
1168
- return this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
1306
+ return $isTableSelection(selection) && this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
1169
1307
  }
1170
1308
  set(tableKey, anchorCellKey, focusCellKey) {
1171
- this.dirty = true;
1309
+ // note: closure compiler's acorn does not support ||=
1310
+ this.dirty = this.dirty || tableKey !== this.tableKey || anchorCellKey !== this.anchor.key || focusCellKey !== this.focus.key;
1172
1311
  this.tableKey = tableKey;
1173
1312
  this.anchor.key = anchorCellKey;
1174
1313
  this.focus.key = focusCellKey;
1175
1314
  this._cachedNodes = null;
1176
1315
  }
1177
1316
  clone() {
1178
- return new TableSelection(this.tableKey, this.anchor, this.focus);
1317
+ return new TableSelection(this.tableKey, lexical.$createPoint(this.anchor.key, this.anchor.offset, this.anchor.type), lexical.$createPoint(this.focus.key, this.focus.offset, this.focus.type));
1179
1318
  }
1180
1319
  isCollapsed() {
1181
1320
  return false;
@@ -1220,19 +1359,15 @@ class TableSelection {
1220
1359
 
1221
1360
  // TODO Deprecate this method. It's confusing when used with colspan|rowspan
1222
1361
  getShape() {
1223
- const anchorCellNode = lexical.$getNodeByKey(this.anchor.key);
1224
- if (!$isTableCellNode(anchorCellNode)) {
1225
- throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
1226
- }
1227
- const anchorCellNodeRect = $getTableCellNodeRect(anchorCellNode);
1362
+ const {
1363
+ anchorCell,
1364
+ focusCell
1365
+ } = $getCellNodes(this);
1366
+ const anchorCellNodeRect = $getTableCellNodeRect(anchorCell);
1228
1367
  if (!(anchorCellNodeRect !== null)) {
1229
1368
  throw Error(`getCellRect: expected to find AnchorNode`);
1230
1369
  }
1231
- const focusCellNode = lexical.$getNodeByKey(this.focus.key);
1232
- if (!$isTableCellNode(focusCellNode)) {
1233
- throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
1234
- }
1235
- const focusCellNodeRect = $getTableCellNodeRect(focusCellNode);
1370
+ const focusCellNodeRect = $getTableCellNodeRect(focusCell);
1236
1371
  if (!(focusCellNodeRect !== null)) {
1237
1372
  throw Error(`getCellRect: expected to find focusCellNode`);
1238
1373
  }
@@ -1248,29 +1383,18 @@ class TableSelection {
1248
1383
  };
1249
1384
  }
1250
1385
  getNodes() {
1386
+ if (!this.isValid()) {
1387
+ return [];
1388
+ }
1251
1389
  const cachedNodes = this._cachedNodes;
1252
1390
  if (cachedNodes !== null) {
1253
1391
  return cachedNodes;
1254
1392
  }
1255
- const anchorNode = this.anchor.getNode();
1256
- const focusNode = this.focus.getNode();
1257
- const anchorCell = utils.$findMatchingParent(anchorNode, $isTableCellNode);
1258
- // todo replace with triplet
1259
- const focusCell = utils.$findMatchingParent(focusNode, $isTableCellNode);
1260
- if (!$isTableCellNode(anchorCell)) {
1261
- throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
1262
- }
1263
- if (!$isTableCellNode(focusCell)) {
1264
- throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
1265
- }
1266
- const anchorRow = anchorCell.getParent();
1267
- if (!$isTableRowNode(anchorRow)) {
1268
- throw Error(`Expected anchorCell to have a parent TableRowNode`);
1269
- }
1270
- const tableNode = anchorRow.getParent();
1271
- if (!$isTableNode(tableNode)) {
1272
- throw Error(`Expected tableNode to have a parent TableNode`);
1273
- }
1393
+ const {
1394
+ anchorTable: tableNode,
1395
+ anchorCell,
1396
+ focusCell
1397
+ } = $getCellNodes(this);
1274
1398
  const focusCellGrid = focusCell.getParents()[1];
1275
1399
  if (focusCellGrid !== tableNode) {
1276
1400
  if (!tableNode.isParentOf(focusCell)) {
@@ -1296,63 +1420,12 @@ class TableSelection {
1296
1420
  // ability to store a state. Killing TableSelection and moving the logic to the plugin would make
1297
1421
  // this possible.
1298
1422
  const [map, cellAMap, cellBMap] = $computeTableMap(tableNode, anchorCell, focusCell);
1299
- let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
1300
- let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
1301
- let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
1302
- let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
1303
- let exploredMinColumn = minColumn;
1304
- let exploredMinRow = minRow;
1305
- let exploredMaxColumn = minColumn;
1306
- let exploredMaxRow = minRow;
1307
- function expandBoundary(mapValue) {
1308
- const {
1309
- cell,
1310
- startColumn: cellStartColumn,
1311
- startRow: cellStartRow
1312
- } = mapValue;
1313
- minColumn = Math.min(minColumn, cellStartColumn);
1314
- minRow = Math.min(minRow, cellStartRow);
1315
- maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
1316
- maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
1317
- }
1318
- while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
1319
- if (minColumn < exploredMinColumn) {
1320
- // Expand on the left
1321
- const rowDiff = exploredMaxRow - exploredMinRow;
1322
- const previousColumn = exploredMinColumn - 1;
1323
- for (let i = 0; i <= rowDiff; i++) {
1324
- expandBoundary(map[exploredMinRow + i][previousColumn]);
1325
- }
1326
- exploredMinColumn = previousColumn;
1327
- }
1328
- if (minRow < exploredMinRow) {
1329
- // Expand on top
1330
- const columnDiff = exploredMaxColumn - exploredMinColumn;
1331
- const previousRow = exploredMinRow - 1;
1332
- for (let i = 0; i <= columnDiff; i++) {
1333
- expandBoundary(map[previousRow][exploredMinColumn + i]);
1334
- }
1335
- exploredMinRow = previousRow;
1336
- }
1337
- if (maxColumn > exploredMaxColumn) {
1338
- // Expand on the right
1339
- const rowDiff = exploredMaxRow - exploredMinRow;
1340
- const nextColumn = exploredMaxColumn + 1;
1341
- for (let i = 0; i <= rowDiff; i++) {
1342
- expandBoundary(map[exploredMinRow + i][nextColumn]);
1343
- }
1344
- exploredMaxColumn = nextColumn;
1345
- }
1346
- if (maxRow > exploredMaxRow) {
1347
- // Expand on the bottom
1348
- const columnDiff = exploredMaxColumn - exploredMinColumn;
1349
- const nextRow = exploredMaxRow + 1;
1350
- for (let i = 0; i <= columnDiff; i++) {
1351
- expandBoundary(map[nextRow][exploredMinColumn + i]);
1352
- }
1353
- exploredMaxRow = nextRow;
1354
- }
1355
- }
1423
+ const {
1424
+ minColumn,
1425
+ maxColumn,
1426
+ minRow,
1427
+ maxRow
1428
+ } = $computeTableCellRectBoundary(map, cellAMap, cellBMap);
1356
1429
 
1357
1430
  // We use a Map here because merged cells in the grid would otherwise
1358
1431
  // show up multiple times in the nodes array
@@ -1369,12 +1442,13 @@ class TableSelection {
1369
1442
  }
1370
1443
  if (currentRow !== lastRow) {
1371
1444
  nodeMap.set(currentRow.getKey(), currentRow);
1445
+ lastRow = currentRow;
1372
1446
  }
1373
- nodeMap.set(cell.getKey(), cell);
1374
- for (const child of $getChildrenRecursively(cell)) {
1375
- nodeMap.set(child.getKey(), child);
1447
+ if (!nodeMap.has(cell.getKey())) {
1448
+ $visitRecursively(cell, childNode => {
1449
+ nodeMap.set(childNode.getKey(), childNode);
1450
+ });
1376
1451
  }
1377
- lastRow = currentRow;
1378
1452
  }
1379
1453
  }
1380
1454
  const nodes = Array.from(nodeMap.values());
@@ -1399,26 +1473,51 @@ function $isTableSelection(x) {
1399
1473
  return x instanceof TableSelection;
1400
1474
  }
1401
1475
  function $createTableSelection() {
1476
+ // TODO this is a suboptimal design, it doesn't make sense to have
1477
+ // a table selection that isn't associated with a table. This
1478
+ // constructor should have required argumnets and in true we
1479
+ // should check that they point to a table and are element points to
1480
+ // cell nodes of that table.
1402
1481
  const anchor = lexical.$createPoint('root', 0, 'element');
1403
1482
  const focus = lexical.$createPoint('root', 0, 'element');
1404
1483
  return new TableSelection('root', anchor, focus);
1405
1484
  }
1406
- function $getChildrenRecursively(node) {
1407
- const nodes = [];
1408
- const stack = [node];
1409
- while (stack.length > 0) {
1410
- const currentNode = stack.pop();
1411
- if (!(currentNode !== undefined)) {
1412
- throw Error(`Stack.length > 0; can't be undefined`);
1485
+ function $createTableSelectionFrom(tableNode, anchorCell, focusCell) {
1486
+ const tableNodeKey = tableNode.getKey();
1487
+ const anchorCellKey = anchorCell.getKey();
1488
+ const focusCellKey = focusCell.getKey();
1489
+ {
1490
+ if (!tableNode.isAttached()) {
1491
+ throw Error(`$createTableSelectionFrom: tableNode ${tableNodeKey} is not attached`);
1413
1492
  }
1414
- if (lexical.$isElementNode(currentNode)) {
1415
- stack.unshift(...currentNode.getChildren());
1493
+ if (!tableNode.is($findTableNode(anchorCell))) {
1494
+ throw Error(`$createTableSelectionFrom: anchorCell ${anchorCellKey} is not in table ${tableNodeKey}`);
1416
1495
  }
1417
- if (currentNode !== node) {
1418
- nodes.push(currentNode);
1496
+ if (!tableNode.is($findTableNode(focusCell))) {
1497
+ throw Error(`$createTableSelectionFrom: focusCell ${focusCellKey} is not in table ${tableNodeKey}`);
1498
+ } // TODO: Check for rectangular grid
1499
+ }
1500
+ const prevSelection = lexical.$getSelection();
1501
+ const nextSelection = $isTableSelection(prevSelection) ? prevSelection.clone() : $createTableSelection();
1502
+ nextSelection.set(tableNode.getKey(), anchorCell.getKey(), focusCell.getKey());
1503
+ return nextSelection;
1504
+ }
1505
+
1506
+ /**
1507
+ * Depth first visitor
1508
+ * @param node The starting node
1509
+ * @param $visit The function to call for each node. If the function returns false, then children of this node will not be explored
1510
+ */
1511
+ function $visitRecursively(node, $visit) {
1512
+ const stack = [[node]];
1513
+ for (let currentArray = stack.at(-1); currentArray !== undefined && stack.length > 0; currentArray = stack.at(-1)) {
1514
+ const currentNode = currentArray.pop();
1515
+ if (currentNode === undefined) {
1516
+ stack.pop();
1517
+ } else if ($visit(currentNode) !== false && lexical.$isElementNode(currentNode)) {
1518
+ stack.push(currentNode.getChildren());
1419
1519
  }
1420
1520
  }
1421
- return nodes;
1422
1521
  }
1423
1522
 
1424
1523
  /**
@@ -1470,6 +1569,7 @@ class TableObserver {
1470
1569
  this.listenerOptions = {
1471
1570
  signal: this.abortController.signal
1472
1571
  };
1572
+ this.nextFocus = null;
1473
1573
  this.trackTable();
1474
1574
  }
1475
1575
  getTable() {
@@ -1523,7 +1623,7 @@ class TableObserver {
1523
1623
  editor: this.editor
1524
1624
  });
1525
1625
  }
1526
- clearHighlight() {
1626
+ $clearHighlight() {
1527
1627
  const editor = this.editor;
1528
1628
  this.isHighlightingCells = false;
1529
1629
  this.anchorX = -1;
@@ -1536,55 +1636,47 @@ class TableObserver {
1536
1636
  this.anchorCell = null;
1537
1637
  this.focusCell = null;
1538
1638
  this.hasHijackedSelectionStyles = false;
1539
- this.enableHighlightStyle();
1540
- editor.update(() => {
1541
- const {
1542
- tableNode,
1543
- tableElement
1544
- } = this.$lookup();
1545
- const grid = getTable(tableNode, tableElement);
1546
- $updateDOMForSelection(editor, grid, null);
1639
+ this.$enableHighlightStyle();
1640
+ const {
1641
+ tableNode,
1642
+ tableElement
1643
+ } = this.$lookup();
1644
+ const grid = getTable(tableNode, tableElement);
1645
+ $updateDOMForSelection(editor, grid, null);
1646
+ if (lexical.$getSelection() !== null) {
1547
1647
  lexical.$setSelection(null);
1548
1648
  editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1549
- });
1649
+ }
1550
1650
  }
1551
- enableHighlightStyle() {
1651
+ $enableHighlightStyle() {
1552
1652
  const editor = this.editor;
1553
- editor.getEditorState().read(() => {
1554
- const {
1555
- tableElement
1556
- } = this.$lookup();
1557
- utils.removeClassNamesFromElement(tableElement, editor._config.theme.tableSelection);
1558
- tableElement.classList.remove('disable-selection');
1559
- this.hasHijackedSelectionStyles = false;
1560
- }, {
1561
- editor
1562
- });
1653
+ const {
1654
+ tableElement
1655
+ } = this.$lookup();
1656
+ utils.removeClassNamesFromElement(tableElement, editor._config.theme.tableSelection);
1657
+ tableElement.classList.remove('disable-selection');
1658
+ this.hasHijackedSelectionStyles = false;
1563
1659
  }
1564
- disableHighlightStyle() {
1565
- const editor = this.editor;
1566
- editor.getEditorState().read(() => {
1567
- const {
1568
- tableElement
1569
- } = this.$lookup();
1570
- utils.addClassNamesToElement(tableElement, editor._config.theme.tableSelection);
1571
- this.hasHijackedSelectionStyles = true;
1572
- }, {
1573
- editor
1574
- });
1660
+ $disableHighlightStyle() {
1661
+ const {
1662
+ tableElement
1663
+ } = this.$lookup();
1664
+ utils.addClassNamesToElement(tableElement, this.editor._config.theme.tableSelection);
1665
+ this.hasHijackedSelectionStyles = true;
1575
1666
  }
1576
- updateTableTableSelection(selection) {
1577
- if (selection !== null && selection.tableKey === this.tableNodeKey) {
1667
+ $updateTableTableSelection(selection) {
1668
+ if (selection !== null) {
1669
+ if (!(selection.tableKey === this.tableNodeKey)) {
1670
+ throw Error(`TableObserver.$updateTableTableSelection: selection.tableKey !== this.tableNodeKey ('${selection.tableKey}' !== '${this.tableNodeKey}')`);
1671
+ }
1578
1672
  const editor = this.editor;
1579
1673
  this.tableSelection = selection;
1580
1674
  this.isHighlightingCells = true;
1581
- this.disableHighlightStyle();
1675
+ this.$disableHighlightStyle();
1676
+ this.updateDOMSelection();
1582
1677
  $updateDOMForSelection(editor, this.table, this.tableSelection);
1583
- } else if (selection == null) {
1584
- this.clearHighlight();
1585
1678
  } else {
1586
- this.tableNodeKey = selection.tableKey;
1587
- this.updateTableTableSelection(selection);
1679
+ this.$clearHighlight();
1588
1680
  }
1589
1681
  }
1590
1682
 
@@ -1610,120 +1702,165 @@ class TableObserver {
1610
1702
  }
1611
1703
  return false;
1612
1704
  }
1613
- setFocusCellForSelection(cell, ignoreStart = false) {
1614
- const editor = this.editor;
1615
- editor.update(() => {
1616
- const {
1617
- tableNode
1618
- } = this.$lookup();
1619
- const cellX = cell.x;
1620
- const cellY = cell.y;
1621
- this.focusCell = cell;
1622
- if (this.anchorCell !== null) {
1623
- const domSelection = getDOMSelection(editor._window);
1624
- // Collapse the selection
1625
- if (domSelection) {
1626
- domSelection.setBaseAndExtent(this.anchorCell.elem, 0, this.focusCell.elem, 0);
1627
- }
1628
- }
1629
- if (!this.isHighlightingCells && (this.anchorX !== cellX || this.anchorY !== cellY || ignoreStart)) {
1630
- this.isHighlightingCells = true;
1631
- this.disableHighlightStyle();
1632
- } else if (cellX === this.focusX && cellY === this.focusY) {
1633
- return;
1705
+
1706
+ /**
1707
+ * @internal
1708
+ * When handling mousemove events we track what the focus cell should be, but
1709
+ * the DOM selection may end up somewhere else entirely. We don't have an elegant
1710
+ * way to handle this after the DOM selection has been resolved in a
1711
+ * SELECTION_CHANGE_COMMAND callback.
1712
+ */
1713
+ setNextFocus(nextFocus) {
1714
+ this.nextFocus = nextFocus;
1715
+ }
1716
+
1717
+ /** @internal */
1718
+ getAndClearNextFocus() {
1719
+ const {
1720
+ nextFocus
1721
+ } = this;
1722
+ if (nextFocus !== null) {
1723
+ this.nextFocus = null;
1724
+ }
1725
+ return nextFocus;
1726
+ }
1727
+
1728
+ /** @internal */
1729
+ updateDOMSelection() {
1730
+ if (this.anchorCell !== null && this.focusCell !== null) {
1731
+ const domSelection = lexical.getDOMSelection(this.editor._window);
1732
+ // We are not using a native selection for tables, and if we
1733
+ // set one then the reconciler will undo it.
1734
+ // TODO - it would make sense to have one so that native
1735
+ // copy/paste worked. Right now we have to emulate with
1736
+ // keyboard events but it won't fire if trigged from the menu
1737
+ if (domSelection && domSelection.rangeCount > 0) {
1738
+ domSelection.removeAllRanges();
1634
1739
  }
1635
- this.focusX = cellX;
1636
- this.focusY = cellY;
1637
- if (this.isHighlightingCells) {
1638
- const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1639
- if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode) && tableNode.is($findTableNode(focusTableCellNode))) {
1640
- const focusNodeKey = focusTableCellNode.getKey();
1641
- this.tableSelection = this.tableSelection.clone() || $createTableSelection();
1642
- this.focusCellNodeKey = focusNodeKey;
1643
- this.tableSelection.set(this.tableNodeKey, this.anchorCellNodeKey, this.focusCellNodeKey);
1644
- lexical.$setSelection(this.tableSelection);
1645
- editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1646
- $updateDOMForSelection(editor, this.table, this.tableSelection);
1647
- }
1740
+ }
1741
+ }
1742
+ $setFocusCellForSelection(cell, ignoreStart = false) {
1743
+ const editor = this.editor;
1744
+ const {
1745
+ tableNode
1746
+ } = this.$lookup();
1747
+ const cellX = cell.x;
1748
+ const cellY = cell.y;
1749
+ this.focusCell = cell;
1750
+ if (!this.isHighlightingCells && (this.anchorX !== cellX || this.anchorY !== cellY || ignoreStart)) {
1751
+ this.isHighlightingCells = true;
1752
+ this.$disableHighlightStyle();
1753
+ } else if (cellX === this.focusX && cellY === this.focusY) {
1754
+ return false;
1755
+ }
1756
+ this.focusX = cellX;
1757
+ this.focusY = cellY;
1758
+ if (this.isHighlightingCells) {
1759
+ const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1760
+ if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode) && tableNode.is($findTableNode(focusTableCellNode))) {
1761
+ this.focusCellNodeKey = focusTableCellNode.getKey();
1762
+ this.tableSelection = $createTableSelectionFrom(tableNode, this.$getAnchorTableCellOrThrow(), focusTableCellNode);
1763
+ lexical.$setSelection(this.tableSelection);
1764
+ editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1765
+ $updateDOMForSelection(editor, this.table, this.tableSelection);
1766
+ return true;
1648
1767
  }
1649
- });
1768
+ }
1769
+ return false;
1770
+ }
1771
+ $getAnchorTableCell() {
1772
+ return this.anchorCellNodeKey ? lexical.$getNodeByKey(this.anchorCellNodeKey) : null;
1773
+ }
1774
+ $getAnchorTableCellOrThrow() {
1775
+ const anchorTableCell = this.$getAnchorTableCell();
1776
+ if (!(anchorTableCell !== null)) {
1777
+ throw Error(`TableObserver anchorTableCell is null`);
1778
+ }
1779
+ return anchorTableCell;
1780
+ }
1781
+ $getFocusTableCell() {
1782
+ return this.focusCellNodeKey ? lexical.$getNodeByKey(this.focusCellNodeKey) : null;
1783
+ }
1784
+ $getFocusTableCellOrThrow() {
1785
+ const focusTableCell = this.$getFocusTableCell();
1786
+ if (!(focusTableCell !== null)) {
1787
+ throw Error(`TableObserver focusTableCell is null`);
1788
+ }
1789
+ return focusTableCell;
1650
1790
  }
1651
- setAnchorCellForSelection(cell) {
1791
+ $setAnchorCellForSelection(cell) {
1652
1792
  this.isHighlightingCells = false;
1653
1793
  this.anchorCell = cell;
1654
1794
  this.anchorX = cell.x;
1655
1795
  this.anchorY = cell.y;
1656
- this.editor.update(() => {
1657
- const anchorTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1658
- if ($isTableCellNode(anchorTableCellNode)) {
1659
- const anchorNodeKey = anchorTableCellNode.getKey();
1660
- this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
1661
- this.anchorCellNodeKey = anchorNodeKey;
1662
- }
1663
- });
1796
+ const anchorTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1797
+ if ($isTableCellNode(anchorTableCellNode)) {
1798
+ const anchorNodeKey = anchorTableCellNode.getKey();
1799
+ this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
1800
+ this.anchorCellNodeKey = anchorNodeKey;
1801
+ }
1664
1802
  }
1665
- formatCells(type) {
1666
- this.editor.update(() => {
1667
- const selection = lexical.$getSelection();
1668
- if (!$isTableSelection(selection)) {
1669
- {
1670
- throw Error(`Expected grid selection`);
1671
- }
1672
- }
1673
- const formatSelection = lexical.$createRangeSelection();
1674
- const anchor = formatSelection.anchor;
1675
- const focus = formatSelection.focus;
1676
- const cellNodes = selection.getNodes().filter($isTableCellNode);
1677
- const paragraph = cellNodes[0].getFirstChild();
1678
- const alignFormatWith = lexical.$isParagraphNode(paragraph) ? paragraph.getFormatFlags(type, null) : null;
1679
- cellNodes.forEach(cellNode => {
1680
- anchor.set(cellNode.getKey(), 0, 'element');
1681
- focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
1682
- formatSelection.formatText(type, alignFormatWith);
1683
- });
1684
- lexical.$setSelection(selection);
1685
- this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1803
+ $formatCells(type) {
1804
+ const selection = lexical.$getSelection();
1805
+ if (!$isTableSelection(selection)) {
1806
+ throw Error(`Expected Table selection`);
1807
+ }
1808
+ const formatSelection = lexical.$createRangeSelection();
1809
+ const anchor = formatSelection.anchor;
1810
+ const focus = formatSelection.focus;
1811
+ const cellNodes = selection.getNodes().filter($isTableCellNode);
1812
+ if (!(cellNodes.length > 0)) {
1813
+ throw Error(`No table cells present`);
1814
+ }
1815
+ const paragraph = cellNodes[0].getFirstChild();
1816
+ const alignFormatWith = lexical.$isParagraphNode(paragraph) ? paragraph.getFormatFlags(type, null) : null;
1817
+ cellNodes.forEach(cellNode => {
1818
+ anchor.set(cellNode.getKey(), 0, 'element');
1819
+ focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
1820
+ formatSelection.formatText(type, alignFormatWith);
1686
1821
  });
1822
+ lexical.$setSelection(selection);
1823
+ this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1687
1824
  }
1688
- clearText() {
1689
- const editor = this.editor;
1690
- editor.update(() => {
1691
- const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
1692
- if (!$isTableNode(tableNode)) {
1693
- throw new Error('Expected TableNode.');
1694
- }
1695
- const selection = lexical.$getSelection();
1696
- if (!$isTableSelection(selection)) {
1697
- {
1698
- throw Error(`Expected grid selection`);
1699
- }
1825
+ $clearText() {
1826
+ const {
1827
+ editor
1828
+ } = this;
1829
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
1830
+ if (!$isTableNode(tableNode)) {
1831
+ throw new Error('Expected TableNode.');
1832
+ }
1833
+ const selection = lexical.$getSelection();
1834
+ if (!$isTableSelection(selection)) {
1835
+ {
1836
+ throw Error(`Expected grid selection`);
1700
1837
  }
1701
- const selectedNodes = selection.getNodes().filter($isTableCellNode);
1702
- if (selectedNodes.length === this.table.columns * this.table.rows) {
1703
- tableNode.selectPrevious();
1704
- // Delete entire table
1705
- tableNode.remove();
1706
- const rootNode = lexical.$getRoot();
1707
- rootNode.selectStart();
1708
- return;
1838
+ }
1839
+ const selectedNodes = selection.getNodes().filter($isTableCellNode);
1840
+ if (selectedNodes.length === this.table.columns * this.table.rows) {
1841
+ tableNode.selectPrevious();
1842
+ // Delete entire table
1843
+ tableNode.remove();
1844
+ const rootNode = lexical.$getRoot();
1845
+ rootNode.selectStart();
1846
+ return;
1847
+ }
1848
+ selectedNodes.forEach(cellNode => {
1849
+ if (lexical.$isElementNode(cellNode)) {
1850
+ const paragraphNode = lexical.$createParagraphNode();
1851
+ const textNode = lexical.$createTextNode();
1852
+ paragraphNode.append(textNode);
1853
+ cellNode.append(paragraphNode);
1854
+ cellNode.getChildren().forEach(child => {
1855
+ if (child !== paragraphNode) {
1856
+ child.remove();
1857
+ }
1858
+ });
1709
1859
  }
1710
- selectedNodes.forEach(cellNode => {
1711
- if (lexical.$isElementNode(cellNode)) {
1712
- const paragraphNode = lexical.$createParagraphNode();
1713
- const textNode = lexical.$createTextNode();
1714
- paragraphNode.append(textNode);
1715
- cellNode.append(paragraphNode);
1716
- cellNode.getChildren().forEach(child => {
1717
- if (child !== paragraphNode) {
1718
- child.remove();
1719
- }
1720
- });
1721
- }
1722
- });
1723
- $updateDOMForSelection(editor, this.table, null);
1724
- lexical.$setSelection(null);
1725
- editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1726
1860
  });
1861
+ $updateDOMForSelection(editor, this.table, null);
1862
+ lexical.$setSelection(null);
1863
+ editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1727
1864
  }
1728
1865
  }
1729
1866
 
@@ -1736,7 +1873,6 @@ class TableObserver {
1736
1873
  */
1737
1874
 
1738
1875
  const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
1739
- const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
1740
1876
  const isMouseDownOnEvent = event => {
1741
1877
  return (event.buttons & 1) === 1;
1742
1878
  };
@@ -1750,64 +1886,103 @@ function getTableElement(tableNode, dom) {
1750
1886
  }
1751
1887
  return element;
1752
1888
  }
1889
+ function getEditorWindow(editor) {
1890
+ return editor._window;
1891
+ }
1892
+ function $findParentTableCellNodeInTable(tableNode, node) {
1893
+ for (let currentNode = node, lastTableCellNode = null; currentNode !== null; currentNode = currentNode.getParent()) {
1894
+ if (tableNode.is(currentNode)) {
1895
+ return lastTableCellNode;
1896
+ } else if ($isTableCellNode(currentNode)) {
1897
+ lastTableCellNode = currentNode;
1898
+ }
1899
+ }
1900
+ return null;
1901
+ }
1902
+ const ARROW_KEY_COMMANDS_WITH_DIRECTION = [[lexical.KEY_ARROW_DOWN_COMMAND, 'down'], [lexical.KEY_ARROW_UP_COMMAND, 'up'], [lexical.KEY_ARROW_LEFT_COMMAND, 'backward'], [lexical.KEY_ARROW_RIGHT_COMMAND, 'forward']];
1903
+ const DELETE_TEXT_COMMANDS = [lexical.DELETE_WORD_COMMAND, lexical.DELETE_LINE_COMMAND, lexical.DELETE_CHARACTER_COMMAND];
1904
+ const DELETE_KEY_COMMANDS = [lexical.KEY_BACKSPACE_COMMAND, lexical.KEY_DELETE_COMMAND];
1753
1905
  function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1754
1906
  const rootElement = editor.getRootElement();
1755
- if (rootElement === null) {
1756
- throw new Error('No root element.');
1907
+ const editorWindow = getEditorWindow(editor);
1908
+ if (!(rootElement !== null && editorWindow !== null)) {
1909
+ throw Error(`applyTableHandlers: editor has no root element set`);
1757
1910
  }
1758
1911
  const tableObserver = new TableObserver(editor, tableNode.getKey());
1759
- const editorWindow = editor._window || window;
1760
1912
  const tableElement = getTableElement(tableNode, element);
1761
1913
  attachTableObserverToTableElement(tableElement, tableObserver);
1762
1914
  tableObserver.listenersToRemove.add(() => detatchTableObserverFromTableElement(tableElement, tableObserver));
1763
1915
  const createMouseHandlers = () => {
1916
+ if (tableObserver.isSelecting) {
1917
+ return;
1918
+ }
1764
1919
  const onMouseUp = () => {
1765
1920
  tableObserver.isSelecting = false;
1766
1921
  editorWindow.removeEventListener('mouseup', onMouseUp);
1767
1922
  editorWindow.removeEventListener('mousemove', onMouseMove);
1768
1923
  };
1769
1924
  const onMouseMove = moveEvent => {
1770
- // delaying mousemove handler to allow selectionchange handler from LexicalEvents.ts to be executed first
1771
- setTimeout(() => {
1772
- if (!isMouseDownOnEvent(moveEvent) && tableObserver.isSelecting) {
1773
- tableObserver.isSelecting = false;
1774
- editorWindow.removeEventListener('mouseup', onMouseUp);
1775
- editorWindow.removeEventListener('mousemove', onMouseMove);
1776
- return;
1777
- }
1778
- const focusCell = getDOMCellFromTarget(moveEvent.target);
1779
- if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
1780
- moveEvent.preventDefault();
1781
- tableObserver.setFocusCellForSelection(focusCell);
1782
- }
1783
- }, 0);
1784
- };
1785
- return {
1786
- onMouseMove,
1787
- onMouseUp
1788
- };
1789
- };
1790
- const onMouseDown = event => {
1791
- setTimeout(() => {
1792
- if (event.button !== 0) {
1925
+ if (!isMouseDownOnEvent(moveEvent) && tableObserver.isSelecting) {
1926
+ tableObserver.isSelecting = false;
1927
+ editorWindow.removeEventListener('mouseup', onMouseUp);
1928
+ editorWindow.removeEventListener('mousemove', onMouseMove);
1793
1929
  return;
1794
1930
  }
1795
- if (!editorWindow) {
1796
- return;
1931
+ const override = !tableElement.contains(moveEvent.target);
1932
+ let focusCell = null;
1933
+ if (!override) {
1934
+ focusCell = getDOMCellFromTarget(moveEvent.target);
1935
+ } else {
1936
+ for (const el of document.elementsFromPoint(moveEvent.clientX, moveEvent.clientY)) {
1937
+ focusCell = tableElement.contains(el) ? getDOMCellFromTarget(el) : null;
1938
+ if (focusCell) {
1939
+ break;
1940
+ }
1941
+ }
1797
1942
  }
1798
- const anchorCell = getDOMCellFromTarget(event.target);
1799
- if (anchorCell !== null) {
1800
- stopEvent(event);
1801
- tableObserver.setAnchorCellForSelection(anchorCell);
1943
+ if (focusCell && (tableObserver.focusCell === null || focusCell.elem !== tableObserver.focusCell.elem)) {
1944
+ tableObserver.setNextFocus({
1945
+ focusCell,
1946
+ override
1947
+ });
1948
+ editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND, undefined);
1802
1949
  }
1803
- const {
1804
- onMouseUp,
1805
- onMouseMove
1806
- } = createMouseHandlers();
1807
- tableObserver.isSelecting = true;
1808
- editorWindow.addEventListener('mouseup', onMouseUp, tableObserver.listenerOptions);
1809
- editorWindow.addEventListener('mousemove', onMouseMove, tableObserver.listenerOptions);
1810
- }, 0);
1950
+ };
1951
+ tableObserver.isSelecting = true;
1952
+ editorWindow.addEventListener('mouseup', onMouseUp, tableObserver.listenerOptions);
1953
+ editorWindow.addEventListener('mousemove', onMouseMove, tableObserver.listenerOptions);
1954
+ };
1955
+ const onMouseDown = event => {
1956
+ if (event.button !== 0) {
1957
+ return;
1958
+ }
1959
+ if (!editorWindow) {
1960
+ return;
1961
+ }
1962
+ const targetCell = getDOMCellFromTarget(event.target);
1963
+ if (targetCell !== null) {
1964
+ editor.update(() => {
1965
+ const prevSelection = lexical.$getPreviousSelection();
1966
+ // We can't trust Firefox to do the right thing with the selection and
1967
+ // we don't have a proper state machine to do this "correctly" but
1968
+ // if we go ahead and make the table selection now it will work
1969
+ if (IS_FIREFOX && event.shiftKey && $isSelectionInTable(prevSelection, tableNode) && (lexical.$isRangeSelection(prevSelection) || $isTableSelection(prevSelection))) {
1970
+ const prevAnchorNode = prevSelection.anchor.getNode();
1971
+ const prevAnchorCell = $findParentTableCellNodeInTable(tableNode, prevSelection.anchor.getNode());
1972
+ if (prevAnchorCell) {
1973
+ tableObserver.$setAnchorCellForSelection($getObserverCellFromCellNodeOrThrow(tableObserver, prevAnchorCell));
1974
+ tableObserver.$setFocusCellForSelection(targetCell);
1975
+ stopEvent(event);
1976
+ } else {
1977
+ const newSelection = tableNode.isBefore(prevAnchorNode) ? tableNode.selectStart() : tableNode.selectEnd();
1978
+ newSelection.anchor.set(prevSelection.anchor.key, prevSelection.anchor.offset, prevSelection.anchor.type);
1979
+ }
1980
+ } else {
1981
+ tableObserver.$setAnchorCellForSelection(targetCell);
1982
+ }
1983
+ });
1984
+ }
1985
+ createMouseHandlers();
1811
1986
  };
1812
1987
  tableElement.addEventListener('mousedown', onMouseDown, tableObserver.listenerOptions);
1813
1988
 
@@ -1820,20 +1995,19 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1820
1995
  const selection = lexical.$getSelection();
1821
1996
  const target = event.target;
1822
1997
  if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey && rootElement.contains(target)) {
1823
- tableObserver.clearHighlight();
1998
+ tableObserver.$clearHighlight();
1824
1999
  }
1825
2000
  });
1826
2001
  };
1827
2002
  editorWindow.addEventListener('mousedown', mouseDownCallback, tableObserver.listenerOptions);
1828
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => $handleArrowKey(editor, event, 'down', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1829
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => $handleArrowKey(editor, event, 'up', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1830
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, event => $handleArrowKey(editor, event, 'backward', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
1831
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_RIGHT_COMMAND, event => $handleArrowKey(editor, event, 'forward', tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
2003
+ for (const [command, direction] of ARROW_KEY_COMMANDS_WITH_DIRECTION) {
2004
+ tableObserver.listenersToRemove.add(editor.registerCommand(command, event => $handleArrowKey(editor, event, direction, tableNode, tableObserver), lexical.COMMAND_PRIORITY_HIGH));
2005
+ }
1832
2006
  tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, event => {
1833
2007
  const selection = lexical.$getSelection();
1834
2008
  if ($isTableSelection(selection)) {
1835
- const focusCellNode = utils.$findMatchingParent(selection.focus.getNode(), $isTableCellNode);
1836
- if ($isTableCellNode(focusCellNode)) {
2009
+ const focusCellNode = $findParentTableCellNodeInTable(tableNode, selection.focus.getNode());
2010
+ if (focusCellNode !== null) {
1837
2011
  stopEvent(event);
1838
2012
  focusCellNode.selectEnd();
1839
2013
  return true;
@@ -1847,10 +2021,10 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1847
2021
  return false;
1848
2022
  }
1849
2023
  if ($isTableSelection(selection)) {
1850
- tableObserver.clearText();
2024
+ tableObserver.$clearText();
1851
2025
  return true;
1852
2026
  } else if (lexical.$isRangeSelection(selection)) {
1853
- const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
2027
+ const tableCellNode = $findParentTableCellNodeInTable(tableNode, selection.anchor.getNode());
1854
2028
  if (!$isTableCellNode(tableCellNode)) {
1855
2029
  return false;
1856
2030
  }
@@ -1860,7 +2034,7 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1860
2034
  const isFocusInside = tableNode.isParentOf(focusNode);
1861
2035
  const selectionContainsPartialTable = isAnchorInside && !isFocusInside || isFocusInside && !isAnchorInside;
1862
2036
  if (selectionContainsPartialTable) {
1863
- tableObserver.clearText();
2037
+ tableObserver.$clearText();
1864
2038
  return true;
1865
2039
  }
1866
2040
  const nearestElementNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
@@ -1875,9 +2049,9 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1875
2049
  }
1876
2050
  return false;
1877
2051
  };
1878
- [lexical.DELETE_WORD_COMMAND, lexical.DELETE_LINE_COMMAND, lexical.DELETE_CHARACTER_COMMAND].forEach(command => {
2052
+ for (const command of DELETE_TEXT_COMMANDS) {
1879
2053
  tableObserver.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), lexical.COMMAND_PRIORITY_CRITICAL));
1880
- });
2054
+ }
1881
2055
  const $deleteCellHandler = event => {
1882
2056
  const selection = lexical.$getSelection();
1883
2057
  if (!($isTableSelection(selection) || lexical.$isRangeSelection(selection))) {
@@ -1910,13 +2084,14 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1910
2084
  event.preventDefault();
1911
2085
  event.stopPropagation();
1912
2086
  }
1913
- tableObserver.clearText();
2087
+ tableObserver.$clearText();
1914
2088
  return true;
1915
2089
  }
1916
2090
  return false;
1917
2091
  };
1918
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, $deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1919
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_DELETE_COMMAND, $deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
2092
+ for (const command of DELETE_KEY_COMMANDS) {
2093
+ tableObserver.listenersToRemove.add(editor.registerCommand(command, $deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
2094
+ }
1920
2095
  tableObserver.listenersToRemove.add(editor.registerCommand(lexical.CUT_COMMAND, event => {
1921
2096
  const selection = lexical.$getSelection();
1922
2097
  if (selection) {
@@ -1941,7 +2116,7 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1941
2116
  return false;
1942
2117
  }
1943
2118
  if ($isTableSelection(selection)) {
1944
- tableObserver.formatCells(payload);
2119
+ tableObserver.$formatCells(payload);
1945
2120
  return true;
1946
2121
  } else if (lexical.$isRangeSelection(selection)) {
1947
2122
  const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
@@ -1962,13 +2137,18 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1962
2137
  return false;
1963
2138
  }
1964
2139
  const [tableMap, anchorCell, focusCell] = $computeTableMap(tableNode, anchorNode, focusNode);
1965
- const maxRow = Math.max(anchorCell.startRow, focusCell.startRow);
1966
- const maxColumn = Math.max(anchorCell.startColumn, focusCell.startColumn);
2140
+ const maxRow = Math.max(anchorCell.startRow + anchorCell.cell.__rowSpan - 1, focusCell.startRow + focusCell.cell.__rowSpan - 1);
2141
+ const maxColumn = Math.max(anchorCell.startColumn + anchorCell.cell.__colSpan - 1, focusCell.startColumn + focusCell.cell.__colSpan - 1);
1967
2142
  const minRow = Math.min(anchorCell.startRow, focusCell.startRow);
1968
2143
  const minColumn = Math.min(anchorCell.startColumn, focusCell.startColumn);
2144
+ const visited = new Set();
1969
2145
  for (let i = minRow; i <= maxRow; i++) {
1970
2146
  for (let j = minColumn; j <= maxColumn; j++) {
1971
2147
  const cell = tableMap[i][j].cell;
2148
+ if (visited.has(cell)) {
2149
+ continue;
2150
+ }
2151
+ visited.add(cell);
1972
2152
  cell.setFormat(formatType);
1973
2153
  const cellChildren = cell.getChildren();
1974
2154
  for (let k = 0; k < cellChildren.length; k++) {
@@ -1987,7 +2167,7 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
1987
2167
  return false;
1988
2168
  }
1989
2169
  if ($isTableSelection(selection)) {
1990
- tableObserver.clearHighlight();
2170
+ tableObserver.$clearHighlight();
1991
2171
  return false;
1992
2172
  } else if (lexical.$isRangeSelection(selection)) {
1993
2173
  const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
@@ -2023,10 +2203,6 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2023
2203
  tableObserver.listenersToRemove.add(editor.registerCommand(lexical.FOCUS_COMMAND, payload => {
2024
2204
  return tableNode.isSelected();
2025
2205
  }, lexical.COMMAND_PRIORITY_HIGH));
2026
- function getObserverCellFromCellNode(tableCellNode) {
2027
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
2028
- return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
2029
- }
2030
2206
  tableObserver.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, selectionPayload => {
2031
2207
  const {
2032
2208
  nodes,
@@ -2101,10 +2277,30 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2101
2277
  tableObserver.listenersToRemove.add(editor.registerCommand(lexical.SELECTION_CHANGE_COMMAND, () => {
2102
2278
  const selection = lexical.$getSelection();
2103
2279
  const prevSelection = lexical.$getPreviousSelection();
2280
+ const nextFocus = tableObserver.getAndClearNextFocus();
2281
+ if (nextFocus !== null) {
2282
+ const {
2283
+ focusCell
2284
+ } = nextFocus;
2285
+ if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
2286
+ if (focusCell.x === tableObserver.focusX && focusCell.y === tableObserver.focusY) {
2287
+ // The selection is already the correct table selection
2288
+ return false;
2289
+ } else {
2290
+ tableObserver.$setFocusCellForSelection(focusCell);
2291
+ return true;
2292
+ }
2293
+ } else if (focusCell !== tableObserver.anchorCell && $isSelectionInTable(selection, tableNode)) {
2294
+ // The selection has crossed cells
2295
+ tableObserver.$setFocusCellForSelection(focusCell);
2296
+ return true;
2297
+ }
2298
+ }
2299
+ const shouldCheckSelection = tableObserver.getAndClearShouldCheckSelection();
2104
2300
  // If they pressed the down arrow with the selection outside of the
2105
2301
  // table, and then the selection ends up in the table but not in the
2106
2302
  // first cell, then move the selection to the first cell.
2107
- if (tableObserver.getAndClearShouldCheckSelection() && lexical.$isRangeSelection(prevSelection) && lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
2303
+ if (shouldCheckSelection && lexical.$isRangeSelection(prevSelection) && lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
2108
2304
  const anchor = selection.anchor.getNode();
2109
2305
  const firstRow = tableNode.getFirstChild();
2110
2306
  const anchorCell = $findCellNode(anchor);
@@ -2130,10 +2326,10 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2130
2326
  const focusCellNode = $findCellNode(focusNode);
2131
2327
  const isAnchorInside = !!(anchorCellNode && tableNode.is($findTableNode(anchorCellNode)));
2132
2328
  const isFocusInside = !!(focusCellNode && tableNode.is($findTableNode(focusCellNode)));
2133
- const isPartialyWithinTable = isAnchorInside !== isFocusInside;
2329
+ const isPartiallyWithinTable = isAnchorInside !== isFocusInside;
2134
2330
  const isWithinTable = isAnchorInside && isFocusInside;
2135
2331
  const isBackward = selection.isBackward();
2136
- if (isPartialyWithinTable) {
2332
+ if (isPartiallyWithinTable) {
2137
2333
  const newSelection = selection.clone();
2138
2334
  if (isFocusInside) {
2139
2335
  const [tableMap] = $computeTableMap(tableNode, focusCellNode, focusCellNode);
@@ -2156,26 +2352,15 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2156
2352
  $addHighlightStyleToTable(editor, tableObserver);
2157
2353
  } else if (isWithinTable) {
2158
2354
  // Handle case when selection spans across multiple cells but still
2159
- // has range selection, then we convert it into grid selection
2355
+ // has range selection, then we convert it into table selection
2160
2356
  if (!anchorCellNode.is(focusCellNode)) {
2161
- tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
2162
- tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
2163
- if (!tableObserver.isSelecting) {
2164
- setTimeout(() => {
2165
- const {
2166
- onMouseUp,
2167
- onMouseMove
2168
- } = createMouseHandlers();
2169
- tableObserver.isSelecting = true;
2170
- editorWindow.addEventListener('mouseup', onMouseUp);
2171
- editorWindow.addEventListener('mousemove', onMouseMove);
2172
- }, 0);
2173
- }
2357
+ tableObserver.$setAnchorCellForSelection($getObserverCellFromCellNodeOrThrow(tableObserver, anchorCellNode));
2358
+ tableObserver.$setFocusCellForSelection($getObserverCellFromCellNodeOrThrow(tableObserver, focusCellNode), true);
2174
2359
  }
2175
2360
  }
2176
2361
  } else if (selection && $isTableSelection(selection) && selection.is(prevSelection) && selection.tableKey === tableNode.getKey()) {
2177
2362
  // if selection goes outside of the table we need to change it to Range selection
2178
- const domSelection = getDOMSelection(editor._window);
2363
+ const domSelection = lexical.getDOMSelection(editorWindow);
2179
2364
  if (domSelection && domSelection.anchorNode && domSelection.focusNode) {
2180
2365
  const focusNode = lexical.$getNearestNodeFromDOMNode(domSelection.focusNode);
2181
2366
  const isFocusOutside = focusNode && !tableNode.is($findTableNode(focusNode));
@@ -2193,9 +2378,9 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2193
2378
  }
2194
2379
  if (selection && !selection.is(prevSelection) && ($isTableSelection(selection) || $isTableSelection(prevSelection)) && tableObserver.tableSelection && !tableObserver.tableSelection.is(prevSelection)) {
2195
2380
  if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
2196
- tableObserver.updateTableTableSelection(selection);
2381
+ tableObserver.$updateTableTableSelection(selection);
2197
2382
  } else if (!$isTableSelection(selection) && $isTableSelection(prevSelection) && prevSelection.tableKey === tableObserver.tableNodeKey) {
2198
- tableObserver.updateTableTableSelection(null);
2383
+ tableObserver.$updateTableTableSelection(null);
2199
2384
  }
2200
2385
  return false;
2201
2386
  }
@@ -2350,14 +2535,14 @@ function $forEachTableCell(grid, cb) {
2350
2535
  }
2351
2536
  }
2352
2537
  function $addHighlightStyleToTable(editor, tableSelection) {
2353
- tableSelection.disableHighlightStyle();
2538
+ tableSelection.$disableHighlightStyle();
2354
2539
  $forEachTableCell(tableSelection.table, cell => {
2355
2540
  cell.highlighted = true;
2356
2541
  $addHighlightToDOM(editor, cell);
2357
2542
  });
2358
2543
  }
2359
2544
  function $removeHighlightStyleToTable(editor, tableObserver) {
2360
- tableObserver.enableHighlightStyle();
2545
+ tableObserver.$enableHighlightStyle();
2361
2546
  $forEachTableCell(tableObserver.table, cell => {
2362
2547
  const elem = cell.elem;
2363
2548
  cell.highlighted = false;
@@ -2402,33 +2587,105 @@ const selectTableNodeInDirection = (tableObserver, tableNode, x, y, direction) =
2402
2587
  return false;
2403
2588
  }
2404
2589
  };
2405
- const adjustFocusNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
2406
- const isForward = direction === 'forward';
2407
- switch (direction) {
2408
- case 'backward':
2409
- case 'forward':
2410
- if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
2411
- tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table));
2412
- }
2413
- return true;
2414
- case 'up':
2415
- if (y !== 0) {
2416
- tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y - 1, tableObserver.table));
2417
- return true;
2418
- } else {
2419
- return false;
2420
- }
2421
- case 'down':
2422
- if (y !== tableObserver.table.rows - 1) {
2423
- tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y + 1, tableObserver.table));
2424
- return true;
2425
- } else {
2426
- return false;
2427
- }
2428
- default:
2429
- return false;
2590
+ function getCorner(rect, cellValue) {
2591
+ let colName;
2592
+ let rowName;
2593
+ if (cellValue.startColumn === rect.minColumn) {
2594
+ colName = 'minColumn';
2595
+ } else if (cellValue.startColumn + cellValue.cell.__colSpan - 1 === rect.maxColumn) {
2596
+ colName = 'maxColumn';
2597
+ } else {
2598
+ return null;
2430
2599
  }
2431
- };
2600
+ if (cellValue.startRow === rect.minRow) {
2601
+ rowName = 'minRow';
2602
+ } else if (cellValue.startRow + cellValue.cell.__rowSpan - 1 === rect.maxRow) {
2603
+ rowName = 'maxRow';
2604
+ } else {
2605
+ return null;
2606
+ }
2607
+ return [colName, rowName];
2608
+ }
2609
+ function getCornerOrThrow(rect, cellValue) {
2610
+ const corner = getCorner(rect, cellValue);
2611
+ if (!(corner !== null)) {
2612
+ throw Error(`getCornerOrThrow: cell ${cellValue.cell.getKey()} is not at a corner of rect`);
2613
+ }
2614
+ return corner;
2615
+ }
2616
+ function oppositeCorner([colName, rowName]) {
2617
+ return [colName === 'minColumn' ? 'maxColumn' : 'minColumn', rowName === 'minRow' ? 'maxRow' : 'minRow'];
2618
+ }
2619
+ function cellAtCornerOrThrow(tableMap, rect, [colName, rowName]) {
2620
+ const rowNum = rect[rowName];
2621
+ const rowMap = tableMap[rowNum];
2622
+ if (!(rowMap !== undefined)) {
2623
+ throw Error(`cellAtCornerOrThrow: ${rowName} = ${String(rowNum)} missing in tableMap`);
2624
+ }
2625
+ const colNum = rect[colName];
2626
+ const cell = rowMap[colNum];
2627
+ if (!(cell !== undefined)) {
2628
+ throw Error(`cellAtCornerOrThrow: ${colName} = ${String(colNum)} missing in tableMap`);
2629
+ }
2630
+ return cell;
2631
+ }
2632
+ function $extractRectCorners(tableMap, anchorCellValue, newFocusCellValue) {
2633
+ // We are sure that the focus now either contracts or expands the rect
2634
+ // but both the anchor and focus might be moved to ensure a rectangle
2635
+ // given a potentially ragged merge shape
2636
+ const rect = $computeTableCellRectBoundary(tableMap, anchorCellValue, newFocusCellValue);
2637
+ const anchorCorner = getCorner(rect, anchorCellValue);
2638
+ if (anchorCorner) {
2639
+ return [cellAtCornerOrThrow(tableMap, rect, anchorCorner), cellAtCornerOrThrow(tableMap, rect, oppositeCorner(anchorCorner))];
2640
+ }
2641
+ const newFocusCorner = getCorner(rect, newFocusCellValue);
2642
+ if (newFocusCorner) {
2643
+ return [cellAtCornerOrThrow(tableMap, rect, oppositeCorner(newFocusCorner)), cellAtCornerOrThrow(tableMap, rect, newFocusCorner)];
2644
+ }
2645
+ // TODO this doesn't have to be arbitrary, use the closest corner instead
2646
+ const newAnchorCorner = ['minColumn', 'minRow'];
2647
+ return [cellAtCornerOrThrow(tableMap, rect, newAnchorCorner), cellAtCornerOrThrow(tableMap, rect, oppositeCorner(newAnchorCorner))];
2648
+ }
2649
+ function $adjustFocusInDirection(tableObserver, tableMap, anchorCellValue, focusCellValue, direction) {
2650
+ const rect = $computeTableCellRectBoundary(tableMap, anchorCellValue, focusCellValue);
2651
+ const spans = $computeTableCellRectSpans(tableMap, rect);
2652
+ const {
2653
+ topSpan,
2654
+ leftSpan,
2655
+ bottomSpan,
2656
+ rightSpan
2657
+ } = spans;
2658
+ const anchorCorner = getCornerOrThrow(rect, anchorCellValue);
2659
+ const [focusColumn, focusRow] = oppositeCorner(anchorCorner);
2660
+ let fCol = rect[focusColumn];
2661
+ let fRow = rect[focusRow];
2662
+ if (direction === 'forward') {
2663
+ fCol += focusColumn === 'maxColumn' ? 1 : leftSpan;
2664
+ } else if (direction === 'backward') {
2665
+ fCol -= focusColumn === 'minColumn' ? 1 : rightSpan;
2666
+ } else if (direction === 'down') {
2667
+ fRow += focusRow === 'maxRow' ? 1 : topSpan;
2668
+ } else if (direction === 'up') {
2669
+ fRow -= focusRow === 'minRow' ? 1 : bottomSpan;
2670
+ }
2671
+ const targetRowMap = tableMap[fRow];
2672
+ if (targetRowMap === undefined) {
2673
+ return false;
2674
+ }
2675
+ const newFocusCellValue = targetRowMap[fCol];
2676
+ if (newFocusCellValue === undefined) {
2677
+ return false;
2678
+ }
2679
+ // We can be certain that anchorCellValue and newFocusCellValue are
2680
+ // contained within the desired selection, but we are not certain if
2681
+ // they need to be expanded or not to maintain a rectangular shape
2682
+ const [finalAnchorCell, finalFocusCell] = $extractRectCorners(tableMap, anchorCellValue, newFocusCellValue);
2683
+ const anchorDOM = $getObserverCellFromCellNodeOrThrow(tableObserver, finalAnchorCell.cell);
2684
+ const focusDOM = $getObserverCellFromCellNodeOrThrow(tableObserver, finalFocusCell.cell);
2685
+ tableObserver.$setAnchorCellForSelection(anchorDOM);
2686
+ tableObserver.$setFocusCellForSelection(focusDOM, true);
2687
+ return true;
2688
+ }
2432
2689
  function $isSelectionInTable(selection, tableNode) {
2433
2690
  if (lexical.$isRangeSelection(selection) || $isTableSelection(selection)) {
2434
2691
  const isAnchorInside = tableNode.isParentOf(selection.anchor.getNode());
@@ -2444,20 +2701,14 @@ function selectTableCellNode(tableCell, fromStart) {
2444
2701
  tableCell.selectEnd();
2445
2702
  }
2446
2703
  }
2447
- const BROWSER_BLUE_RGB = '172,206,247';
2448
2704
  function $addHighlightToDOM(editor, cell) {
2449
2705
  const element = cell.elem;
2706
+ const editorThemeClasses = editor._config.theme;
2450
2707
  const node = lexical.$getNearestNodeFromDOMNode(element);
2451
2708
  if (!$isTableCellNode(node)) {
2452
2709
  throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
2453
2710
  }
2454
- const backgroundColor = node.getBackgroundColor();
2455
- if (backgroundColor === null) {
2456
- element.style.setProperty('background-color', `rgb(${BROWSER_BLUE_RGB})`);
2457
- } else {
2458
- element.style.setProperty('background-image', `linear-gradient(to right, rgba(${BROWSER_BLUE_RGB},0.85), rgba(${BROWSER_BLUE_RGB},0.85))`);
2459
- }
2460
- element.style.setProperty('caret-color', 'transparent');
2711
+ utils.addClassNamesToElement(element, editorThemeClasses.tableCellSelected);
2461
2712
  }
2462
2713
  function $removeHighlightFromDOM(editor, cell) {
2463
2714
  const element = cell.elem;
@@ -2465,12 +2716,8 @@ function $removeHighlightFromDOM(editor, cell) {
2465
2716
  if (!$isTableCellNode(node)) {
2466
2717
  throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
2467
2718
  }
2468
- const backgroundColor = node.getBackgroundColor();
2469
- if (backgroundColor === null) {
2470
- element.style.removeProperty('background-color');
2471
- }
2472
- element.style.removeProperty('background-image');
2473
- element.style.removeProperty('caret-color');
2719
+ const editorThemeClasses = editor._config.theme;
2720
+ utils.removeClassNamesFromElement(element, editorThemeClasses.tableCellSelected);
2474
2721
  }
2475
2722
  function $findCellNode(node) {
2476
2723
  const cellNode = utils.$findMatchingParent(node, $isTableCellNode);
@@ -2561,8 +2808,8 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2561
2808
  const lastCellCoords = tableNode.getCordsFromCellNode(lastCellNode, tableObserver.table);
2562
2809
  const firstCellDOM = tableNode.getDOMCellFromCordsOrThrow(firstCellCoords.x, firstCellCoords.y, tableObserver.table);
2563
2810
  const lastCellDOM = tableNode.getDOMCellFromCordsOrThrow(lastCellCoords.x, lastCellCoords.y, tableObserver.table);
2564
- tableObserver.setAnchorCellForSelection(firstCellDOM);
2565
- tableObserver.setFocusCellForSelection(lastCellDOM, true);
2811
+ tableObserver.$setAnchorCellForSelection(firstCellDOM);
2812
+ tableObserver.$setFocusCellForSelection(lastCellDOM, true);
2566
2813
  return true;
2567
2814
  }
2568
2815
  }
@@ -2629,7 +2876,7 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2629
2876
  return false;
2630
2877
  }
2631
2878
  if (isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction)) {
2632
- return $handleTableExit(event, anchorNode, tableNode, direction);
2879
+ return $handleTableExit(event, anchorNode, anchorCellNode, tableNode, direction);
2633
2880
  }
2634
2881
  return false;
2635
2882
  }
@@ -2642,7 +2889,7 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2642
2889
  if (anchor.type === 'element') {
2643
2890
  edgeSelectionRect = anchorDOM.getBoundingClientRect();
2644
2891
  } else {
2645
- const domSelection = window.getSelection();
2892
+ const domSelection = lexical.getDOMSelection(getEditorWindow(editor));
2646
2893
  if (domSelection === null || domSelection.rangeCount === 0) {
2647
2894
  return false;
2648
2895
  }
@@ -2664,8 +2911,8 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2664
2911
  const cords = tableNode.getCordsFromCellNode(anchorCellNode, tableObserver.table);
2665
2912
  if (event.shiftKey) {
2666
2913
  const cell = tableNode.getDOMCellFromCordsOrThrow(cords.x, cords.y, tableObserver.table);
2667
- tableObserver.setAnchorCellForSelection(cell);
2668
- tableObserver.setFocusCellForSelection(cell, true);
2914
+ tableObserver.$setAnchorCellForSelection(cell);
2915
+ tableObserver.$setFocusCellForSelection(cell, true);
2669
2916
  } else {
2670
2917
  return selectTableNodeInDirection(tableObserver, tableNode, cords.x, cords.y, direction);
2671
2918
  }
@@ -2686,15 +2933,15 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2686
2933
  if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableNode(tableNodeFromSelection) || tableElement == null) {
2687
2934
  return false;
2688
2935
  }
2689
- tableObserver.updateTableTableSelection(selection);
2936
+ tableObserver.$updateTableTableSelection(selection);
2690
2937
  const grid = getTable(tableNodeFromSelection, tableElement);
2691
2938
  const cordsAnchor = tableNode.getCordsFromCellNode(anchorCellNode, grid);
2692
2939
  const anchorCell = tableNode.getDOMCellFromCordsOrThrow(cordsAnchor.x, cordsAnchor.y, grid);
2693
- tableObserver.setAnchorCellForSelection(anchorCell);
2940
+ tableObserver.$setAnchorCellForSelection(anchorCell);
2694
2941
  stopEvent(event);
2695
2942
  if (event.shiftKey) {
2696
- const cords = tableNode.getCordsFromCellNode(focusCellNode, grid);
2697
- return adjustFocusNodeInDirection(tableObserver, tableNodeFromSelection, cords.x, cords.y, direction);
2943
+ const [tableMap, anchorValue, focusValue] = $computeTableMap(tableNode, anchorCellNode, focusCellNode);
2944
+ return $adjustFocusInDirection(tableObserver, tableMap, anchorValue, focusValue, direction);
2698
2945
  } else {
2699
2946
  focusCellNode.selectEnd();
2700
2947
  }
@@ -2730,11 +2977,7 @@ function $isExitingTableTextAnchor(type, offset, anchorNode, direction) {
2730
2977
  const hasValidOffset = direction === 'backward' ? offset === 0 : offset === anchorNode.getTextContentSize();
2731
2978
  return type === 'text' && hasValidOffset && (direction === 'backward' ? parentNode.getPreviousSibling() === null : parentNode.getNextSibling() === null);
2732
2979
  }
2733
- function $handleTableExit(event, anchorNode, tableNode, direction) {
2734
- const anchorCellNode = utils.$findMatchingParent(anchorNode, $isTableCellNode);
2735
- if (!$isTableCellNode(anchorCellNode)) {
2736
- return false;
2737
- }
2980
+ function $handleTableExit(event, anchorNode, anchorCellNode, tableNode, direction) {
2738
2981
  const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
2739
2982
  if (!isExitingCell(tableMap, cellValue, direction)) {
2740
2983
  return false;
@@ -2785,7 +3028,7 @@ function $getTableEdgeCursorPosition(editor, selection, tableNode) {
2785
3028
  }
2786
3029
 
2787
3030
  // TODO: Add support for nested tables
2788
- const domSelection = window.getSelection();
3031
+ const domSelection = lexical.getDOMSelection(getEditorWindow(editor));
2789
3032
  if (!domSelection) {
2790
3033
  return undefined;
2791
3034
  }
@@ -2825,6 +3068,13 @@ function $getTableEdgeCursorPosition(editor, selection, tableNode) {
2825
3068
  return undefined;
2826
3069
  }
2827
3070
  }
3071
+ function $getObserverCellFromCellNodeOrThrow(tableObserver, tableCellNode) {
3072
+ const {
3073
+ tableNode
3074
+ } = tableObserver.$lookup();
3075
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
3076
+ return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
3077
+ }
2828
3078
 
2829
3079
  /**
2830
3080
  * Copyright (c) Meta Platforms, Inc. and affiliates.