@lexical/table 0.29.1-nightly.20250401.0 → 0.29.1-nightly.20250403.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.
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { addClassNamesToElement, $descendantsMatching, $findMatchingParent, removeClassNamesFromElement, objectKlassEquals, isHTMLElement as isHTMLElement$1, $insertFirst as $insertFirst$1, mergeRegister, $insertNodeToNearestRoot, $unwrapAndFilterDescendants } from '@lexical/utils';
10
- import { ElementNode, isHTMLElement, $isInlineElementOrDecoratorNode, $isTextNode, $isLineBreakNode, $createParagraphNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $createPoint, $isParagraphNode, $isElementNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getNodeByKey, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $createRangeSelection, $isRootNode, INSERT_PARAGRAPH_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_CRITICAL, CUT_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $getPreviousSelection, $getNearestNodeFromDOMNode, $createRangeSelectionFromDom, $isRootOrShadowRoot, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, isDOMNode, $caretFromPoint, $isExtendableTextPointCaret, $extendCaretToRange, $isSiblingCaret, $getSiblingCaret, $setPointFromCaret, $normalizeCaret, $getAdjacentChildCaret, $isChildCaret, $getChildCaret, setDOMUnmanaged, COMMAND_PRIORITY_EDITOR, CLICK_COMMAND } from 'lexical';
10
+ import { ElementNode, isHTMLElement, $isInlineElementOrDecoratorNode, $isTextNode, $isLineBreakNode, $createParagraphNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $isParagraphNode, $createPoint, $isElementNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getNodeByKey, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $createRangeSelection, $isRootNode, INSERT_PARAGRAPH_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_CRITICAL, CUT_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $getPreviousSelection, $getNearestNodeFromDOMNode, $createRangeSelectionFromDom, $isRootOrShadowRoot, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, isDOMNode, $caretFromPoint, $isExtendableTextPointCaret, $extendCaretToRange, $isSiblingCaret, $getSiblingCaret, $setPointFromCaret, $normalizeCaret, $getAdjacentChildCaret, $isChildCaret, $getChildCaret, setDOMUnmanaged, COMMAND_PRIORITY_EDITOR, CLICK_COMMAND } from 'lexical';
11
11
  import { copyToClipboard, $getClipboardDataFromSelection } from '@lexical/clipboard';
12
12
 
13
13
  /**
@@ -533,6 +533,10 @@ function $removeTableRowAtIndex(tableNode, indexToDelete) {
533
533
  targetRowNode.remove();
534
534
  return tableNode;
535
535
  }
536
+
537
+ /**
538
+ * @deprecated This function does not support merged cells. Use {@link $insertTableRowAtSelection} or {@link $insertTableRowAtNode} instead.
539
+ */
536
540
  function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, table) {
537
541
  const tableRows = tableNode.getChildren();
538
542
  if (targetIndex >= tableRows.length || targetIndex < 0) {
@@ -585,7 +589,7 @@ const getHeaderState = (currentState, possibleState) => {
585
589
  * taking into account any spans. If successful, returns the
586
590
  * inserted table row node.
587
591
  */
588
- function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
592
+ function $insertTableRowAtSelection(insertAfter = true) {
589
593
  const selection = $getSelection();
590
594
  if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
591
595
  formatDevErrorMessage(`Expected a RangeSelection or TableSelection`);
@@ -594,17 +598,40 @@ function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
594
598
  const focus = selection.focus.getNode();
595
599
  const [anchorCell] = $getNodeTriplet(anchor);
596
600
  const [focusCell,, grid] = $getNodeTriplet(focus);
597
- const [gridMap, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
598
- const columnCount = gridMap[0].length;
601
+ const [, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
599
602
  const {
600
603
  startRow: anchorStartRow
601
604
  } = anchorCellMap;
602
605
  const {
603
606
  startRow: focusStartRow
604
607
  } = focusCellMap;
608
+ if (insertAfter) {
609
+ return $insertTableRowAtNode(anchorStartRow + anchorCell.__rowSpan > focusStartRow + focusCell.__rowSpan ? anchorCell : focusCell, true);
610
+ } else {
611
+ return $insertTableRowAtNode(focusStartRow < anchorStartRow ? focusCell : anchorCell, false);
612
+ }
613
+ }
614
+
615
+ /**
616
+ * @deprecated renamed to {@link $insertTableRowAtSelection}
617
+ */
618
+ const $insertTableRow__EXPERIMENTAL = $insertTableRowAtSelection;
619
+
620
+ /**
621
+ * Inserts a table row before or after the given cell node,
622
+ * taking into account any spans. If successful, returns the
623
+ * inserted table row node.
624
+ */
625
+ function $insertTableRowAtNode(cellNode, insertAfter = true) {
626
+ const [,, grid] = $getNodeTriplet(cellNode);
627
+ const [gridMap, cellMap] = $computeTableMap(grid, cellNode, cellNode);
628
+ const columnCount = gridMap[0].length;
629
+ const {
630
+ startRow: cellStartRow
631
+ } = cellMap;
605
632
  let insertedRow = null;
606
633
  if (insertAfter) {
607
- const insertAfterEndRow = Math.max(focusStartRow + focusCell.__rowSpan, anchorStartRow + anchorCell.__rowSpan) - 1;
634
+ const insertAfterEndRow = cellStartRow + cellNode.__rowSpan - 1;
608
635
  const insertAfterEndRowMap = gridMap[insertAfterEndRow];
609
636
  const newRow = $createTableRowNode();
610
637
  for (let i = 0; i < columnCount; i++) {
@@ -628,7 +655,7 @@ function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
628
655
  insertAfterEndRowNode.insertAfter(newRow);
629
656
  insertedRow = newRow;
630
657
  } else {
631
- const insertBeforeStartRow = Math.min(focusStartRow, anchorStartRow);
658
+ const insertBeforeStartRow = cellStartRow;
632
659
  const insertBeforeStartRowMap = gridMap[insertBeforeStartRow];
633
660
  const newRow = $createTableRowNode();
634
661
  for (let i = 0; i < columnCount; i++) {
@@ -654,6 +681,10 @@ function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
654
681
  }
655
682
  return insertedRow;
656
683
  }
684
+
685
+ /**
686
+ * @deprecated This function does not support merged cells. Use {@link $insertTableColumnAtSelection} or {@link $insertTableColumnAtNode} instead.
687
+ */
657
688
  function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, columnCount, table) {
658
689
  const tableRows = tableNode.getChildren();
659
690
  const tableCellsToBeInserted = [];
@@ -704,7 +735,7 @@ function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, co
704
735
  * taking into account any spans. If successful, returns the
705
736
  * first inserted cell node.
706
737
  */
707
- function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
738
+ function $insertTableColumnAtSelection(insertAfter = true) {
708
739
  const selection = $getSelection();
709
740
  if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
710
741
  formatDevErrorMessage(`Expected a RangeSelection or TableSelection`);
@@ -713,10 +744,38 @@ function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
713
744
  const focus = selection.focus.getNode();
714
745
  const [anchorCell] = $getNodeTriplet(anchor);
715
746
  const [focusCell,, grid] = $getNodeTriplet(focus);
716
- const [gridMap, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
747
+ const [, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
748
+ const {
749
+ startColumn: anchorStartColumn
750
+ } = anchorCellMap;
751
+ const {
752
+ startColumn: focusStartColumn
753
+ } = focusCellMap;
754
+ if (insertAfter) {
755
+ return $insertTableColumnAtNode(anchorStartColumn + anchorCell.__colSpan > focusStartColumn + focusCell.__colSpan ? anchorCell : focusCell, true);
756
+ } else {
757
+ return $insertTableColumnAtNode(focusStartColumn < anchorStartColumn ? focusCell : anchorCell, false);
758
+ }
759
+ }
760
+
761
+ /**
762
+ * @deprecated renamed to {@link $insertTableColumnAtSelection}
763
+ */
764
+ const $insertTableColumn__EXPERIMENTAL = $insertTableColumnAtSelection;
765
+
766
+ /**
767
+ * Inserts a column before or after the given cell node,
768
+ * taking into account any spans. If successful, returns the
769
+ * first inserted cell node.
770
+ */
771
+ function $insertTableColumnAtNode(cellNode, insertAfter = true, shouldSetSelection = true) {
772
+ const [,, grid] = $getNodeTriplet(cellNode);
773
+ const [gridMap, cellMap] = $computeTableMap(grid, cellNode, cellNode);
717
774
  const rowCount = gridMap.length;
718
- const startColumn = insertAfter ? Math.max(focusCellMap.startColumn, anchorCellMap.startColumn) : Math.min(focusCellMap.startColumn, anchorCellMap.startColumn);
719
- const insertAfterColumn = insertAfter ? startColumn + focusCell.__colSpan - 1 : startColumn - 1;
775
+ const {
776
+ startColumn
777
+ } = cellMap;
778
+ const insertAfterColumn = insertAfter ? startColumn + cellNode.__colSpan - 1 : startColumn - 1;
720
779
  const gridFirstChild = grid.getFirstChild();
721
780
  if (!$isTableRowNode(gridFirstChild)) {
722
781
  formatDevErrorMessage(`Expected firstTable child to be a row`);
@@ -773,7 +832,7 @@ function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
773
832
  currentCell.setColSpan(currentCell.__colSpan + 1);
774
833
  }
775
834
  }
776
- if (firstInsertedCell !== null) {
835
+ if (firstInsertedCell !== null && shouldSetSelection) {
777
836
  $moveSelectionToCell(firstInsertedCell);
778
837
  }
779
838
  const colWidths = grid.getColWidths();
@@ -786,6 +845,10 @@ function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
786
845
  }
787
846
  return firstInsertedCell;
788
847
  }
848
+
849
+ /**
850
+ * @deprecated This function does not support merged cells. Use {@link $deleteTableColumnAtSelection} instead.
851
+ */
789
852
  function $deleteTableColumn(tableNode, targetIndex) {
790
853
  const tableRows = tableNode.getChildren();
791
854
  for (let i = 0; i < tableRows.length; i++) {
@@ -800,7 +863,7 @@ function $deleteTableColumn(tableNode, targetIndex) {
800
863
  }
801
864
  return tableNode;
802
865
  }
803
- function $deleteTableRow__EXPERIMENTAL() {
866
+ function $deleteTableRowAtSelection() {
804
867
  const selection = $getSelection();
805
868
  if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
806
869
  formatDevErrorMessage(`Expected a RangeSelection or TableSelection`);
@@ -885,7 +948,12 @@ function $deleteTableRow__EXPERIMENTAL() {
885
948
  $moveSelectionToCell(cell);
886
949
  }
887
950
  }
888
- function $deleteTableColumn__EXPERIMENTAL() {
951
+
952
+ /**
953
+ * @deprecated renamed to {@link $deleteTableRowAtSelection}
954
+ */
955
+ const $deleteTableRow__EXPERIMENTAL = $deleteTableRowAtSelection;
956
+ function $deleteTableColumnAtSelection() {
889
957
  const selection = $getSelection();
890
958
  if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
891
959
  formatDevErrorMessage(`Expected a RangeSelection or TableSelection`);
@@ -959,6 +1027,11 @@ function $deleteTableColumn__EXPERIMENTAL() {
959
1027
  grid.setColWidths(newColWidths);
960
1028
  }
961
1029
  }
1030
+
1031
+ /**
1032
+ * @deprecated renamed to {@link $deleteTableColumnAtSelection}
1033
+ */
1034
+ const $deleteTableColumn__EXPERIMENTAL = $deleteTableColumnAtSelection;
962
1035
  function $moveSelectionToCell(cell) {
963
1036
  const firstDescendant = cell.getFirstDescendant();
964
1037
  if (firstDescendant == null) {
@@ -975,13 +1048,122 @@ function $insertFirst(parent, node) {
975
1048
  parent.append(node);
976
1049
  }
977
1050
  }
1051
+ function $mergeCells(cellNodes) {
1052
+ if (cellNodes.length === 0) {
1053
+ return null;
1054
+ }
1055
+
1056
+ // Find the table node
1057
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(cellNodes[0]);
1058
+ const [gridMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
1059
+
1060
+ // Find the boundaries of the selection including merged cells
1061
+ let minRow = Infinity;
1062
+ let maxRow = -Infinity;
1063
+ let minCol = Infinity;
1064
+ let maxCol = -Infinity;
1065
+
1066
+ // First pass: find the actual boundaries considering merged cells
1067
+ const processedCells = new Set();
1068
+ for (const row of gridMap) {
1069
+ for (const mapCell of row) {
1070
+ if (!mapCell || !mapCell.cell) {
1071
+ continue;
1072
+ }
1073
+ const cellKey = mapCell.cell.getKey();
1074
+ if (processedCells.has(cellKey)) {
1075
+ continue;
1076
+ }
1077
+ if (cellNodes.some(cell => cell.is(mapCell.cell))) {
1078
+ processedCells.add(cellKey);
1079
+ // Get the actual position of this cell in the grid
1080
+ const cellStartRow = mapCell.startRow;
1081
+ const cellStartCol = mapCell.startColumn;
1082
+ const cellRowSpan = mapCell.cell.__rowSpan || 1;
1083
+ const cellColSpan = mapCell.cell.__colSpan || 1;
1084
+
1085
+ // Update boundaries considering the cell's actual position and span
1086
+ minRow = Math.min(minRow, cellStartRow);
1087
+ maxRow = Math.max(maxRow, cellStartRow + cellRowSpan - 1);
1088
+ minCol = Math.min(minCol, cellStartCol);
1089
+ maxCol = Math.max(maxCol, cellStartCol + cellColSpan - 1);
1090
+ }
1091
+ }
1092
+ }
1093
+
1094
+ // Validate boundaries
1095
+ if (minRow === Infinity || minCol === Infinity) {
1096
+ return null;
1097
+ }
1098
+
1099
+ // The total span of the merged cell
1100
+ const totalRowSpan = maxRow - minRow + 1;
1101
+ const totalColSpan = maxCol - minCol + 1;
1102
+
1103
+ // Use the top-left cell as the target cell
1104
+ const targetCellMap = gridMap[minRow][minCol];
1105
+ if (!targetCellMap.cell) {
1106
+ return null;
1107
+ }
1108
+ const targetCell = targetCellMap.cell;
1109
+
1110
+ // Set the spans for the target cell
1111
+ targetCell.setColSpan(totalColSpan);
1112
+ targetCell.setRowSpan(totalRowSpan);
1113
+
1114
+ // Move content from other cells to the target cell
1115
+ const seenCells = new Set([targetCell.getKey()]);
1116
+
1117
+ // Second pass: merge content and remove other cells
1118
+ for (let row = minRow; row <= maxRow; row++) {
1119
+ for (let col = minCol; col <= maxCol; col++) {
1120
+ const mapCell = gridMap[row][col];
1121
+ if (!mapCell.cell) {
1122
+ continue;
1123
+ }
1124
+ const currentCell = mapCell.cell;
1125
+ const key = currentCell.getKey();
1126
+ if (!seenCells.has(key)) {
1127
+ seenCells.add(key);
1128
+ const isEmpty = $cellContainsEmptyParagraph(currentCell);
1129
+ if (!isEmpty) {
1130
+ targetCell.append(...currentCell.getChildren());
1131
+ }
1132
+ currentCell.remove();
1133
+ }
1134
+ }
1135
+ }
1136
+
1137
+ // Ensure target cell has content
1138
+ if (targetCell.getChildrenSize() === 0) {
1139
+ targetCell.append($createParagraphNode());
1140
+ }
1141
+ return targetCell;
1142
+ }
1143
+ function $cellContainsEmptyParagraph(cell) {
1144
+ if (cell.getChildrenSize() !== 1) {
1145
+ return false;
1146
+ }
1147
+ const firstChild = cell.getFirstChildOrThrow();
1148
+ if (!$isParagraphNode(firstChild) || !firstChild.isEmpty()) {
1149
+ return false;
1150
+ }
1151
+ return true;
1152
+ }
978
1153
  function $unmergeCell() {
979
1154
  const selection = $getSelection();
980
1155
  if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
981
1156
  formatDevErrorMessage(`Expected a RangeSelection or TableSelection`);
982
1157
  }
983
1158
  const anchor = selection.anchor.getNode();
984
- const [cell, row, grid] = $getNodeTriplet(anchor);
1159
+ const cellNode = $findMatchingParent(anchor, $isTableCellNode);
1160
+ if (!$isTableCellNode(cellNode)) {
1161
+ formatDevErrorMessage(`Expected to find a parent TableCellNode`);
1162
+ }
1163
+ return $unmergeCellNode(cellNode);
1164
+ }
1165
+ function $unmergeCellNode(cellNode) {
1166
+ const [cell, row, grid] = $getNodeTriplet(cellNode);
985
1167
  const colSpan = cell.__colSpan;
986
1168
  const rowSpan = cell.__rowSpan;
987
1169
  if (colSpan === 1 && rowSpan === 1) {
@@ -2283,62 +2465,115 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2283
2465
  if (nodes.length !== 1 || !$isTableNode(nodes[0]) || !isSelectionInsideOfGrid || anchorAndFocus === null) {
2284
2466
  return false;
2285
2467
  }
2286
- const [anchor] = anchorAndFocus;
2287
- const newGrid = nodes[0];
2288
- const newGridRows = newGrid.getChildren();
2289
- const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
2290
- const newRowCount = newGrid.getChildrenSize();
2291
- const gridCellNode = $findMatchingParent(anchor.getNode(), n => $isTableCellNode(n));
2292
- const gridRowNode = gridCellNode && $findMatchingParent(gridCellNode, n => $isTableRowNode(n));
2293
- const gridNode = gridRowNode && $findMatchingParent(gridRowNode, n => $isTableNode(n));
2294
- if (!$isTableCellNode(gridCellNode) || !$isTableRowNode(gridRowNode) || !$isTableNode(gridNode)) {
2468
+ const [anchor, focus] = anchorAndFocus;
2469
+ const [anchorCellNode, anchorRowNode, gridNode] = $getNodeTriplet(anchor);
2470
+ const focusCellNode = $findMatchingParent(focus.getNode(), n => $isTableCellNode(n));
2471
+ if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableRowNode(anchorRowNode) || !$isTableNode(gridNode)) {
2295
2472
  return false;
2296
2473
  }
2297
- const startY = gridRowNode.getIndexWithinParent();
2298
- const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
2299
- const startX = gridCellNode.getIndexWithinParent();
2300
- const stopX = Math.min(gridRowNode.getChildrenSize() - 1, startX + newColumnCount - 1);
2301
- const fromX = Math.min(startX, stopX);
2302
- const fromY = Math.min(startY, stopY);
2303
- const toX = Math.max(startX, stopX);
2304
- const toY = Math.max(startY, stopY);
2305
- const gridRowNodes = gridNode.getChildren();
2306
- let newRowIdx = 0;
2307
- for (let r = fromY; r <= toY; r++) {
2308
- const currentGridRowNode = gridRowNodes[r];
2309
- if (!$isTableRowNode(currentGridRowNode)) {
2310
- return false;
2311
- }
2312
- const newGridRowNode = newGridRows[newRowIdx];
2313
- if (!$isTableRowNode(newGridRowNode)) {
2314
- return false;
2474
+ const templateGrid = nodes[0];
2475
+ const [initialGridMap, anchorCellMap, focusCellMap] = $computeTableMap(gridNode, anchorCellNode, focusCellNode);
2476
+ const [templateGridMap] = $computeTableMapSkipCellCheck(templateGrid, null, null);
2477
+ const initialRowCount = initialGridMap.length;
2478
+ const initialColCount = initialRowCount > 0 ? initialGridMap[0].length : 0;
2479
+
2480
+ // If we have a range selection, we'll fit the template grid into the
2481
+ // table, growing the table if necessary.
2482
+ let startRow = anchorCellMap.startRow;
2483
+ let startCol = anchorCellMap.startColumn;
2484
+ let affectedRowCount = templateGridMap.length;
2485
+ let affectedColCount = affectedRowCount > 0 ? templateGridMap[0].length : 0;
2486
+ if (isTableSelection) {
2487
+ // If we have a table selection, we'll only modify the cells within
2488
+ // the selection boundary.
2489
+ const selectionBoundary = $computeTableCellRectBoundary(initialGridMap, anchorCellMap, focusCellMap);
2490
+ const selectionRowCount = selectionBoundary.maxRow - selectionBoundary.minRow + 1;
2491
+ const selectionColCount = selectionBoundary.maxColumn - selectionBoundary.minColumn + 1;
2492
+ startRow = selectionBoundary.minRow;
2493
+ startCol = selectionBoundary.minColumn;
2494
+ affectedRowCount = Math.min(affectedRowCount, selectionRowCount);
2495
+ affectedColCount = Math.min(affectedColCount, selectionColCount);
2496
+ }
2497
+
2498
+ // Step 1: Unmerge all merged cells within the affected area
2499
+ let didPerformMergeOperations = false;
2500
+ const lastRowForUnmerge = Math.min(initialRowCount, startRow + affectedRowCount) - 1;
2501
+ const lastColForUnmerge = Math.min(initialColCount, startCol + affectedColCount) - 1;
2502
+ const unmergedKeys = new Set();
2503
+ for (let row = startRow; row <= lastRowForUnmerge; row++) {
2504
+ for (let col = startCol; col <= lastColForUnmerge; col++) {
2505
+ const cellMap = initialGridMap[row][col];
2506
+ if (unmergedKeys.has(cellMap.cell.getKey())) {
2507
+ continue; // cell was a merged cell that was already handled
2508
+ }
2509
+ if (cellMap.cell.__rowSpan === 1 && cellMap.cell.__colSpan === 1) {
2510
+ continue; // cell is not a merged cell
2511
+ }
2512
+ $unmergeCellNode(cellMap.cell);
2513
+ unmergedKeys.add(cellMap.cell.getKey());
2514
+ didPerformMergeOperations = true;
2315
2515
  }
2316
- const gridCellNodes = currentGridRowNode.getChildren();
2317
- const newGridCellNodes = newGridRowNode.getChildren();
2318
- let newColumnIdx = 0;
2319
- for (let c = fromX; c <= toX; c++) {
2320
- const currentGridCellNode = gridCellNodes[c];
2321
- if (!$isTableCellNode(currentGridCellNode)) {
2322
- return false;
2516
+ }
2517
+ let [interimGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
2518
+
2519
+ // Step 2: Expand current table (if needed)
2520
+ const rowsToInsert = affectedRowCount - initialRowCount + startRow;
2521
+ for (let i = 0; i < rowsToInsert; i++) {
2522
+ const cellMap = interimGridMap[initialRowCount - 1][0];
2523
+ $insertTableRowAtNode(cellMap.cell);
2524
+ }
2525
+ const colsToInsert = affectedColCount - initialColCount + startCol;
2526
+ for (let i = 0; i < colsToInsert; i++) {
2527
+ const cellMap = interimGridMap[0][initialColCount - 1];
2528
+ $insertTableColumnAtNode(cellMap.cell, true, false);
2529
+ }
2530
+ [interimGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
2531
+
2532
+ // Step 3: Merge cells and set cell content, to match template grid
2533
+ for (let row = startRow; row < startRow + affectedRowCount; row++) {
2534
+ for (let col = startCol; col < startCol + affectedColCount; col++) {
2535
+ const templateRow = row - startRow;
2536
+ const templateCol = col - startCol;
2537
+ const templateCellMap = templateGridMap[templateRow][templateCol];
2538
+ if (templateCellMap.startRow !== templateRow || templateCellMap.startColumn !== templateCol) {
2539
+ continue; // cell is a merged cell that was already handled
2323
2540
  }
2324
- const newGridCellNode = newGridCellNodes[newColumnIdx];
2325
- if (!$isTableCellNode(newGridCellNode)) {
2326
- return false;
2541
+ const templateCell = templateCellMap.cell;
2542
+ if (templateCell.__rowSpan !== 1 || templateCell.__colSpan !== 1) {
2543
+ const cellsToMerge = [];
2544
+ const lastRowForMerge = Math.min(row + templateCell.__rowSpan, startRow + affectedRowCount) - 1;
2545
+ const lastColForMerge = Math.min(col + templateCell.__colSpan, startCol + affectedColCount) - 1;
2546
+ for (let r = row; r <= lastRowForMerge; r++) {
2547
+ for (let c = col; c <= lastColForMerge; c++) {
2548
+ const cellMap = interimGridMap[r][c];
2549
+ cellsToMerge.push(cellMap.cell);
2550
+ }
2551
+ }
2552
+ $mergeCells(cellsToMerge);
2553
+ didPerformMergeOperations = true;
2327
2554
  }
2328
- const originalChildren = currentGridCellNode.getChildren();
2329
- newGridCellNode.getChildren().forEach(child => {
2555
+ const {
2556
+ cell
2557
+ } = interimGridMap[row][col];
2558
+ const originalChildren = cell.getChildren();
2559
+ templateCell.getChildren().forEach(child => {
2330
2560
  if ($isTextNode(child)) {
2331
2561
  const paragraphNode = $createParagraphNode();
2332
2562
  paragraphNode.append(child);
2333
- currentGridCellNode.append(child);
2563
+ cell.append(child);
2334
2564
  } else {
2335
- currentGridCellNode.append(child);
2565
+ cell.append(child);
2336
2566
  }
2337
2567
  });
2338
2568
  originalChildren.forEach(n => n.remove());
2339
- newColumnIdx++;
2340
2569
  }
2341
- newRowIdx++;
2570
+ }
2571
+ if (isTableSelection && didPerformMergeOperations) {
2572
+ // reset the table selection in case the anchor or focus cell was
2573
+ // removed via merge operations
2574
+ const [finalGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
2575
+ const newAnchorCellMap = finalGridMap[anchorCellMap.startRow][anchorCellMap.startColumn];
2576
+ newAnchorCellMap.cell.selectEnd();
2342
2577
  }
2343
2578
  return true;
2344
2579
  }, COMMAND_PRIORITY_CRITICAL));
@@ -3889,4 +4124,4 @@ function registerTablePlugin(editor) {
3889
4124
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(CLICK_COMMAND, $tableClickCommand, COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(TableNode, $tableTransform), editor.registerNodeTransform(TableRowNode, $tableRowTransform), editor.registerNodeTransform(TableCellNode, $tableCellTransform));
3890
4125
  }
3891
4126
 
3892
- export { $computeTableMap, $computeTableMapSkipCellCheck, $createTableCellNode, $createTableNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $createTableSelectionFrom, $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $findCellNode, $findTableNode, $getElementForTableNode, $getNodeTriplet, $getTableAndElementByKey, $getTableCellNodeFromLexicalNode, $getTableCellNodeRect, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $getTableRowNodeFromTableCellNodeOrThrow, $insertTableColumn, $insertTableColumn__EXPERIMENTAL, $insertTableRow, $insertTableRow__EXPERIMENTAL, $isScrollableTablesActive, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, $removeTableRowAtIndex, $unmergeCell, INSERT_TABLE_COMMAND, TableCellHeaderStates, TableCellNode, TableNode, TableObserver, TableRowNode, applyTableHandlers, getDOMCellFromTarget, getTableElement, getTableObserverFromTableElement, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive };
4127
+ export { $computeTableMap, $computeTableMapSkipCellCheck, $createTableCellNode, $createTableNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $createTableSelectionFrom, $deleteTableColumn, $deleteTableColumnAtSelection, $deleteTableColumn__EXPERIMENTAL, $deleteTableRowAtSelection, $deleteTableRow__EXPERIMENTAL, $findCellNode, $findTableNode, $getElementForTableNode, $getNodeTriplet, $getTableAndElementByKey, $getTableCellNodeFromLexicalNode, $getTableCellNodeRect, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $getTableRowNodeFromTableCellNodeOrThrow, $insertTableColumn, $insertTableColumnAtSelection, $insertTableColumn__EXPERIMENTAL, $insertTableRow, $insertTableRowAtSelection, $insertTableRow__EXPERIMENTAL, $isScrollableTablesActive, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, $mergeCells, $removeTableRowAtIndex, $unmergeCell, INSERT_TABLE_COMMAND, TableCellHeaderStates, TableCellNode, TableNode, TableObserver, TableRowNode, applyTableHandlers, getDOMCellFromTarget, getTableElement, getTableObserverFromTableElement, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive };
@@ -239,6 +239,7 @@ declare export function $removeTableRowAtIndex(
239
239
  indexToDelete: number,
240
240
  ): TableNode;
241
241
 
242
+ /** @deprecated This function does not support merged cells. Use {@link $insertTableRowAtSelection} or {@link $insertTableRowAtNode} instead. */
242
243
  declare export function $insertTableRow(
243
244
  tableNode: TableNode,
244
245
  targetIndex: number,
@@ -247,6 +248,7 @@ declare export function $insertTableRow(
247
248
  table: TableDOMTable,
248
249
  ): TableNode;
249
250
 
251
+ /** @deprecated This function does not support merged cells. Use {@link $insertTableColumnAtSelection} or {@link $insertTableColumnAtNode} instead. */
250
252
  declare export function $insertTableColumn(
251
253
  tableNode: TableNode,
252
254
  targetIndex: number,
@@ -255,21 +257,49 @@ declare export function $insertTableColumn(
255
257
  table: TableDOMTable,
256
258
  ): TableNode;
257
259
 
260
+ /** @deprecated This function does not support merged cells. Use {@link $deleteTableColumnAtSelection} instead. */
258
261
  declare export function $deleteTableColumn(
259
262
  tableNode: TableNode,
260
263
  targetIndex: number,
261
264
  ): TableNode;
262
265
 
266
+ declare export function $insertTableRowAtSelection(
267
+ insertAfter?: boolean,
268
+ ): TableRowNode | null;
269
+
270
+ declare export function $insertTableRowAtNode(
271
+ cellNode: TableCellNode,
272
+ insertAfter?: boolean,
273
+ ): TableRowNode | null;
274
+
275
+ declare export function $insertTableColumnAtSelection(
276
+ insertAfter?: boolean,
277
+ ): TableCellNode | null;
278
+
279
+ declare export function $insertTableColumnAtNode(
280
+ cellNode: TableCellNode,
281
+ insertAfter?: boolean,
282
+ shouldSetSelection?: boolean,
283
+ ): TableCellNode | null
284
+
285
+ declare export function $deleteTableRowAtSelection(): void;
286
+
287
+ declare export function $deleteTableColumnAtSelection(): void;
288
+
289
+ /** @deprecated renamed to {@link $insertTableRowAtSelection} */
263
290
  declare export function $insertTableRow__EXPERIMENTAL(
264
291
  insertAfter: boolean,
265
292
  ): TableRowNode | null;
266
293
 
294
+ /** @deprecated renamed to {@link $insertTableColumnAtSelection} */
267
295
  declare export function $insertTableColumn__EXPERIMENTAL(
268
296
  insertAfter: boolean,
269
297
  ): TableCellNode | null;
270
298
 
299
+ /** @deprecated renamed to {@link $deleteTableRowAtSelection} */
271
300
  declare export function $deleteTableRow__EXPERIMENTAL(): void;
272
301
 
302
+ /** @deprecated renamed to {@link $deleteTableColumnAtSelection} */
273
303
  declare export function $deleteTableColumn__EXPERIMENTAL(): void;
274
304
 
275
305
  declare export function $unmergeCell(): void;
package/LexicalTable.mjs CHANGED
@@ -18,7 +18,9 @@ export const $createTableRowNode = mod.$createTableRowNode;
18
18
  export const $createTableSelection = mod.$createTableSelection;
19
19
  export const $createTableSelectionFrom = mod.$createTableSelectionFrom;
20
20
  export const $deleteTableColumn = mod.$deleteTableColumn;
21
+ export const $deleteTableColumnAtSelection = mod.$deleteTableColumnAtSelection;
21
22
  export const $deleteTableColumn__EXPERIMENTAL = mod.$deleteTableColumn__EXPERIMENTAL;
23
+ export const $deleteTableRowAtSelection = mod.$deleteTableRowAtSelection;
22
24
  export const $deleteTableRow__EXPERIMENTAL = mod.$deleteTableRow__EXPERIMENTAL;
23
25
  export const $findCellNode = mod.$findCellNode;
24
26
  export const $findTableNode = mod.$findTableNode;
@@ -32,14 +34,17 @@ export const $getTableNodeFromLexicalNodeOrThrow = mod.$getTableNodeFromLexicalN
32
34
  export const $getTableRowIndexFromTableCellNode = mod.$getTableRowIndexFromTableCellNode;
33
35
  export const $getTableRowNodeFromTableCellNodeOrThrow = mod.$getTableRowNodeFromTableCellNodeOrThrow;
34
36
  export const $insertTableColumn = mod.$insertTableColumn;
37
+ export const $insertTableColumnAtSelection = mod.$insertTableColumnAtSelection;
35
38
  export const $insertTableColumn__EXPERIMENTAL = mod.$insertTableColumn__EXPERIMENTAL;
36
39
  export const $insertTableRow = mod.$insertTableRow;
40
+ export const $insertTableRowAtSelection = mod.$insertTableRowAtSelection;
37
41
  export const $insertTableRow__EXPERIMENTAL = mod.$insertTableRow__EXPERIMENTAL;
38
42
  export const $isScrollableTablesActive = mod.$isScrollableTablesActive;
39
43
  export const $isTableCellNode = mod.$isTableCellNode;
40
44
  export const $isTableNode = mod.$isTableNode;
41
45
  export const $isTableRowNode = mod.$isTableRowNode;
42
46
  export const $isTableSelection = mod.$isTableSelection;
47
+ export const $mergeCells = mod.$mergeCells;
43
48
  export const $removeTableRowAtIndex = mod.$removeTableRowAtIndex;
44
49
  export const $unmergeCell = mod.$unmergeCell;
45
50
  export const INSERT_TABLE_COMMAND = mod.INSERT_TABLE_COMMAND;
@@ -16,7 +16,9 @@ export const $createTableRowNode = mod.$createTableRowNode;
16
16
  export const $createTableSelection = mod.$createTableSelection;
17
17
  export const $createTableSelectionFrom = mod.$createTableSelectionFrom;
18
18
  export const $deleteTableColumn = mod.$deleteTableColumn;
19
+ export const $deleteTableColumnAtSelection = mod.$deleteTableColumnAtSelection;
19
20
  export const $deleteTableColumn__EXPERIMENTAL = mod.$deleteTableColumn__EXPERIMENTAL;
21
+ export const $deleteTableRowAtSelection = mod.$deleteTableRowAtSelection;
20
22
  export const $deleteTableRow__EXPERIMENTAL = mod.$deleteTableRow__EXPERIMENTAL;
21
23
  export const $findCellNode = mod.$findCellNode;
22
24
  export const $findTableNode = mod.$findTableNode;
@@ -30,14 +32,17 @@ export const $getTableNodeFromLexicalNodeOrThrow = mod.$getTableNodeFromLexicalN
30
32
  export const $getTableRowIndexFromTableCellNode = mod.$getTableRowIndexFromTableCellNode;
31
33
  export const $getTableRowNodeFromTableCellNodeOrThrow = mod.$getTableRowNodeFromTableCellNodeOrThrow;
32
34
  export const $insertTableColumn = mod.$insertTableColumn;
35
+ export const $insertTableColumnAtSelection = mod.$insertTableColumnAtSelection;
33
36
  export const $insertTableColumn__EXPERIMENTAL = mod.$insertTableColumn__EXPERIMENTAL;
34
37
  export const $insertTableRow = mod.$insertTableRow;
38
+ export const $insertTableRowAtSelection = mod.$insertTableRowAtSelection;
35
39
  export const $insertTableRow__EXPERIMENTAL = mod.$insertTableRow__EXPERIMENTAL;
36
40
  export const $isScrollableTablesActive = mod.$isScrollableTablesActive;
37
41
  export const $isTableCellNode = mod.$isTableCellNode;
38
42
  export const $isTableNode = mod.$isTableNode;
39
43
  export const $isTableRowNode = mod.$isTableRowNode;
40
44
  export const $isTableSelection = mod.$isTableSelection;
45
+ export const $mergeCells = mod.$mergeCells;
41
46
  export const $removeTableRowAtIndex = mod.$removeTableRowAtIndex;
42
47
  export const $unmergeCell = mod.$unmergeCell;
43
48
  export const INSERT_TABLE_COMMAND = mod.INSERT_TABLE_COMMAND;