@lexical/table 0.39.1-nightly.20260120.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.mjs
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
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
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
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
|
-
|
|
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
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
-
|
|
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 &&
|
|
2566
|
+
} else if (focusCell !== tableObserver.anchorCell && tableObserver.anchorCell !== null && tableObserver.anchorCellNodeKey !== null && tableObserver.tableSelection !== null) {
|
|
2646
2567
|
// The selection has crossed cells
|
|
2647
|
-
|
|
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
|
|
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
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
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.
|