@lexical/table 0.14.2 → 0.14.3
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 +205 -19
- package/{LexicalTable.dev.esm.js → LexicalTable.dev.mjs} +206 -20
- package/{LexicalTable.esm.js → LexicalTable.mjs} +2 -2
- package/LexicalTable.node.mjs +43 -0
- package/LexicalTable.prod.js +82 -76
- package/LexicalTable.prod.mjs +7 -0
- package/LexicalTableObserver.d.ts +1 -0
- package/LexicalTableSelectionHelpers.d.ts +2 -0
- package/package.json +19 -7
- package/LexicalTable.prod.esm.js +0 -7
package/LexicalTable.dev.js
CHANGED
@@ -1296,7 +1296,6 @@ function $getChildrenRecursively(node) {
|
|
1296
1296
|
* LICENSE file in the root directory of this source tree.
|
1297
1297
|
*
|
1298
1298
|
*/
|
1299
|
-
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
1300
1299
|
class TableObserver {
|
1301
1300
|
constructor(editor, tableNodeKey) {
|
1302
1301
|
this.isHighlightingCells = false;
|
@@ -1319,6 +1318,7 @@ class TableObserver {
|
|
1319
1318
|
this.focusCell = null;
|
1320
1319
|
this.hasHijackedSelectionStyles = false;
|
1321
1320
|
this.trackTable();
|
1321
|
+
this.isSelecting = false;
|
1322
1322
|
}
|
1323
1323
|
getTable() {
|
1324
1324
|
return this.table;
|
@@ -1458,7 +1458,7 @@ class TableObserver {
|
|
1458
1458
|
this.focusY = cellY;
|
1459
1459
|
if (this.isHighlightingCells) {
|
1460
1460
|
const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
|
1461
|
-
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
|
1461
|
+
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode) && tableNode.is($findTableNode(focusTableCellNode))) {
|
1462
1462
|
const focusNodeKey = focusTableCellNode.getKey();
|
1463
1463
|
this.tableSelection = this.tableSelection.clone() || $createTableSelection();
|
1464
1464
|
this.focusCellNodeKey = focusNodeKey;
|
@@ -1556,6 +1556,7 @@ class TableObserver {
|
|
1556
1556
|
*
|
1557
1557
|
*/
|
1558
1558
|
const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
|
1559
|
+
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
1559
1560
|
function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
1560
1561
|
const rootElement = editor.getRootElement();
|
1561
1562
|
if (rootElement === null) {
|
@@ -1564,6 +1565,24 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1564
1565
|
const tableObserver = new TableObserver(editor, tableNode.getKey());
|
1565
1566
|
const editorWindow = editor._window || window;
|
1566
1567
|
attachTableObserverToTableElement(tableElement, tableObserver);
|
1568
|
+
const createMouseHandlers = () => {
|
1569
|
+
const onMouseUp = () => {
|
1570
|
+
tableObserver.isSelecting = false;
|
1571
|
+
editorWindow.removeEventListener('mouseup', onMouseUp);
|
1572
|
+
editorWindow.removeEventListener('mousemove', onMouseMove);
|
1573
|
+
};
|
1574
|
+
const onMouseMove = moveEvent => {
|
1575
|
+
const focusCell = getDOMCellFromTarget(moveEvent.target);
|
1576
|
+
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
|
1577
|
+
moveEvent.preventDefault();
|
1578
|
+
tableObserver.setFocusCellForSelection(focusCell);
|
1579
|
+
}
|
1580
|
+
};
|
1581
|
+
return {
|
1582
|
+
onMouseMove: onMouseMove,
|
1583
|
+
onMouseUp: onMouseUp
|
1584
|
+
};
|
1585
|
+
};
|
1567
1586
|
tableElement.addEventListener('mousedown', event => {
|
1568
1587
|
setTimeout(() => {
|
1569
1588
|
if (event.button !== 0) {
|
@@ -1577,17 +1596,11 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1577
1596
|
stopEvent(event);
|
1578
1597
|
tableObserver.setAnchorCellForSelection(anchorCell);
|
1579
1598
|
}
|
1580
|
-
const
|
1581
|
-
|
1582
|
-
|
1583
|
-
};
|
1584
|
-
|
1585
|
-
const focusCell = getDOMCellFromTarget(moveEvent.target);
|
1586
|
-
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
|
1587
|
-
moveEvent.preventDefault();
|
1588
|
-
tableObserver.setFocusCellForSelection(focusCell);
|
1589
|
-
}
|
1590
|
-
};
|
1599
|
+
const {
|
1600
|
+
onMouseUp,
|
1601
|
+
onMouseMove
|
1602
|
+
} = createMouseHandlers();
|
1603
|
+
tableObserver.isSelecting = true;
|
1591
1604
|
editorWindow.addEventListener('mouseup', onMouseUp);
|
1592
1605
|
editorWindow.addEventListener('mousemove', onMouseMove);
|
1593
1606
|
}, 0);
|
@@ -1752,6 +1765,13 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1752
1765
|
if (!$isTableCellNode(tableCellNode)) {
|
1753
1766
|
return false;
|
1754
1767
|
}
|
1768
|
+
if (typeof payload === 'string') {
|
1769
|
+
const edgePosition = $getTableEdgeCursorPosition(editor, selection, tableNode);
|
1770
|
+
if (edgePosition) {
|
1771
|
+
$insertParagraphAtTableEdge(edgePosition, tableNode, [lexical.$createTextNode(payload)]);
|
1772
|
+
return true;
|
1773
|
+
}
|
1774
|
+
}
|
1755
1775
|
}
|
1756
1776
|
return false;
|
1757
1777
|
}, lexical.COMMAND_PRIORITY_CRITICAL));
|
@@ -1882,7 +1902,11 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1882
1902
|
const isBackward = selection.isBackward();
|
1883
1903
|
if (isPartialyWithinTable) {
|
1884
1904
|
const newSelection = selection.clone();
|
1885
|
-
|
1905
|
+
if (isFocusInside) {
|
1906
|
+
newSelection.focus.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() : tableNode.getIndexWithinParent() + 1, 'element');
|
1907
|
+
} else {
|
1908
|
+
newSelection.anchor.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() + 1 : tableNode.getIndexWithinParent(), 'element');
|
1909
|
+
}
|
1886
1910
|
lexical.$setSelection(newSelection);
|
1887
1911
|
$addHighlightStyleToTable(editor, tableObserver);
|
1888
1912
|
} else if (isWithinTable) {
|
@@ -1891,6 +1915,34 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1891
1915
|
if (!anchorCellNode.is(focusCellNode)) {
|
1892
1916
|
tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
|
1893
1917
|
tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
|
1918
|
+
if (!tableObserver.isSelecting) {
|
1919
|
+
setTimeout(() => {
|
1920
|
+
const {
|
1921
|
+
onMouseUp,
|
1922
|
+
onMouseMove
|
1923
|
+
} = createMouseHandlers();
|
1924
|
+
tableObserver.isSelecting = true;
|
1925
|
+
editorWindow.addEventListener('mouseup', onMouseUp);
|
1926
|
+
editorWindow.addEventListener('mousemove', onMouseMove);
|
1927
|
+
}, 0);
|
1928
|
+
}
|
1929
|
+
}
|
1930
|
+
}
|
1931
|
+
} else if (selection && $isTableSelection(selection) && selection.is(prevSelection) && selection.tableKey === tableNode.getKey()) {
|
1932
|
+
// if selection goes outside of the table we need to change it to Range selection
|
1933
|
+
const domSelection = getDOMSelection(editor._window);
|
1934
|
+
if (domSelection && domSelection.anchorNode && domSelection.focusNode) {
|
1935
|
+
const focusNode = lexical.$getNearestNodeFromDOMNode(domSelection.focusNode);
|
1936
|
+
const isFocusOutside = focusNode && !tableNode.is($findTableNode(focusNode));
|
1937
|
+
const anchorNode = lexical.$getNearestNodeFromDOMNode(domSelection.anchorNode);
|
1938
|
+
const isAnchorInside = anchorNode && tableNode.is($findTableNode(anchorNode));
|
1939
|
+
if (isFocusOutside && isAnchorInside && domSelection.rangeCount > 0) {
|
1940
|
+
const newSelection = lexical.$createRangeSelectionFromDom(domSelection, editor);
|
1941
|
+
if (newSelection) {
|
1942
|
+
newSelection.anchor.set(tableNode.getKey(), selection.isBackward() ? tableNode.getChildrenSize() : 0, 'element');
|
1943
|
+
domSelection.removeAllRanges();
|
1944
|
+
lexical.$setSelection(newSelection);
|
1945
|
+
}
|
1894
1946
|
}
|
1895
1947
|
}
|
1896
1948
|
}
|
@@ -1909,6 +1961,18 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1909
1961
|
}
|
1910
1962
|
return false;
|
1911
1963
|
}, lexical.COMMAND_PRIORITY_CRITICAL));
|
1964
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(lexical.INSERT_PARAGRAPH_COMMAND, () => {
|
1965
|
+
const selection = lexical.$getSelection();
|
1966
|
+
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
|
1967
|
+
return false;
|
1968
|
+
}
|
1969
|
+
const edgePosition = $getTableEdgeCursorPosition(editor, selection, tableNode);
|
1970
|
+
if (edgePosition) {
|
1971
|
+
$insertParagraphAtTableEdge(edgePosition, tableNode);
|
1972
|
+
return true;
|
1973
|
+
}
|
1974
|
+
return false;
|
1975
|
+
}, lexical.COMMAND_PRIORITY_CRITICAL));
|
1912
1976
|
return tableObserver;
|
1913
1977
|
}
|
1914
1978
|
function attachTableObserverToTableElement(tableElement, tableObserver) {
|
@@ -2165,14 +2229,31 @@ function $findTableNode(node) {
|
|
2165
2229
|
function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
2166
2230
|
const selection = lexical.$getSelection();
|
2167
2231
|
if (!$isSelectionInTable(selection, tableNode)) {
|
2232
|
+
if (direction === 'backward' && lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
|
2233
|
+
const anchorType = selection.anchor.type;
|
2234
|
+
const anchorOffset = selection.anchor.offset;
|
2235
|
+
if (anchorType !== 'element' && !(anchorType === 'text' && anchorOffset === 0)) {
|
2236
|
+
return false;
|
2237
|
+
}
|
2238
|
+
const anchorNode = selection.anchor.getNode();
|
2239
|
+
if (!anchorNode) {
|
2240
|
+
return false;
|
2241
|
+
}
|
2242
|
+
const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
|
2243
|
+
if (!parentNode) {
|
2244
|
+
return false;
|
2245
|
+
}
|
2246
|
+
const siblingNode = parentNode.getPreviousSibling();
|
2247
|
+
if (!siblingNode || !$isTableNode(siblingNode)) {
|
2248
|
+
return false;
|
2249
|
+
}
|
2250
|
+
stopEvent(event);
|
2251
|
+
siblingNode.selectEnd();
|
2252
|
+
return true;
|
2253
|
+
}
|
2168
2254
|
return false;
|
2169
2255
|
}
|
2170
2256
|
if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
|
2171
|
-
// Horizontal move between cels seem to work well without interruption
|
2172
|
-
// so just exit early, and handle vertical moves
|
2173
|
-
if (direction === 'backward' || direction === 'forward') {
|
2174
|
-
return false;
|
2175
|
-
}
|
2176
2257
|
const {
|
2177
2258
|
anchor,
|
2178
2259
|
focus
|
@@ -2190,6 +2271,18 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2190
2271
|
return $handleArrowKey(editor, event, direction, anchorCellTable, tableObserver);
|
2191
2272
|
}
|
2192
2273
|
}
|
2274
|
+
if (direction === 'backward' || direction === 'forward') {
|
2275
|
+
const anchorType = anchor.type;
|
2276
|
+
const anchorOffset = anchor.offset;
|
2277
|
+
const anchorNode = anchor.getNode();
|
2278
|
+
if (!anchorNode) {
|
2279
|
+
return false;
|
2280
|
+
}
|
2281
|
+
if (isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction)) {
|
2282
|
+
return $handleTableExit(event, anchorNode, tableNode, direction);
|
2283
|
+
}
|
2284
|
+
return false;
|
2285
|
+
}
|
2193
2286
|
const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
|
2194
2287
|
const anchorDOM = editor.getElementByKey(anchor.key);
|
2195
2288
|
if (anchorDOM == null || anchorCellDom == null) {
|
@@ -2261,6 +2354,99 @@ function stopEvent(event) {
|
|
2261
2354
|
event.stopImmediatePropagation();
|
2262
2355
|
event.stopPropagation();
|
2263
2356
|
}
|
2357
|
+
function isExitingTableAnchor(type, offset, anchorNode, direction) {
|
2358
|
+
return isExitingTableElementAnchor(type, anchorNode, direction) || isExitingTableTextAnchor(type, offset, anchorNode, direction);
|
2359
|
+
}
|
2360
|
+
function isExitingTableElementAnchor(type, anchorNode, direction) {
|
2361
|
+
return type === 'element' && (direction === 'backward' ? anchorNode.getPreviousSibling() === null : anchorNode.getNextSibling() === null);
|
2362
|
+
}
|
2363
|
+
function isExitingTableTextAnchor(type, offset, anchorNode, direction) {
|
2364
|
+
const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
|
2365
|
+
if (!parentNode) {
|
2366
|
+
return false;
|
2367
|
+
}
|
2368
|
+
const hasValidOffset = direction === 'backward' ? offset === 0 : offset === anchorNode.getTextContentSize();
|
2369
|
+
return type === 'text' && hasValidOffset && (direction === 'backward' ? parentNode.getPreviousSibling() === null : parentNode.getNextSibling() === null);
|
2370
|
+
}
|
2371
|
+
function $handleTableExit(event, anchorNode, tableNode, direction) {
|
2372
|
+
const anchorCellNode = utils.$findMatchingParent(anchorNode, $isTableCellNode);
|
2373
|
+
if (!$isTableCellNode(anchorCellNode)) {
|
2374
|
+
return false;
|
2375
|
+
}
|
2376
|
+
const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
|
2377
|
+
if (!isExitingCell(tableMap, cellValue, direction)) {
|
2378
|
+
return false;
|
2379
|
+
}
|
2380
|
+
const toNode = getExitingToNode(anchorNode, direction, tableNode);
|
2381
|
+
if (!toNode || $isTableNode(toNode)) {
|
2382
|
+
return false;
|
2383
|
+
}
|
2384
|
+
stopEvent(event);
|
2385
|
+
if (direction === 'backward') {
|
2386
|
+
toNode.selectEnd();
|
2387
|
+
} else {
|
2388
|
+
toNode.selectStart();
|
2389
|
+
}
|
2390
|
+
return true;
|
2391
|
+
}
|
2392
|
+
function isExitingCell(tableMap, cellValue, direction) {
|
2393
|
+
const firstCell = tableMap[0][0];
|
2394
|
+
const lastCell = tableMap[tableMap.length - 1][tableMap[0].length - 1];
|
2395
|
+
const {
|
2396
|
+
startColumn,
|
2397
|
+
startRow
|
2398
|
+
} = cellValue;
|
2399
|
+
return direction === 'backward' ? startColumn === firstCell.startColumn && startRow === firstCell.startRow : startColumn === lastCell.startColumn && startRow === lastCell.startRow;
|
2400
|
+
}
|
2401
|
+
function getExitingToNode(anchorNode, direction, tableNode) {
|
2402
|
+
const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
|
2403
|
+
if (!parentNode) {
|
2404
|
+
return undefined;
|
2405
|
+
}
|
2406
|
+
const anchorSibling = direction === 'backward' ? parentNode.getPreviousSibling() : parentNode.getNextSibling();
|
2407
|
+
return anchorSibling && $isTableNode(anchorSibling) ? anchorSibling : direction === 'backward' ? tableNode.getPreviousSibling() : tableNode.getNextSibling();
|
2408
|
+
}
|
2409
|
+
function $insertParagraphAtTableEdge(edgePosition, tableNode, children) {
|
2410
|
+
const paragraphNode = lexical.$createParagraphNode();
|
2411
|
+
if (edgePosition === 'first') {
|
2412
|
+
tableNode.insertBefore(paragraphNode);
|
2413
|
+
} else {
|
2414
|
+
tableNode.insertAfter(paragraphNode);
|
2415
|
+
}
|
2416
|
+
paragraphNode.append(...(children || []));
|
2417
|
+
paragraphNode.selectEnd();
|
2418
|
+
}
|
2419
|
+
function $getTableEdgeCursorPosition(editor, selection, tableNode) {
|
2420
|
+
// TODO: Add support for nested tables
|
2421
|
+
const domSelection = window.getSelection();
|
2422
|
+
if (!domSelection || domSelection.anchorNode !== editor.getRootElement()) {
|
2423
|
+
return undefined;
|
2424
|
+
}
|
2425
|
+
const anchorCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
2426
|
+
if (!anchorCellNode) {
|
2427
|
+
return undefined;
|
2428
|
+
}
|
2429
|
+
const parentTable = utils.$findMatchingParent(anchorCellNode, n => $isTableNode(n));
|
2430
|
+
if (!$isTableNode(parentTable) || !parentTable.is(tableNode)) {
|
2431
|
+
return undefined;
|
2432
|
+
}
|
2433
|
+
const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
|
2434
|
+
const firstCell = tableMap[0][0];
|
2435
|
+
const lastCell = tableMap[tableMap.length - 1][tableMap[0].length - 1];
|
2436
|
+
const {
|
2437
|
+
startRow,
|
2438
|
+
startColumn
|
2439
|
+
} = cellValue;
|
2440
|
+
const isAtFirstCell = startRow === firstCell.startRow && startColumn === firstCell.startColumn;
|
2441
|
+
const isAtLastCell = startRow === lastCell.startRow && startColumn === lastCell.startColumn;
|
2442
|
+
if (isAtFirstCell) {
|
2443
|
+
return 'first';
|
2444
|
+
} else if (isAtLastCell) {
|
2445
|
+
return 'last';
|
2446
|
+
} else {
|
2447
|
+
return undefined;
|
2448
|
+
}
|
2449
|
+
}
|
2264
2450
|
|
2265
2451
|
/**
|
2266
2452
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
@@ -5,7 +5,7 @@
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
6
6
|
*/
|
7
7
|
import { addClassNamesToElement, $findMatchingParent, removeClassNamesFromElement, isHTMLElement } from '@lexical/utils';
|
8
|
-
import { ElementNode, $applyNodeReplacement, $createParagraphNode, $isElementNode, $isLineBreakNode, createCommand, $createTextNode, $getSelection, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $getNodeByKey, isCurrentlyReadOnlyMode, $createPoint, $setSelection, SELECTION_CHANGE_COMMAND, $getNearestNodeFromDOMNode, $createRangeSelection, $getRoot, KEY_ARROW_DOWN_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ESCAPE_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $isTextNode, $getPreviousSelection } from 'lexical';
|
8
|
+
import { ElementNode, $applyNodeReplacement, $createParagraphNode, $isElementNode, $isLineBreakNode, createCommand, $createTextNode, $getSelection, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $getNodeByKey, isCurrentlyReadOnlyMode, $createPoint, $setSelection, SELECTION_CHANGE_COMMAND, $getNearestNodeFromDOMNode, $createRangeSelection, $getRoot, KEY_ARROW_DOWN_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ESCAPE_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $isTextNode, $getPreviousSelection, $createRangeSelectionFromDom, INSERT_PARAGRAPH_COMMAND } from 'lexical';
|
9
9
|
|
10
10
|
/**
|
11
11
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
@@ -1294,7 +1294,6 @@ function $getChildrenRecursively(node) {
|
|
1294
1294
|
* LICENSE file in the root directory of this source tree.
|
1295
1295
|
*
|
1296
1296
|
*/
|
1297
|
-
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
1298
1297
|
class TableObserver {
|
1299
1298
|
constructor(editor, tableNodeKey) {
|
1300
1299
|
this.isHighlightingCells = false;
|
@@ -1317,6 +1316,7 @@ class TableObserver {
|
|
1317
1316
|
this.focusCell = null;
|
1318
1317
|
this.hasHijackedSelectionStyles = false;
|
1319
1318
|
this.trackTable();
|
1319
|
+
this.isSelecting = false;
|
1320
1320
|
}
|
1321
1321
|
getTable() {
|
1322
1322
|
return this.table;
|
@@ -1456,7 +1456,7 @@ class TableObserver {
|
|
1456
1456
|
this.focusY = cellY;
|
1457
1457
|
if (this.isHighlightingCells) {
|
1458
1458
|
const focusTableCellNode = $getNearestNodeFromDOMNode(cell.elem);
|
1459
|
-
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
|
1459
|
+
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode) && tableNode.is($findTableNode(focusTableCellNode))) {
|
1460
1460
|
const focusNodeKey = focusTableCellNode.getKey();
|
1461
1461
|
this.tableSelection = this.tableSelection.clone() || $createTableSelection();
|
1462
1462
|
this.focusCellNodeKey = focusNodeKey;
|
@@ -1554,6 +1554,7 @@ class TableObserver {
|
|
1554
1554
|
*
|
1555
1555
|
*/
|
1556
1556
|
const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
|
1557
|
+
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
1557
1558
|
function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
1558
1559
|
const rootElement = editor.getRootElement();
|
1559
1560
|
if (rootElement === null) {
|
@@ -1562,6 +1563,24 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1562
1563
|
const tableObserver = new TableObserver(editor, tableNode.getKey());
|
1563
1564
|
const editorWindow = editor._window || window;
|
1564
1565
|
attachTableObserverToTableElement(tableElement, tableObserver);
|
1566
|
+
const createMouseHandlers = () => {
|
1567
|
+
const onMouseUp = () => {
|
1568
|
+
tableObserver.isSelecting = false;
|
1569
|
+
editorWindow.removeEventListener('mouseup', onMouseUp);
|
1570
|
+
editorWindow.removeEventListener('mousemove', onMouseMove);
|
1571
|
+
};
|
1572
|
+
const onMouseMove = moveEvent => {
|
1573
|
+
const focusCell = getDOMCellFromTarget(moveEvent.target);
|
1574
|
+
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
|
1575
|
+
moveEvent.preventDefault();
|
1576
|
+
tableObserver.setFocusCellForSelection(focusCell);
|
1577
|
+
}
|
1578
|
+
};
|
1579
|
+
return {
|
1580
|
+
onMouseMove: onMouseMove,
|
1581
|
+
onMouseUp: onMouseUp
|
1582
|
+
};
|
1583
|
+
};
|
1565
1584
|
tableElement.addEventListener('mousedown', event => {
|
1566
1585
|
setTimeout(() => {
|
1567
1586
|
if (event.button !== 0) {
|
@@ -1575,17 +1594,11 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1575
1594
|
stopEvent(event);
|
1576
1595
|
tableObserver.setAnchorCellForSelection(anchorCell);
|
1577
1596
|
}
|
1578
|
-
const
|
1579
|
-
|
1580
|
-
|
1581
|
-
};
|
1582
|
-
|
1583
|
-
const focusCell = getDOMCellFromTarget(moveEvent.target);
|
1584
|
-
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
|
1585
|
-
moveEvent.preventDefault();
|
1586
|
-
tableObserver.setFocusCellForSelection(focusCell);
|
1587
|
-
}
|
1588
|
-
};
|
1597
|
+
const {
|
1598
|
+
onMouseUp,
|
1599
|
+
onMouseMove
|
1600
|
+
} = createMouseHandlers();
|
1601
|
+
tableObserver.isSelecting = true;
|
1589
1602
|
editorWindow.addEventListener('mouseup', onMouseUp);
|
1590
1603
|
editorWindow.addEventListener('mousemove', onMouseMove);
|
1591
1604
|
}, 0);
|
@@ -1750,6 +1763,13 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1750
1763
|
if (!$isTableCellNode(tableCellNode)) {
|
1751
1764
|
return false;
|
1752
1765
|
}
|
1766
|
+
if (typeof payload === 'string') {
|
1767
|
+
const edgePosition = $getTableEdgeCursorPosition(editor, selection, tableNode);
|
1768
|
+
if (edgePosition) {
|
1769
|
+
$insertParagraphAtTableEdge(edgePosition, tableNode, [$createTextNode(payload)]);
|
1770
|
+
return true;
|
1771
|
+
}
|
1772
|
+
}
|
1753
1773
|
}
|
1754
1774
|
return false;
|
1755
1775
|
}, COMMAND_PRIORITY_CRITICAL));
|
@@ -1880,7 +1900,11 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1880
1900
|
const isBackward = selection.isBackward();
|
1881
1901
|
if (isPartialyWithinTable) {
|
1882
1902
|
const newSelection = selection.clone();
|
1883
|
-
|
1903
|
+
if (isFocusInside) {
|
1904
|
+
newSelection.focus.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() : tableNode.getIndexWithinParent() + 1, 'element');
|
1905
|
+
} else {
|
1906
|
+
newSelection.anchor.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() + 1 : tableNode.getIndexWithinParent(), 'element');
|
1907
|
+
}
|
1884
1908
|
$setSelection(newSelection);
|
1885
1909
|
$addHighlightStyleToTable(editor, tableObserver);
|
1886
1910
|
} else if (isWithinTable) {
|
@@ -1889,6 +1913,34 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1889
1913
|
if (!anchorCellNode.is(focusCellNode)) {
|
1890
1914
|
tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
|
1891
1915
|
tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
|
1916
|
+
if (!tableObserver.isSelecting) {
|
1917
|
+
setTimeout(() => {
|
1918
|
+
const {
|
1919
|
+
onMouseUp,
|
1920
|
+
onMouseMove
|
1921
|
+
} = createMouseHandlers();
|
1922
|
+
tableObserver.isSelecting = true;
|
1923
|
+
editorWindow.addEventListener('mouseup', onMouseUp);
|
1924
|
+
editorWindow.addEventListener('mousemove', onMouseMove);
|
1925
|
+
}, 0);
|
1926
|
+
}
|
1927
|
+
}
|
1928
|
+
}
|
1929
|
+
} else if (selection && $isTableSelection(selection) && selection.is(prevSelection) && selection.tableKey === tableNode.getKey()) {
|
1930
|
+
// if selection goes outside of the table we need to change it to Range selection
|
1931
|
+
const domSelection = getDOMSelection(editor._window);
|
1932
|
+
if (domSelection && domSelection.anchorNode && domSelection.focusNode) {
|
1933
|
+
const focusNode = $getNearestNodeFromDOMNode(domSelection.focusNode);
|
1934
|
+
const isFocusOutside = focusNode && !tableNode.is($findTableNode(focusNode));
|
1935
|
+
const anchorNode = $getNearestNodeFromDOMNode(domSelection.anchorNode);
|
1936
|
+
const isAnchorInside = anchorNode && tableNode.is($findTableNode(anchorNode));
|
1937
|
+
if (isFocusOutside && isAnchorInside && domSelection.rangeCount > 0) {
|
1938
|
+
const newSelection = $createRangeSelectionFromDom(domSelection, editor);
|
1939
|
+
if (newSelection) {
|
1940
|
+
newSelection.anchor.set(tableNode.getKey(), selection.isBackward() ? tableNode.getChildrenSize() : 0, 'element');
|
1941
|
+
domSelection.removeAllRanges();
|
1942
|
+
$setSelection(newSelection);
|
1943
|
+
}
|
1892
1944
|
}
|
1893
1945
|
}
|
1894
1946
|
}
|
@@ -1907,6 +1959,18 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
|
1907
1959
|
}
|
1908
1960
|
return false;
|
1909
1961
|
}, COMMAND_PRIORITY_CRITICAL));
|
1962
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(INSERT_PARAGRAPH_COMMAND, () => {
|
1963
|
+
const selection = $getSelection();
|
1964
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
|
1965
|
+
return false;
|
1966
|
+
}
|
1967
|
+
const edgePosition = $getTableEdgeCursorPosition(editor, selection, tableNode);
|
1968
|
+
if (edgePosition) {
|
1969
|
+
$insertParagraphAtTableEdge(edgePosition, tableNode);
|
1970
|
+
return true;
|
1971
|
+
}
|
1972
|
+
return false;
|
1973
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1910
1974
|
return tableObserver;
|
1911
1975
|
}
|
1912
1976
|
function attachTableObserverToTableElement(tableElement, tableObserver) {
|
@@ -2163,14 +2227,31 @@ function $findTableNode(node) {
|
|
2163
2227
|
function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
2164
2228
|
const selection = $getSelection();
|
2165
2229
|
if (!$isSelectionInTable(selection, tableNode)) {
|
2230
|
+
if (direction === 'backward' && $isRangeSelection(selection) && selection.isCollapsed()) {
|
2231
|
+
const anchorType = selection.anchor.type;
|
2232
|
+
const anchorOffset = selection.anchor.offset;
|
2233
|
+
if (anchorType !== 'element' && !(anchorType === 'text' && anchorOffset === 0)) {
|
2234
|
+
return false;
|
2235
|
+
}
|
2236
|
+
const anchorNode = selection.anchor.getNode();
|
2237
|
+
if (!anchorNode) {
|
2238
|
+
return false;
|
2239
|
+
}
|
2240
|
+
const parentNode = $findMatchingParent(anchorNode, n => $isElementNode(n) && !n.isInline());
|
2241
|
+
if (!parentNode) {
|
2242
|
+
return false;
|
2243
|
+
}
|
2244
|
+
const siblingNode = parentNode.getPreviousSibling();
|
2245
|
+
if (!siblingNode || !$isTableNode(siblingNode)) {
|
2246
|
+
return false;
|
2247
|
+
}
|
2248
|
+
stopEvent(event);
|
2249
|
+
siblingNode.selectEnd();
|
2250
|
+
return true;
|
2251
|
+
}
|
2166
2252
|
return false;
|
2167
2253
|
}
|
2168
2254
|
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
2169
|
-
// Horizontal move between cels seem to work well without interruption
|
2170
|
-
// so just exit early, and handle vertical moves
|
2171
|
-
if (direction === 'backward' || direction === 'forward') {
|
2172
|
-
return false;
|
2173
|
-
}
|
2174
2255
|
const {
|
2175
2256
|
anchor,
|
2176
2257
|
focus
|
@@ -2188,6 +2269,18 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
|
2188
2269
|
return $handleArrowKey(editor, event, direction, anchorCellTable, tableObserver);
|
2189
2270
|
}
|
2190
2271
|
}
|
2272
|
+
if (direction === 'backward' || direction === 'forward') {
|
2273
|
+
const anchorType = anchor.type;
|
2274
|
+
const anchorOffset = anchor.offset;
|
2275
|
+
const anchorNode = anchor.getNode();
|
2276
|
+
if (!anchorNode) {
|
2277
|
+
return false;
|
2278
|
+
}
|
2279
|
+
if (isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction)) {
|
2280
|
+
return $handleTableExit(event, anchorNode, tableNode, direction);
|
2281
|
+
}
|
2282
|
+
return false;
|
2283
|
+
}
|
2191
2284
|
const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
|
2192
2285
|
const anchorDOM = editor.getElementByKey(anchor.key);
|
2193
2286
|
if (anchorDOM == null || anchorCellDom == null) {
|
@@ -2259,6 +2352,99 @@ function stopEvent(event) {
|
|
2259
2352
|
event.stopImmediatePropagation();
|
2260
2353
|
event.stopPropagation();
|
2261
2354
|
}
|
2355
|
+
function isExitingTableAnchor(type, offset, anchorNode, direction) {
|
2356
|
+
return isExitingTableElementAnchor(type, anchorNode, direction) || isExitingTableTextAnchor(type, offset, anchorNode, direction);
|
2357
|
+
}
|
2358
|
+
function isExitingTableElementAnchor(type, anchorNode, direction) {
|
2359
|
+
return type === 'element' && (direction === 'backward' ? anchorNode.getPreviousSibling() === null : anchorNode.getNextSibling() === null);
|
2360
|
+
}
|
2361
|
+
function isExitingTableTextAnchor(type, offset, anchorNode, direction) {
|
2362
|
+
const parentNode = $findMatchingParent(anchorNode, n => $isElementNode(n) && !n.isInline());
|
2363
|
+
if (!parentNode) {
|
2364
|
+
return false;
|
2365
|
+
}
|
2366
|
+
const hasValidOffset = direction === 'backward' ? offset === 0 : offset === anchorNode.getTextContentSize();
|
2367
|
+
return type === 'text' && hasValidOffset && (direction === 'backward' ? parentNode.getPreviousSibling() === null : parentNode.getNextSibling() === null);
|
2368
|
+
}
|
2369
|
+
function $handleTableExit(event, anchorNode, tableNode, direction) {
|
2370
|
+
const anchorCellNode = $findMatchingParent(anchorNode, $isTableCellNode);
|
2371
|
+
if (!$isTableCellNode(anchorCellNode)) {
|
2372
|
+
return false;
|
2373
|
+
}
|
2374
|
+
const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
|
2375
|
+
if (!isExitingCell(tableMap, cellValue, direction)) {
|
2376
|
+
return false;
|
2377
|
+
}
|
2378
|
+
const toNode = getExitingToNode(anchorNode, direction, tableNode);
|
2379
|
+
if (!toNode || $isTableNode(toNode)) {
|
2380
|
+
return false;
|
2381
|
+
}
|
2382
|
+
stopEvent(event);
|
2383
|
+
if (direction === 'backward') {
|
2384
|
+
toNode.selectEnd();
|
2385
|
+
} else {
|
2386
|
+
toNode.selectStart();
|
2387
|
+
}
|
2388
|
+
return true;
|
2389
|
+
}
|
2390
|
+
function isExitingCell(tableMap, cellValue, direction) {
|
2391
|
+
const firstCell = tableMap[0][0];
|
2392
|
+
const lastCell = tableMap[tableMap.length - 1][tableMap[0].length - 1];
|
2393
|
+
const {
|
2394
|
+
startColumn,
|
2395
|
+
startRow
|
2396
|
+
} = cellValue;
|
2397
|
+
return direction === 'backward' ? startColumn === firstCell.startColumn && startRow === firstCell.startRow : startColumn === lastCell.startColumn && startRow === lastCell.startRow;
|
2398
|
+
}
|
2399
|
+
function getExitingToNode(anchorNode, direction, tableNode) {
|
2400
|
+
const parentNode = $findMatchingParent(anchorNode, n => $isElementNode(n) && !n.isInline());
|
2401
|
+
if (!parentNode) {
|
2402
|
+
return undefined;
|
2403
|
+
}
|
2404
|
+
const anchorSibling = direction === 'backward' ? parentNode.getPreviousSibling() : parentNode.getNextSibling();
|
2405
|
+
return anchorSibling && $isTableNode(anchorSibling) ? anchorSibling : direction === 'backward' ? tableNode.getPreviousSibling() : tableNode.getNextSibling();
|
2406
|
+
}
|
2407
|
+
function $insertParagraphAtTableEdge(edgePosition, tableNode, children) {
|
2408
|
+
const paragraphNode = $createParagraphNode();
|
2409
|
+
if (edgePosition === 'first') {
|
2410
|
+
tableNode.insertBefore(paragraphNode);
|
2411
|
+
} else {
|
2412
|
+
tableNode.insertAfter(paragraphNode);
|
2413
|
+
}
|
2414
|
+
paragraphNode.append(...(children || []));
|
2415
|
+
paragraphNode.selectEnd();
|
2416
|
+
}
|
2417
|
+
function $getTableEdgeCursorPosition(editor, selection, tableNode) {
|
2418
|
+
// TODO: Add support for nested tables
|
2419
|
+
const domSelection = window.getSelection();
|
2420
|
+
if (!domSelection || domSelection.anchorNode !== editor.getRootElement()) {
|
2421
|
+
return undefined;
|
2422
|
+
}
|
2423
|
+
const anchorCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
2424
|
+
if (!anchorCellNode) {
|
2425
|
+
return undefined;
|
2426
|
+
}
|
2427
|
+
const parentTable = $findMatchingParent(anchorCellNode, n => $isTableNode(n));
|
2428
|
+
if (!$isTableNode(parentTable) || !parentTable.is(tableNode)) {
|
2429
|
+
return undefined;
|
2430
|
+
}
|
2431
|
+
const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
|
2432
|
+
const firstCell = tableMap[0][0];
|
2433
|
+
const lastCell = tableMap[tableMap.length - 1][tableMap[0].length - 1];
|
2434
|
+
const {
|
2435
|
+
startRow,
|
2436
|
+
startColumn
|
2437
|
+
} = cellValue;
|
2438
|
+
const isAtFirstCell = startRow === firstCell.startRow && startColumn === firstCell.startColumn;
|
2439
|
+
const isAtLastCell = startRow === lastCell.startRow && startColumn === lastCell.startColumn;
|
2440
|
+
if (isAtFirstCell) {
|
2441
|
+
return 'first';
|
2442
|
+
} else if (isAtLastCell) {
|
2443
|
+
return 'last';
|
2444
|
+
} else {
|
2445
|
+
return undefined;
|
2446
|
+
}
|
2447
|
+
}
|
2262
2448
|
|
2263
2449
|
/**
|
2264
2450
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
@@ -4,8 +4,8 @@
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
6
6
|
*/
|
7
|
-
import * as modDev from './LexicalTable.dev.
|
8
|
-
import * as modProd from './LexicalTable.prod.
|
7
|
+
import * as modDev from './LexicalTable.dev.mjs';
|
8
|
+
import * as modProd from './LexicalTable.prod.mjs';
|
9
9
|
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
10
10
|
export const $computeTableMap = mod.$computeTableMap;
|
11
11
|
export const $createTableCellNode = mod.$createTableCellNode;
|