@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.
@@ -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 onMouseUp = () => {
1581
- editorWindow.removeEventListener('mouseup', onMouseUp);
1582
- editorWindow.removeEventListener('mousemove', onMouseMove);
1583
- };
1584
- const onMouseMove = moveEvent => {
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
- newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
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 onMouseUp = () => {
1579
- editorWindow.removeEventListener('mouseup', onMouseUp);
1580
- editorWindow.removeEventListener('mousemove', onMouseMove);
1581
- };
1582
- const onMouseMove = moveEvent => {
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
- newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
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.esm.js';
8
- import * as modProd from './LexicalTable.prod.esm.js';
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;