@lexical/table 0.39.1-nightly.20260116.0 → 0.39.1-nightly.20260121.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, $isParagraphNode, $createPoint, $getNodeByKey, $isElementNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $createRangeSelection, $isRootNode, INSERT_PARAGRAPH_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, 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, isDOMNode, $isRootOrShadowRoot, $caretFromPoint, $isExtendableTextPointCaret, $extendCaretToRange, $isSiblingCaret, $getSiblingCaret, $setPointFromCaret, $normalizeCaret, $isChildCaret, $getChildCaret, $getAdjacentChildCaret, setDOMUnmanaged, COMMAND_PRIORITY_EDITOR, SELECT_ALL_COMMAND, COMMAND_PRIORITY_LOW, CLICK_COMMAND, $getRoot, defineExtension, safeCast } from 'lexical';
10
+ import { ElementNode, isHTMLElement, $isInlineElementOrDecoratorNode, $isTextNode, $isLineBreakNode, $createParagraphNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $isParagraphNode, $createPoint, $getNodeByKey, $isElementNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $createRangeSelection, $isRootNode, INSERT_PARAGRAPH_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, CUT_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, $getPreviousSelection, $getNearestNodeFromDOMNode, $createRangeSelectionFromDom, isDOMNode, $isRootOrShadowRoot, $caretFromPoint, $isExtendableTextPointCaret, $extendCaretToRange, $isSiblingCaret, $getSiblingCaret, $setPointFromCaret, $normalizeCaret, $isChildCaret, $getChildCaret, $getAdjacentChildCaret, setDOMUnmanaged, COMMAND_PRIORITY_EDITOR, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SELECT_ALL_COMMAND, COMMAND_PRIORITY_LOW, CLICK_COMMAND, $getRoot, defineExtension, safeCast } from 'lexical';
11
11
  import { signal, effect, namedSignals } from '@lexical/extension';
12
12
  import { copyToClipboard, $getClipboardDataFromSelection } from '@lexical/clipboard';
13
13
 
@@ -2006,23 +2006,42 @@ class TableObserver {
2006
2006
  const cellX = cell.x;
2007
2007
  const cellY = cell.y;
2008
2008
  this.focusCell = cell;
2009
- if (!this.isHighlightingCells && (this.anchorX !== cellX || this.anchorY !== cellY || ignoreStart)) {
2010
- this.isHighlightingCells = true;
2011
- this.$disableHighlightStyle();
2012
- } else if (cellX === this.focusX && cellY === this.focusY) {
2009
+
2010
+ // Enable highlighting if: ignoreStart is true, or anchor differs from focus,
2011
+ // or we have valid tableSelection with anchor (for first drag after column switch)
2012
+ if (!this.isHighlightingCells) {
2013
+ const shouldEnable = ignoreStart || this.anchorX !== cellX || this.anchorY !== cellY || this.tableSelection != null && this.anchorCellNodeKey != null;
2014
+ if (shouldEnable) {
2015
+ this.isHighlightingCells = true;
2016
+ this.$disableHighlightStyle();
2017
+ }
2018
+ }
2019
+
2020
+ // Skip if we're trying to select the same cell we already have selected
2021
+ // But only if focusX/focusY are valid (not -1, which means not reset)
2022
+ if (this.focusX !== -1 && this.focusY !== -1 && cellX === this.focusX && cellY === this.focusY) {
2013
2023
  return false;
2014
2024
  }
2015
2025
  this.focusX = cellX;
2016
2026
  this.focusY = cellY;
2017
2027
  if (this.isHighlightingCells) {
2018
2028
  const focusTableCellNode = $getNearestTableCellInTableFromDOMNode(tableNode, cell.elem);
2019
- if (this.tableSelection != null && this.anchorCellNodeKey != null && focusTableCellNode !== null) {
2020
- this.focusCellNodeKey = focusTableCellNode.getKey();
2021
- this.tableSelection = $createTableSelectionFrom(tableNode, this.$getAnchorTableCellOrThrow(), focusTableCellNode);
2022
- $setSelection(this.tableSelection);
2023
- editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
2024
- $updateDOMForSelection(editor, this.table, this.tableSelection);
2025
- return true;
2029
+ if (this.tableSelection != null && this.anchorCellNodeKey != null) {
2030
+ let targetCellNode = focusTableCellNode;
2031
+
2032
+ // Fallback: use coordinates if DOM lookup failed (handles timing issues on first drag)
2033
+ if (targetCellNode === null && ignoreStart) {
2034
+ targetCellNode = tableNode.getCellNodeFromCords(cellX, cellY, this.table);
2035
+ }
2036
+ if (targetCellNode !== null) {
2037
+ const anchorTableCell = this.$getAnchorTableCellOrThrow();
2038
+ this.focusCellNodeKey = targetCellNode.getKey();
2039
+ this.tableSelection = $createTableSelectionFrom(tableNode, anchorTableCell, targetCellNode);
2040
+ $setSelection(this.tableSelection);
2041
+ editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
2042
+ $updateDOMForSelection(editor, this.table, this.tableSelection);
2043
+ return true;
2044
+ }
2026
2045
  }
2027
2046
  }
2028
2047
  return false;
@@ -2052,13 +2071,23 @@ class TableObserver {
2052
2071
  this.anchorCell = cell;
2053
2072
  this.anchorX = cell.x;
2054
2073
  this.anchorY = cell.y;
2074
+ // Reset focus state to prevent stale values from previous selections
2075
+ this.focusX = -1;
2076
+ this.focusY = -1;
2077
+ this.focusCell = null;
2078
+ this.focusCellNodeKey = null;
2055
2079
  const {
2056
2080
  tableNode
2057
2081
  } = this.$lookup();
2058
2082
  const anchorTableCellNode = $getNearestTableCellInTableFromDOMNode(tableNode, cell.elem);
2059
2083
  if (anchorTableCellNode !== null) {
2060
2084
  const anchorNodeKey = anchorTableCellNode.getKey();
2061
- this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
2085
+ if (this.tableSelection != null) {
2086
+ this.tableSelection = this.tableSelection.clone();
2087
+ this.tableSelection.set(tableNode.getKey(), anchorNodeKey, anchorNodeKey);
2088
+ } else {
2089
+ this.tableSelection = $createTableSelectionFrom(tableNode, anchorTableCellNode, anchorTableCellNode);
2090
+ }
2062
2091
  this.anchorCellNodeKey = anchorNodeKey;
2063
2092
  }
2064
2093
  }
@@ -2175,10 +2204,18 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2175
2204
  const tableElement = getTableElement(tableNode, element);
2176
2205
  attachTableObserverToTableElement(tableElement, tableObserver);
2177
2206
  tableObserver.listenersToRemove.add(() => detachTableObserverFromTableElement(tableElement, tableObserver));
2178
- const createPointerHandlers = () => {
2207
+ const createPointerHandlers = startingCell => {
2179
2208
  if (tableObserver.isSelecting) {
2180
2209
  return;
2181
2210
  }
2211
+ tableObserver.isSelecting = true;
2212
+
2213
+ // Set anchor immediately if starting cell provided (handles direct drag without click)
2214
+ if (startingCell !== null && tableObserver.anchorCell === null) {
2215
+ editor.update(() => {
2216
+ tableObserver.$setAnchorCellForSelection(startingCell);
2217
+ });
2218
+ }
2182
2219
  const onPointerUp = () => {
2183
2220
  tableObserver.isSelecting = false;
2184
2221
  editorWindow.removeEventListener('pointerup', onPointerUp);
@@ -2208,15 +2245,23 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2208
2245
  }
2209
2246
  }
2210
2247
  }
2211
- if (focusCell && (tableObserver.focusCell === null || focusCell.elem !== tableObserver.focusCell.elem)) {
2212
- tableObserver.setNextFocus({
2213
- focusCell,
2214
- override
2215
- });
2216
- editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
2248
+ if (focusCell) {
2249
+ const anchorCell = focusCell;
2250
+ // Fallback: set anchor if still missing (handles race conditions)
2251
+ if (tableObserver.anchorCell === null) {
2252
+ editor.update(() => {
2253
+ tableObserver.$setAnchorCellForSelection(anchorCell);
2254
+ });
2255
+ }
2256
+ if (tableObserver.focusCell === null || focusCell.elem !== tableObserver.focusCell.elem) {
2257
+ tableObserver.setNextFocus({
2258
+ focusCell,
2259
+ override
2260
+ });
2261
+ editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
2262
+ }
2217
2263
  }
2218
2264
  };
2219
- tableObserver.isSelecting = true;
2220
2265
  editorWindow.addEventListener('pointerup', onPointerUp, tableObserver.listenerOptions);
2221
2266
  editorWindow.addEventListener('pointermove', onPointerMove, tableObserver.listenerOptions);
2222
2267
  };
@@ -2252,7 +2297,10 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2252
2297
  }
2253
2298
  });
2254
2299
  }
2255
- createPointerHandlers();
2300
+
2301
+ // Pass the target cell to createPointerHandlers so it can be used as anchor
2302
+ // if user drags directly without clicking first
2303
+ createPointerHandlers(targetCell);
2256
2304
  };
2257
2305
  tableElement.addEventListener('pointerdown', onPointerDown, tableObserver.listenerOptions);
2258
2306
  tableObserver.listenersToRemove.add(() => {
@@ -2499,133 +2547,6 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2499
2547
  tableObserver.listenersToRemove.add(editor.registerCommand(FOCUS_COMMAND, payload => {
2500
2548
  return tableNode.isSelected();
2501
2549
  }, COMMAND_PRIORITY_HIGH));
2502
- tableObserver.listenersToRemove.add(editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, (selectionPayload, dispatchEditor) => {
2503
- if (editor !== dispatchEditor) {
2504
- return false;
2505
- }
2506
- const {
2507
- nodes,
2508
- selection
2509
- } = selectionPayload;
2510
- const anchorAndFocus = selection.getStartEndPoints();
2511
- const isTableSelection = $isTableSelection(selection);
2512
- const isRangeSelection = $isRangeSelection(selection);
2513
- const isSelectionInsideOfGrid = isRangeSelection && $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n)) !== null && $findMatchingParent(selection.focus.getNode(), n => $isTableCellNode(n)) !== null || isTableSelection;
2514
- if (nodes.length !== 1 || !$isTableNode(nodes[0]) || !isSelectionInsideOfGrid || anchorAndFocus === null) {
2515
- return false;
2516
- }
2517
- const [anchor, focus] = anchorAndFocus;
2518
- const [anchorCellNode, anchorRowNode, gridNode] = $getNodeTriplet(anchor);
2519
- const focusCellNode = $findMatchingParent(focus.getNode(), n => $isTableCellNode(n));
2520
- if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableRowNode(anchorRowNode) || !$isTableNode(gridNode)) {
2521
- return false;
2522
- }
2523
- const templateGrid = nodes[0];
2524
- const [initialGridMap, anchorCellMap, focusCellMap] = $computeTableMap(gridNode, anchorCellNode, focusCellNode);
2525
- const [templateGridMap] = $computeTableMapSkipCellCheck(templateGrid, null, null);
2526
- const initialRowCount = initialGridMap.length;
2527
- const initialColCount = initialRowCount > 0 ? initialGridMap[0].length : 0;
2528
-
2529
- // If we have a range selection, we'll fit the template grid into the
2530
- // table, growing the table if necessary.
2531
- let startRow = anchorCellMap.startRow;
2532
- let startCol = anchorCellMap.startColumn;
2533
- let affectedRowCount = templateGridMap.length;
2534
- let affectedColCount = affectedRowCount > 0 ? templateGridMap[0].length : 0;
2535
- if (isTableSelection) {
2536
- // If we have a table selection, we'll only modify the cells within
2537
- // the selection boundary.
2538
- const selectionBoundary = $computeTableCellRectBoundary(initialGridMap, anchorCellMap, focusCellMap);
2539
- const selectionRowCount = selectionBoundary.maxRow - selectionBoundary.minRow + 1;
2540
- const selectionColCount = selectionBoundary.maxColumn - selectionBoundary.minColumn + 1;
2541
- startRow = selectionBoundary.minRow;
2542
- startCol = selectionBoundary.minColumn;
2543
- affectedRowCount = Math.min(affectedRowCount, selectionRowCount);
2544
- affectedColCount = Math.min(affectedColCount, selectionColCount);
2545
- }
2546
-
2547
- // Step 1: Unmerge all merged cells within the affected area
2548
- let didPerformMergeOperations = false;
2549
- const lastRowForUnmerge = Math.min(initialRowCount, startRow + affectedRowCount) - 1;
2550
- const lastColForUnmerge = Math.min(initialColCount, startCol + affectedColCount) - 1;
2551
- const unmergedKeys = new Set();
2552
- for (let row = startRow; row <= lastRowForUnmerge; row++) {
2553
- for (let col = startCol; col <= lastColForUnmerge; col++) {
2554
- const cellMap = initialGridMap[row][col];
2555
- if (unmergedKeys.has(cellMap.cell.getKey())) {
2556
- continue; // cell was a merged cell that was already handled
2557
- }
2558
- if (cellMap.cell.__rowSpan === 1 && cellMap.cell.__colSpan === 1) {
2559
- continue; // cell is not a merged cell
2560
- }
2561
- $unmergeCellNode(cellMap.cell);
2562
- unmergedKeys.add(cellMap.cell.getKey());
2563
- didPerformMergeOperations = true;
2564
- }
2565
- }
2566
- let [interimGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
2567
-
2568
- // Step 2: Expand current table (if needed)
2569
- const rowsToInsert = affectedRowCount - initialRowCount + startRow;
2570
- for (let i = 0; i < rowsToInsert; i++) {
2571
- const cellMap = interimGridMap[initialRowCount - 1][0];
2572
- $insertTableRowAtNode(cellMap.cell);
2573
- }
2574
- const colsToInsert = affectedColCount - initialColCount + startCol;
2575
- for (let i = 0; i < colsToInsert; i++) {
2576
- const cellMap = interimGridMap[0][initialColCount - 1];
2577
- $insertTableColumnAtNode(cellMap.cell, true, false);
2578
- }
2579
- [interimGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
2580
-
2581
- // Step 3: Merge cells and set cell content, to match template grid
2582
- for (let row = startRow; row < startRow + affectedRowCount; row++) {
2583
- for (let col = startCol; col < startCol + affectedColCount; col++) {
2584
- const templateRow = row - startRow;
2585
- const templateCol = col - startCol;
2586
- const templateCellMap = templateGridMap[templateRow][templateCol];
2587
- if (templateCellMap.startRow !== templateRow || templateCellMap.startColumn !== templateCol) {
2588
- continue; // cell is a merged cell that was already handled
2589
- }
2590
- const templateCell = templateCellMap.cell;
2591
- if (templateCell.__rowSpan !== 1 || templateCell.__colSpan !== 1) {
2592
- const cellsToMerge = [];
2593
- const lastRowForMerge = Math.min(row + templateCell.__rowSpan, startRow + affectedRowCount) - 1;
2594
- const lastColForMerge = Math.min(col + templateCell.__colSpan, startCol + affectedColCount) - 1;
2595
- for (let r = row; r <= lastRowForMerge; r++) {
2596
- for (let c = col; c <= lastColForMerge; c++) {
2597
- const cellMap = interimGridMap[r][c];
2598
- cellsToMerge.push(cellMap.cell);
2599
- }
2600
- }
2601
- $mergeCells(cellsToMerge);
2602
- didPerformMergeOperations = true;
2603
- }
2604
- const {
2605
- cell
2606
- } = interimGridMap[row][col];
2607
- const originalChildren = cell.getChildren();
2608
- templateCell.getChildren().forEach(child => {
2609
- if ($isTextNode(child)) {
2610
- const paragraphNode = $createParagraphNode();
2611
- paragraphNode.append(child);
2612
- cell.append(child);
2613
- } else {
2614
- cell.append(child);
2615
- }
2616
- });
2617
- originalChildren.forEach(n => n.remove());
2618
- }
2619
- }
2620
- if (isTableSelection && didPerformMergeOperations) {
2621
- // reset the table selection in case the anchor or focus cell was
2622
- // removed via merge operations
2623
- const [finalGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
2624
- const newAnchorCellMap = finalGridMap[anchorCellMap.startRow][anchorCellMap.startColumn];
2625
- newAnchorCellMap.cell.selectEnd();
2626
- }
2627
- return true;
2628
- }, COMMAND_PRIORITY_HIGH));
2629
2550
  tableObserver.listenersToRemove.add(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
2630
2551
  const selection = $getSelection();
2631
2552
  const prevSelection = $getPreviousSelection();
@@ -2642,9 +2563,14 @@ function applyTableHandlers(tableNode, element, editor, hasTabHandler) {
2642
2563
  tableObserver.$setFocusCellForSelection(focusCell);
2643
2564
  return true;
2644
2565
  }
2645
- } else if (focusCell !== tableObserver.anchorCell && $isSelectionInTable(selection, tableNode)) {
2566
+ } else if (focusCell !== tableObserver.anchorCell && tableObserver.anchorCell !== null && tableObserver.anchorCellNodeKey !== null && tableObserver.tableSelection !== null) {
2646
2567
  // The selection has crossed cells
2647
- tableObserver.$setFocusCellForSelection(focusCell);
2568
+ // If we have an anchor cell set and tableSelection initialized,
2569
+ // we have all the necessary state to create the selection.
2570
+ // The presence of nextFocus means we're dragging, so process it.
2571
+ // Use ignoreStart=true to ensure isHighlightingCells is set correctly
2572
+ // on the first drag attempt, especially when switching columns.
2573
+ tableObserver.$setFocusCellForSelection(focusCell, true);
2648
2574
  return true;
2649
2575
  }
2650
2576
  }
@@ -4227,7 +4153,7 @@ function registerTableSelectionObserver(editor, hasTabHandler = true) {
4227
4153
  }
4228
4154
 
4229
4155
  /**
4230
- * Register the INSERT_TABLE_COMMAND listener and the table integrity transforms. The
4156
+ * Register table command listeners and the table integrity transforms. The
4231
4157
  * table selection observer should be registered separately after this with
4232
4158
  * {@link registerTableSelectionObserver}.
4233
4159
  *
@@ -4245,10 +4171,17 @@ function registerTablePlugin(editor, options) {
4245
4171
  } = options ?? {};
4246
4172
  return mergeRegister(editor.registerCommand(INSERT_TABLE_COMMAND, payload => {
4247
4173
  return $insertTable(payload, hasNestedTables.peek());
4248
- }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, ({
4249
- nodes,
4250
- selection
4251
- }, dispatchEditor) => {
4174
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, (selectionPayload, dispatchEditor) => {
4175
+ if (editor !== dispatchEditor) {
4176
+ return false;
4177
+ }
4178
+ if ($tableSelectionInsertClipboardNodesCommand(selectionPayload)) {
4179
+ return true;
4180
+ }
4181
+ const {
4182
+ selection,
4183
+ nodes
4184
+ } = selectionPayload;
4252
4185
  if (hasNestedTables.peek() || editor !== dispatchEditor || !$isRangeSelection(selection)) {
4253
4186
  return false;
4254
4187
  }
@@ -4256,6 +4189,130 @@ function registerTablePlugin(editor, options) {
4256
4189
  return isInsideTableCell && nodes.some($isTableNode);
4257
4190
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(SELECT_ALL_COMMAND, $tableSelectAllCommand, COMMAND_PRIORITY_LOW), editor.registerCommand(CLICK_COMMAND, $tableClickCommand, COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(TableNode, $tableTransform), editor.registerNodeTransform(TableRowNode, $tableRowTransform), editor.registerNodeTransform(TableCellNode, $tableCellTransform));
4258
4191
  }
4192
+ function $tableSelectionInsertClipboardNodesCommand(selectionPayload) {
4193
+ const {
4194
+ nodes,
4195
+ selection
4196
+ } = selectionPayload;
4197
+ const anchorAndFocus = selection.getStartEndPoints();
4198
+ const isTableSelection = $isTableSelection(selection);
4199
+ const isRangeSelection = $isRangeSelection(selection);
4200
+ const isSelectionInsideOfGrid = isRangeSelection && $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n)) !== null && $findMatchingParent(selection.focus.getNode(), n => $isTableCellNode(n)) !== null || isTableSelection;
4201
+ if (nodes.length !== 1 || !$isTableNode(nodes[0]) || !isSelectionInsideOfGrid || anchorAndFocus === null) {
4202
+ return false;
4203
+ }
4204
+ const [anchor, focus] = anchorAndFocus;
4205
+ const [anchorCellNode, anchorRowNode, gridNode] = $getNodeTriplet(anchor);
4206
+ const focusCellNode = $findMatchingParent(focus.getNode(), n => $isTableCellNode(n));
4207
+ if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableRowNode(anchorRowNode) || !$isTableNode(gridNode)) {
4208
+ return false;
4209
+ }
4210
+ const templateGrid = nodes[0];
4211
+ const [initialGridMap, anchorCellMap, focusCellMap] = $computeTableMap(gridNode, anchorCellNode, focusCellNode);
4212
+ const [templateGridMap] = $computeTableMapSkipCellCheck(templateGrid, null, null);
4213
+ const initialRowCount = initialGridMap.length;
4214
+ const initialColCount = initialRowCount > 0 ? initialGridMap[0].length : 0;
4215
+
4216
+ // If we have a range selection, we'll fit the template grid into the
4217
+ // table, growing the table if necessary.
4218
+ let startRow = anchorCellMap.startRow;
4219
+ let startCol = anchorCellMap.startColumn;
4220
+ let affectedRowCount = templateGridMap.length;
4221
+ let affectedColCount = affectedRowCount > 0 ? templateGridMap[0].length : 0;
4222
+ if (isTableSelection) {
4223
+ // If we have a table selection, we'll only modify the cells within
4224
+ // the selection boundary.
4225
+ const selectionBoundary = $computeTableCellRectBoundary(initialGridMap, anchorCellMap, focusCellMap);
4226
+ const selectionRowCount = selectionBoundary.maxRow - selectionBoundary.minRow + 1;
4227
+ const selectionColCount = selectionBoundary.maxColumn - selectionBoundary.minColumn + 1;
4228
+ startRow = selectionBoundary.minRow;
4229
+ startCol = selectionBoundary.minColumn;
4230
+ affectedRowCount = Math.min(affectedRowCount, selectionRowCount);
4231
+ affectedColCount = Math.min(affectedColCount, selectionColCount);
4232
+ }
4233
+
4234
+ // Step 1: Unmerge all merged cells within the affected area
4235
+ let didPerformMergeOperations = false;
4236
+ const lastRowForUnmerge = Math.min(initialRowCount, startRow + affectedRowCount) - 1;
4237
+ const lastColForUnmerge = Math.min(initialColCount, startCol + affectedColCount) - 1;
4238
+ const unmergedKeys = new Set();
4239
+ for (let row = startRow; row <= lastRowForUnmerge; row++) {
4240
+ for (let col = startCol; col <= lastColForUnmerge; col++) {
4241
+ const cellMap = initialGridMap[row][col];
4242
+ if (unmergedKeys.has(cellMap.cell.getKey())) {
4243
+ continue; // cell was a merged cell that was already handled
4244
+ }
4245
+ if (cellMap.cell.__rowSpan === 1 && cellMap.cell.__colSpan === 1) {
4246
+ continue; // cell is not a merged cell
4247
+ }
4248
+ $unmergeCellNode(cellMap.cell);
4249
+ unmergedKeys.add(cellMap.cell.getKey());
4250
+ didPerformMergeOperations = true;
4251
+ }
4252
+ }
4253
+ let [interimGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
4254
+
4255
+ // Step 2: Expand current table (if needed)
4256
+ const rowsToInsert = affectedRowCount - initialRowCount + startRow;
4257
+ for (let i = 0; i < rowsToInsert; i++) {
4258
+ const cellMap = interimGridMap[initialRowCount - 1][0];
4259
+ $insertTableRowAtNode(cellMap.cell);
4260
+ }
4261
+ const colsToInsert = affectedColCount - initialColCount + startCol;
4262
+ for (let i = 0; i < colsToInsert; i++) {
4263
+ const cellMap = interimGridMap[0][initialColCount - 1];
4264
+ $insertTableColumnAtNode(cellMap.cell, true, false);
4265
+ }
4266
+ [interimGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
4267
+
4268
+ // Step 3: Merge cells and set cell content, to match template grid
4269
+ for (let row = startRow; row < startRow + affectedRowCount; row++) {
4270
+ for (let col = startCol; col < startCol + affectedColCount; col++) {
4271
+ const templateRow = row - startRow;
4272
+ const templateCol = col - startCol;
4273
+ const templateCellMap = templateGridMap[templateRow][templateCol];
4274
+ if (templateCellMap.startRow !== templateRow || templateCellMap.startColumn !== templateCol) {
4275
+ continue; // cell is a merged cell that was already handled
4276
+ }
4277
+ const templateCell = templateCellMap.cell;
4278
+ if (templateCell.__rowSpan !== 1 || templateCell.__colSpan !== 1) {
4279
+ const cellsToMerge = [];
4280
+ const lastRowForMerge = Math.min(row + templateCell.__rowSpan, startRow + affectedRowCount) - 1;
4281
+ const lastColForMerge = Math.min(col + templateCell.__colSpan, startCol + affectedColCount) - 1;
4282
+ for (let r = row; r <= lastRowForMerge; r++) {
4283
+ for (let c = col; c <= lastColForMerge; c++) {
4284
+ const cellMap = interimGridMap[r][c];
4285
+ cellsToMerge.push(cellMap.cell);
4286
+ }
4287
+ }
4288
+ $mergeCells(cellsToMerge);
4289
+ didPerformMergeOperations = true;
4290
+ }
4291
+ const {
4292
+ cell
4293
+ } = interimGridMap[row][col];
4294
+ const originalChildren = cell.getChildren();
4295
+ templateCell.getChildren().forEach(child => {
4296
+ if ($isTextNode(child)) {
4297
+ const paragraphNode = $createParagraphNode();
4298
+ paragraphNode.append(child);
4299
+ cell.append(child);
4300
+ } else {
4301
+ cell.append(child);
4302
+ }
4303
+ });
4304
+ originalChildren.forEach(n => n.remove());
4305
+ }
4306
+ }
4307
+ if (isTableSelection && didPerformMergeOperations) {
4308
+ // reset the table selection in case the anchor or focus cell was
4309
+ // removed via merge operations
4310
+ const [finalGridMap] = $computeTableMapSkipCellCheck(gridNode.getWritable(), null, null);
4311
+ const newAnchorCellMap = finalGridMap[anchorCellMap.startRow][anchorCellMap.startColumn];
4312
+ newAnchorCellMap.cell.selectEnd();
4313
+ }
4314
+ return true;
4315
+ }
4259
4316
 
4260
4317
  /**
4261
4318
  * Copyright (c) Meta Platforms, Inc. and affiliates.