@lexical/table 0.14.2 → 0.14.4

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.
@@ -221,6 +221,11 @@ function convertTableCellNodeElement(domNode) {
221
221
  if (backgroundColor !== '') {
222
222
  tableCellNode.__backgroundColor = backgroundColor;
223
223
  }
224
+ const style = domNode_.style;
225
+ const hasBoldFontWeight = style.fontWeight === '700' || style.fontWeight === 'bold';
226
+ const hasLinethroughTextDecoration = style.textDecoration === 'line-through';
227
+ const hasItalicFontStyle = style.fontStyle === 'italic';
228
+ const hasUnderlineTextDecoration = style.textDecoration === 'underline';
224
229
  return {
225
230
  after: childLexicalNodes => {
226
231
  if (childLexicalNodes.length === 0) {
@@ -234,6 +239,20 @@ function convertTableCellNodeElement(domNode) {
234
239
  if (lexical.$isLineBreakNode(lexicalNode) && lexicalNode.getTextContent() === '\n') {
235
240
  return null;
236
241
  }
242
+ if (lexical.$isTextNode(lexicalNode)) {
243
+ if (hasBoldFontWeight) {
244
+ lexicalNode.toggleFormat('bold');
245
+ }
246
+ if (hasLinethroughTextDecoration) {
247
+ lexicalNode.toggleFormat('strikethrough');
248
+ }
249
+ if (hasItalicFontStyle) {
250
+ lexicalNode.toggleFormat('italic');
251
+ }
252
+ if (hasUnderlineTextDecoration) {
253
+ lexicalNode.toggleFormat('underline');
254
+ }
255
+ }
237
256
  paragraphNode.append(lexicalNode);
238
257
  return paragraphNode;
239
258
  }
@@ -1296,7 +1315,6 @@ function $getChildrenRecursively(node) {
1296
1315
  * LICENSE file in the root directory of this source tree.
1297
1316
  *
1298
1317
  */
1299
- const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
1300
1318
  class TableObserver {
1301
1319
  constructor(editor, tableNodeKey) {
1302
1320
  this.isHighlightingCells = false;
@@ -1319,6 +1337,7 @@ class TableObserver {
1319
1337
  this.focusCell = null;
1320
1338
  this.hasHijackedSelectionStyles = false;
1321
1339
  this.trackTable();
1340
+ this.isSelecting = false;
1322
1341
  }
1323
1342
  getTable() {
1324
1343
  return this.table;
@@ -1458,7 +1477,7 @@ class TableObserver {
1458
1477
  this.focusY = cellY;
1459
1478
  if (this.isHighlightingCells) {
1460
1479
  const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
1461
- if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
1480
+ if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode) && tableNode.is($findTableNode(focusTableCellNode))) {
1462
1481
  const focusNodeKey = focusTableCellNode.getKey();
1463
1482
  this.tableSelection = this.tableSelection.clone() || $createTableSelection();
1464
1483
  this.focusCellNodeKey = focusNodeKey;
@@ -1556,6 +1575,7 @@ class TableObserver {
1556
1575
  *
1557
1576
  */
1558
1577
  const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
1578
+ const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
1559
1579
  function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1560
1580
  const rootElement = editor.getRootElement();
1561
1581
  if (rootElement === null) {
@@ -1564,6 +1584,24 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1564
1584
  const tableObserver = new TableObserver(editor, tableNode.getKey());
1565
1585
  const editorWindow = editor._window || window;
1566
1586
  attachTableObserverToTableElement(tableElement, tableObserver);
1587
+ const createMouseHandlers = () => {
1588
+ const onMouseUp = () => {
1589
+ tableObserver.isSelecting = false;
1590
+ editorWindow.removeEventListener('mouseup', onMouseUp);
1591
+ editorWindow.removeEventListener('mousemove', onMouseMove);
1592
+ };
1593
+ const onMouseMove = moveEvent => {
1594
+ const focusCell = getDOMCellFromTarget(moveEvent.target);
1595
+ if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
1596
+ moveEvent.preventDefault();
1597
+ tableObserver.setFocusCellForSelection(focusCell);
1598
+ }
1599
+ };
1600
+ return {
1601
+ onMouseMove: onMouseMove,
1602
+ onMouseUp: onMouseUp
1603
+ };
1604
+ };
1567
1605
  tableElement.addEventListener('mousedown', event => {
1568
1606
  setTimeout(() => {
1569
1607
  if (event.button !== 0) {
@@ -1577,17 +1615,11 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1577
1615
  stopEvent(event);
1578
1616
  tableObserver.setAnchorCellForSelection(anchorCell);
1579
1617
  }
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
- };
1618
+ const {
1619
+ onMouseUp,
1620
+ onMouseMove
1621
+ } = createMouseHandlers();
1622
+ tableObserver.isSelecting = true;
1591
1623
  editorWindow.addEventListener('mouseup', onMouseUp);
1592
1624
  editorWindow.addEventListener('mousemove', onMouseMove);
1593
1625
  }, 0);
@@ -1655,18 +1687,6 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1655
1687
  // TODO: Fix Delete Line in Table Cells.
1656
1688
  return true;
1657
1689
  }
1658
- if (command === lexical.DELETE_CHARACTER_COMMAND || command === lexical.DELETE_WORD_COMMAND) {
1659
- if (selection.isCollapsed() && selection.anchor.offset === 0) {
1660
- if (nearestElementNode !== topLevelCellElementNode) {
1661
- const children = nearestElementNode.getChildren();
1662
- const newParagraphNode = lexical.$createParagraphNode();
1663
- children.forEach(child => newParagraphNode.append(child));
1664
- nearestElementNode.replace(newParagraphNode);
1665
- nearestElementNode.getWritable().__parent = tableCellNode.getKey();
1666
- return true;
1667
- }
1668
- }
1669
- }
1670
1690
  }
1671
1691
  return false;
1672
1692
  };
@@ -1752,6 +1772,13 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1752
1772
  if (!$isTableCellNode(tableCellNode)) {
1753
1773
  return false;
1754
1774
  }
1775
+ if (typeof payload === 'string') {
1776
+ const edgePosition = $getTableEdgeCursorPosition(editor, selection, tableNode);
1777
+ if (edgePosition) {
1778
+ $insertParagraphAtTableEdge(edgePosition, tableNode, [lexical.$createTextNode(payload)]);
1779
+ return true;
1780
+ }
1781
+ }
1755
1782
  }
1756
1783
  return false;
1757
1784
  }, lexical.COMMAND_PRIORITY_CRITICAL));
@@ -1882,7 +1909,11 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1882
1909
  const isBackward = selection.isBackward();
1883
1910
  if (isPartialyWithinTable) {
1884
1911
  const newSelection = selection.clone();
1885
- newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
1912
+ if (isFocusInside) {
1913
+ newSelection.focus.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() : tableNode.getIndexWithinParent() + 1, 'element');
1914
+ } else {
1915
+ newSelection.anchor.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() + 1 : tableNode.getIndexWithinParent(), 'element');
1916
+ }
1886
1917
  lexical.$setSelection(newSelection);
1887
1918
  $addHighlightStyleToTable(editor, tableObserver);
1888
1919
  } else if (isWithinTable) {
@@ -1891,6 +1922,34 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1891
1922
  if (!anchorCellNode.is(focusCellNode)) {
1892
1923
  tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
1893
1924
  tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
1925
+ if (!tableObserver.isSelecting) {
1926
+ setTimeout(() => {
1927
+ const {
1928
+ onMouseUp,
1929
+ onMouseMove
1930
+ } = createMouseHandlers();
1931
+ tableObserver.isSelecting = true;
1932
+ editorWindow.addEventListener('mouseup', onMouseUp);
1933
+ editorWindow.addEventListener('mousemove', onMouseMove);
1934
+ }, 0);
1935
+ }
1936
+ }
1937
+ }
1938
+ } else if (selection && $isTableSelection(selection) && selection.is(prevSelection) && selection.tableKey === tableNode.getKey()) {
1939
+ // if selection goes outside of the table we need to change it to Range selection
1940
+ const domSelection = getDOMSelection(editor._window);
1941
+ if (domSelection && domSelection.anchorNode && domSelection.focusNode) {
1942
+ const focusNode = lexical.$getNearestNodeFromDOMNode(domSelection.focusNode);
1943
+ const isFocusOutside = focusNode && !tableNode.is($findTableNode(focusNode));
1944
+ const anchorNode = lexical.$getNearestNodeFromDOMNode(domSelection.anchorNode);
1945
+ const isAnchorInside = anchorNode && tableNode.is($findTableNode(anchorNode));
1946
+ if (isFocusOutside && isAnchorInside && domSelection.rangeCount > 0) {
1947
+ const newSelection = lexical.$createRangeSelectionFromDom(domSelection, editor);
1948
+ if (newSelection) {
1949
+ newSelection.anchor.set(tableNode.getKey(), selection.isBackward() ? tableNode.getChildrenSize() : 0, 'element');
1950
+ domSelection.removeAllRanges();
1951
+ lexical.$setSelection(newSelection);
1952
+ }
1894
1953
  }
1895
1954
  }
1896
1955
  }
@@ -1909,6 +1968,18 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1909
1968
  }
1910
1969
  return false;
1911
1970
  }, lexical.COMMAND_PRIORITY_CRITICAL));
1971
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.INSERT_PARAGRAPH_COMMAND, () => {
1972
+ const selection = lexical.$getSelection();
1973
+ if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
1974
+ return false;
1975
+ }
1976
+ const edgePosition = $getTableEdgeCursorPosition(editor, selection, tableNode);
1977
+ if (edgePosition) {
1978
+ $insertParagraphAtTableEdge(edgePosition, tableNode);
1979
+ return true;
1980
+ }
1981
+ return false;
1982
+ }, lexical.COMMAND_PRIORITY_CRITICAL));
1912
1983
  return tableObserver;
1913
1984
  }
1914
1985
  function attachTableObserverToTableElement(tableElement, tableObserver) {
@@ -2165,14 +2236,31 @@ function $findTableNode(node) {
2165
2236
  function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2166
2237
  const selection = lexical.$getSelection();
2167
2238
  if (!$isSelectionInTable(selection, tableNode)) {
2239
+ if (direction === 'backward' && lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
2240
+ const anchorType = selection.anchor.type;
2241
+ const anchorOffset = selection.anchor.offset;
2242
+ if (anchorType !== 'element' && !(anchorType === 'text' && anchorOffset === 0)) {
2243
+ return false;
2244
+ }
2245
+ const anchorNode = selection.anchor.getNode();
2246
+ if (!anchorNode) {
2247
+ return false;
2248
+ }
2249
+ const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
2250
+ if (!parentNode) {
2251
+ return false;
2252
+ }
2253
+ const siblingNode = parentNode.getPreviousSibling();
2254
+ if (!siblingNode || !$isTableNode(siblingNode)) {
2255
+ return false;
2256
+ }
2257
+ stopEvent(event);
2258
+ siblingNode.selectEnd();
2259
+ return true;
2260
+ }
2168
2261
  return false;
2169
2262
  }
2170
2263
  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
2264
  const {
2177
2265
  anchor,
2178
2266
  focus
@@ -2190,6 +2278,18 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2190
2278
  return $handleArrowKey(editor, event, direction, anchorCellTable, tableObserver);
2191
2279
  }
2192
2280
  }
2281
+ if (direction === 'backward' || direction === 'forward') {
2282
+ const anchorType = anchor.type;
2283
+ const anchorOffset = anchor.offset;
2284
+ const anchorNode = anchor.getNode();
2285
+ if (!anchorNode) {
2286
+ return false;
2287
+ }
2288
+ if (isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction)) {
2289
+ return $handleTableExit(event, anchorNode, tableNode, direction);
2290
+ }
2291
+ return false;
2292
+ }
2193
2293
  const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
2194
2294
  const anchorDOM = editor.getElementByKey(anchor.key);
2195
2295
  if (anchorDOM == null || anchorCellDom == null) {
@@ -2261,6 +2361,99 @@ function stopEvent(event) {
2261
2361
  event.stopImmediatePropagation();
2262
2362
  event.stopPropagation();
2263
2363
  }
2364
+ function isExitingTableAnchor(type, offset, anchorNode, direction) {
2365
+ return isExitingTableElementAnchor(type, anchorNode, direction) || isExitingTableTextAnchor(type, offset, anchorNode, direction);
2366
+ }
2367
+ function isExitingTableElementAnchor(type, anchorNode, direction) {
2368
+ return type === 'element' && (direction === 'backward' ? anchorNode.getPreviousSibling() === null : anchorNode.getNextSibling() === null);
2369
+ }
2370
+ function isExitingTableTextAnchor(type, offset, anchorNode, direction) {
2371
+ const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
2372
+ if (!parentNode) {
2373
+ return false;
2374
+ }
2375
+ const hasValidOffset = direction === 'backward' ? offset === 0 : offset === anchorNode.getTextContentSize();
2376
+ return type === 'text' && hasValidOffset && (direction === 'backward' ? parentNode.getPreviousSibling() === null : parentNode.getNextSibling() === null);
2377
+ }
2378
+ function $handleTableExit(event, anchorNode, tableNode, direction) {
2379
+ const anchorCellNode = utils.$findMatchingParent(anchorNode, $isTableCellNode);
2380
+ if (!$isTableCellNode(anchorCellNode)) {
2381
+ return false;
2382
+ }
2383
+ const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
2384
+ if (!isExitingCell(tableMap, cellValue, direction)) {
2385
+ return false;
2386
+ }
2387
+ const toNode = getExitingToNode(anchorNode, direction, tableNode);
2388
+ if (!toNode || $isTableNode(toNode)) {
2389
+ return false;
2390
+ }
2391
+ stopEvent(event);
2392
+ if (direction === 'backward') {
2393
+ toNode.selectEnd();
2394
+ } else {
2395
+ toNode.selectStart();
2396
+ }
2397
+ return true;
2398
+ }
2399
+ function isExitingCell(tableMap, cellValue, direction) {
2400
+ const firstCell = tableMap[0][0];
2401
+ const lastCell = tableMap[tableMap.length - 1][tableMap[0].length - 1];
2402
+ const {
2403
+ startColumn,
2404
+ startRow
2405
+ } = cellValue;
2406
+ return direction === 'backward' ? startColumn === firstCell.startColumn && startRow === firstCell.startRow : startColumn === lastCell.startColumn && startRow === lastCell.startRow;
2407
+ }
2408
+ function getExitingToNode(anchorNode, direction, tableNode) {
2409
+ const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
2410
+ if (!parentNode) {
2411
+ return undefined;
2412
+ }
2413
+ const anchorSibling = direction === 'backward' ? parentNode.getPreviousSibling() : parentNode.getNextSibling();
2414
+ return anchorSibling && $isTableNode(anchorSibling) ? anchorSibling : direction === 'backward' ? tableNode.getPreviousSibling() : tableNode.getNextSibling();
2415
+ }
2416
+ function $insertParagraphAtTableEdge(edgePosition, tableNode, children) {
2417
+ const paragraphNode = lexical.$createParagraphNode();
2418
+ if (edgePosition === 'first') {
2419
+ tableNode.insertBefore(paragraphNode);
2420
+ } else {
2421
+ tableNode.insertAfter(paragraphNode);
2422
+ }
2423
+ paragraphNode.append(...(children || []));
2424
+ paragraphNode.selectEnd();
2425
+ }
2426
+ function $getTableEdgeCursorPosition(editor, selection, tableNode) {
2427
+ // TODO: Add support for nested tables
2428
+ const domSelection = window.getSelection();
2429
+ if (!domSelection || domSelection.anchorNode !== editor.getRootElement()) {
2430
+ return undefined;
2431
+ }
2432
+ const anchorCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
2433
+ if (!anchorCellNode) {
2434
+ return undefined;
2435
+ }
2436
+ const parentTable = utils.$findMatchingParent(anchorCellNode, n => $isTableNode(n));
2437
+ if (!$isTableNode(parentTable) || !parentTable.is(tableNode)) {
2438
+ return undefined;
2439
+ }
2440
+ const [tableMap, cellValue] = $computeTableMap(tableNode, anchorCellNode, anchorCellNode);
2441
+ const firstCell = tableMap[0][0];
2442
+ const lastCell = tableMap[tableMap.length - 1][tableMap[0].length - 1];
2443
+ const {
2444
+ startRow,
2445
+ startColumn
2446
+ } = cellValue;
2447
+ const isAtFirstCell = startRow === firstCell.startRow && startColumn === firstCell.startColumn;
2448
+ const isAtLastCell = startRow === lastCell.startRow && startColumn === lastCell.startColumn;
2449
+ if (isAtFirstCell) {
2450
+ return 'first';
2451
+ } else if (isAtLastCell) {
2452
+ return 'last';
2453
+ } else {
2454
+ return undefined;
2455
+ }
2456
+ }
2264
2457
 
2265
2458
  /**
2266
2459
  * Copyright (c) Meta Platforms, Inc. and affiliates.