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