@lexical/table 0.14.5 → 0.16.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.
@@ -3,7 +3,9 @@
3
3
  *
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
  */
8
+
7
9
  'use strict';
8
10
 
9
11
  var utils = require('@lexical/utils');
@@ -19,6 +21,10 @@ var lexical = require('lexical');
19
21
 
20
22
  const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
21
23
 
24
+ // .PlaygroundEditorTheme__tableCell width value from
25
+ // packages/lexical-playground/src/themes/PlaygroundEditorTheme.css
26
+ const COLUMN_WIDTH = 75;
27
+
22
28
  /**
23
29
  * Copyright (c) Meta Platforms, Inc. and affiliates.
24
30
  *
@@ -26,6 +32,7 @@ const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
26
32
  * LICENSE file in the root directory of this source tree.
27
33
  *
28
34
  */
35
+
29
36
  const TableCellHeaderStates = {
30
37
  BOTH: 3,
31
38
  COLUMN: 2,
@@ -56,11 +63,11 @@ class TableCellNode extends lexical.ElementNode {
56
63
  static importDOM() {
57
64
  return {
58
65
  td: node => ({
59
- conversion: convertTableCellNodeElement,
66
+ conversion: $convertTableCellNodeElement,
60
67
  priority: 0
61
68
  }),
62
69
  th: node => ({
63
- conversion: convertTableCellNodeElement,
70
+ conversion: $convertTableCellNodeElement,
64
71
  priority: 0
65
72
  })
66
73
  };
@@ -104,8 +111,6 @@ class TableCellNode extends lexical.ElementNode {
104
111
  } = super.exportDOM(editor);
105
112
  if (element) {
106
113
  const element_ = element;
107
- const maxWidth = 700;
108
- const colCount = this.getParentOrThrow().getChildrenSize();
109
114
  element_.style.border = '1px solid black';
110
115
  if (this.__colSpan > 1) {
111
116
  element_.colSpan = this.__colSpan;
@@ -113,7 +118,7 @@ class TableCellNode extends lexical.ElementNode {
113
118
  if (this.__rowSpan > 1) {
114
119
  element_.rowSpan = this.__rowSpan;
115
120
  }
116
- element_.style.width = `${this.getWidth() || Math.max(90, maxWidth / colCount)}px`;
121
+ element_.style.width = `${this.getWidth() || COLUMN_WIDTH}px`;
117
122
  element_.style.verticalAlign = 'top';
118
123
  element_.style.textAlign = 'start';
119
124
  const backgroundColor = this.getBackgroundColor();
@@ -208,7 +213,7 @@ class TableCellNode extends lexical.ElementNode {
208
213
  return false;
209
214
  }
210
215
  }
211
- function convertTableCellNodeElement(domNode) {
216
+ function $convertTableCellNodeElement(domNode) {
212
217
  const domNode_ = domNode;
213
218
  const nodeName = domNode.nodeName.toLowerCase();
214
219
  let width = undefined;
@@ -222,10 +227,11 @@ function convertTableCellNodeElement(domNode) {
222
227
  tableCellNode.__backgroundColor = backgroundColor;
223
228
  }
224
229
  const style = domNode_.style;
230
+ const textDecoration = style.textDecoration.split(' ');
225
231
  const hasBoldFontWeight = style.fontWeight === '700' || style.fontWeight === 'bold';
226
- const hasLinethroughTextDecoration = style.textDecoration === 'line-through';
232
+ const hasLinethroughTextDecoration = textDecoration.includes('line-through');
227
233
  const hasItalicFontStyle = style.fontStyle === 'italic';
228
- const hasUnderlineTextDecoration = style.textDecoration === 'underline';
234
+ const hasUnderlineTextDecoration = textDecoration.includes('underline');
229
235
  return {
230
236
  after: childLexicalNodes => {
231
237
  if (childLexicalNodes.length === 0) {
@@ -275,6 +281,7 @@ function $isTableCellNode(node) {
275
281
  * LICENSE file in the root directory of this source tree.
276
282
  *
277
283
  */
284
+
278
285
  const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
279
286
 
280
287
  /**
@@ -284,6 +291,7 @@ const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
284
291
  * LICENSE file in the root directory of this source tree.
285
292
  *
286
293
  */
294
+
287
295
  /** @noInheritDoc */
288
296
  class TableRowNode extends lexical.ElementNode {
289
297
  /** @internal */
@@ -297,7 +305,7 @@ class TableRowNode extends lexical.ElementNode {
297
305
  static importDOM() {
298
306
  return {
299
307
  tr: node => ({
300
- conversion: convertTableRowElement,
308
+ conversion: $convertTableRowElement,
301
309
  priority: 0
302
310
  })
303
311
  };
@@ -348,7 +356,7 @@ class TableRowNode extends lexical.ElementNode {
348
356
  return false;
349
357
  }
350
358
  }
351
- function convertTableRowElement(domNode) {
359
+ function $convertTableRowElement(domNode) {
352
360
  const domNode_ = domNode;
353
361
  let height = undefined;
354
362
  if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) {
@@ -382,6 +390,7 @@ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !==
382
390
  * LICENSE file in the root directory of this source tree.
383
391
  *
384
392
  */
393
+
385
394
  function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders = true) {
386
395
  const tableNode = $createTableNode();
387
396
  for (let iRow = 0; iRow < rowCount; iRow++) {
@@ -914,6 +923,16 @@ function $unmergeCell() {
914
923
  }
915
924
  }
916
925
  function $computeTableMap(grid, cellA, cellB) {
926
+ const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(grid, cellA, cellB);
927
+ if (!(cellAValue !== null)) {
928
+ throw Error(`Anchor not found in Grid`);
929
+ }
930
+ if (!(cellBValue !== null)) {
931
+ throw Error(`Focus not found in Grid`);
932
+ }
933
+ return [tableMap, cellAValue, cellBValue];
934
+ }
935
+ function $computeTableMapSkipCellCheck(grid, cellA, cellB) {
917
936
  const tableMap = [];
918
937
  let cellAValue = null;
919
938
  let cellBValue = null;
@@ -933,10 +952,10 @@ function $computeTableMap(grid, cellA, cellB) {
933
952
  tableMap[startRow + i][startColumn + j] = value;
934
953
  }
935
954
  }
936
- if (cellA.is(cell)) {
955
+ if (cellA !== null && cellA.is(cell)) {
937
956
  cellAValue = value;
938
957
  }
939
- if (cellB.is(cell)) {
958
+ if (cellB !== null && cellB.is(cell)) {
940
959
  cellBValue = value;
941
960
  }
942
961
  }
@@ -962,12 +981,6 @@ function $computeTableMap(grid, cellA, cellB) {
962
981
  j += cell.__colSpan;
963
982
  }
964
983
  }
965
- if (!(cellAValue !== null)) {
966
- throw Error(`Anchor not found in Grid`);
967
- }
968
- if (!(cellBValue !== null)) {
969
- throw Error(`Focus not found in Grid`);
970
- }
971
984
  return [tableMap, cellAValue, cellBValue];
972
985
  }
973
986
  function $getNodeTriplet(source) {
@@ -1050,6 +1063,7 @@ function $getTableCellNodeRect(tableCellNode) {
1050
1063
  * LICENSE file in the root directory of this source tree.
1051
1064
  *
1052
1065
  */
1066
+
1053
1067
  class TableSelection {
1054
1068
  constructor(tableKey, anchor, focus) {
1055
1069
  this.anchor = anchor;
@@ -1315,6 +1329,7 @@ function $getChildrenRecursively(node) {
1315
1329
  * LICENSE file in the root directory of this source tree.
1316
1330
  *
1317
1331
  */
1332
+
1318
1333
  class TableObserver {
1319
1334
  constructor(editor, tableNodeKey) {
1320
1335
  this.isHighlightingCells = false;
@@ -1353,7 +1368,7 @@ class TableObserver {
1353
1368
  const record = records[i];
1354
1369
  const target = record.target;
1355
1370
  const nodeName = target.nodeName;
1356
- if (nodeName === 'TABLE' || nodeName === 'TR') {
1371
+ if (nodeName === 'TABLE' || nodeName === 'TBODY' || nodeName === 'THEAD' || nodeName === 'TR') {
1357
1372
  gridNeedsRedraw = true;
1358
1373
  break;
1359
1374
  }
@@ -1574,8 +1589,12 @@ class TableObserver {
1574
1589
  * LICENSE file in the root directory of this source tree.
1575
1590
  *
1576
1591
  */
1592
+
1577
1593
  const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
1578
1594
  const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
1595
+ const isMouseDownOnEvent = event => {
1596
+ return (event.buttons & 1) === 1;
1597
+ };
1579
1598
  function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1580
1599
  const rootElement = editor.getRootElement();
1581
1600
  if (rootElement === null) {
@@ -1591,11 +1610,20 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1591
1610
  editorWindow.removeEventListener('mousemove', onMouseMove);
1592
1611
  };
1593
1612
  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
- }
1613
+ // delaying mousemove handler to allow selectionchange handler from LexicalEvents.ts to be executed first
1614
+ setTimeout(() => {
1615
+ if (!isMouseDownOnEvent(moveEvent) && tableObserver.isSelecting) {
1616
+ tableObserver.isSelecting = false;
1617
+ editorWindow.removeEventListener('mouseup', onMouseUp);
1618
+ editorWindow.removeEventListener('mousemove', onMouseMove);
1619
+ return;
1620
+ }
1621
+ const focusCell = getDOMCellFromTarget(moveEvent.target);
1622
+ if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
1623
+ moveEvent.preventDefault();
1624
+ tableObserver.setFocusCellForSelection(focusCell);
1625
+ }
1626
+ }, 0);
1599
1627
  };
1600
1628
  return {
1601
1629
  onMouseMove: onMouseMove,
@@ -1693,7 +1721,7 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1693
1721
  [lexical.DELETE_WORD_COMMAND, lexical.DELETE_LINE_COMMAND, lexical.DELETE_CHARACTER_COMMAND].forEach(command => {
1694
1722
  tableObserver.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), lexical.COMMAND_PRIORITY_CRITICAL));
1695
1723
  });
1696
- const deleteCellHandler = event => {
1724
+ const $deleteCellHandler = event => {
1697
1725
  const selection = lexical.$getSelection();
1698
1726
  if (!$isSelectionInTable(selection, tableNode)) {
1699
1727
  return false;
@@ -1711,8 +1739,8 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1711
1739
  }
1712
1740
  return false;
1713
1741
  };
1714
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1715
- tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_DELETE_COMMAND, deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1742
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, $deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1743
+ tableObserver.listenersToRemove.add(editor.registerCommand(lexical.KEY_DELETE_COMMAND, $deleteCellHandler, lexical.COMMAND_PRIORITY_CRITICAL));
1716
1744
  tableObserver.listenersToRemove.add(editor.registerCommand(lexical.FORMAT_TEXT_COMMAND, payload => {
1717
1745
  const selection = lexical.$getSelection();
1718
1746
  if (!$isSelectionInTable(selection, tableNode)) {
@@ -1910,7 +1938,7 @@ function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
1910
1938
  if (isPartialyWithinTable) {
1911
1939
  const newSelection = selection.clone();
1912
1940
  if (isFocusInside) {
1913
- newSelection.focus.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() : tableNode.getIndexWithinParent() + 1, 'element');
1941
+ newSelection.focus.set(tableNode.getParentOrThrow().getKey(), tableNode.getIndexWithinParent(), 'element');
1914
1942
  } else {
1915
1943
  newSelection.anchor.set(tableNode.getParentOrThrow().getKey(), isBackward ? tableNode.getIndexWithinParent() + 1 : tableNode.getIndexWithinParent(), 'element');
1916
1944
  }
@@ -2234,6 +2262,9 @@ function $findTableNode(node) {
2234
2262
  return $isTableNode(tableNode) ? tableNode : null;
2235
2263
  }
2236
2264
  function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2265
+ if ((direction === 'up' || direction === 'down') && isTypeaheadMenuInView(editor)) {
2266
+ return false;
2267
+ }
2237
2268
  const selection = lexical.$getSelection();
2238
2269
  if (!$isSelectionInTable(selection, tableNode)) {
2239
2270
  if (direction === 'backward' && lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
@@ -2285,6 +2316,10 @@ function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
2285
2316
  if (!anchorNode) {
2286
2317
  return false;
2287
2318
  }
2319
+ const selectedNodes = selection.getNodes();
2320
+ if (selectedNodes.length === 1 && lexical.$isDecoratorNode(selectedNodes[0])) {
2321
+ return false;
2322
+ }
2288
2323
  if (isExitingTableAnchor(anchorType, anchorOffset, anchorNode, direction)) {
2289
2324
  return $handleTableExit(event, anchorNode, tableNode, direction);
2290
2325
  }
@@ -2361,13 +2396,22 @@ function stopEvent(event) {
2361
2396
  event.stopImmediatePropagation();
2362
2397
  event.stopPropagation();
2363
2398
  }
2399
+ function isTypeaheadMenuInView(editor) {
2400
+ // There is no inbuilt way to check if the component picker is in view
2401
+ // but we can check if the root DOM element has the aria-controls attribute "typeahead-menu".
2402
+ const root = editor.getRootElement();
2403
+ if (!root) {
2404
+ return false;
2405
+ }
2406
+ return root.hasAttribute('aria-controls') && root.getAttribute('aria-controls') === 'typeahead-menu';
2407
+ }
2364
2408
  function isExitingTableAnchor(type, offset, anchorNode, direction) {
2365
- return isExitingTableElementAnchor(type, anchorNode, direction) || isExitingTableTextAnchor(type, offset, anchorNode, direction);
2409
+ return isExitingTableElementAnchor(type, anchorNode, direction) || $isExitingTableTextAnchor(type, offset, anchorNode, direction);
2366
2410
  }
2367
2411
  function isExitingTableElementAnchor(type, anchorNode, direction) {
2368
2412
  return type === 'element' && (direction === 'backward' ? anchorNode.getPreviousSibling() === null : anchorNode.getNextSibling() === null);
2369
2413
  }
2370
- function isExitingTableTextAnchor(type, offset, anchorNode, direction) {
2414
+ function $isExitingTableTextAnchor(type, offset, anchorNode, direction) {
2371
2415
  const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
2372
2416
  if (!parentNode) {
2373
2417
  return false;
@@ -2384,7 +2428,7 @@ function $handleTableExit(event, anchorNode, tableNode, direction) {
2384
2428
  if (!isExitingCell(tableMap, cellValue, direction)) {
2385
2429
  return false;
2386
2430
  }
2387
- const toNode = getExitingToNode(anchorNode, direction, tableNode);
2431
+ const toNode = $getExitingToNode(anchorNode, direction, tableNode);
2388
2432
  if (!toNode || $isTableNode(toNode)) {
2389
2433
  return false;
2390
2434
  }
@@ -2405,7 +2449,7 @@ function isExitingCell(tableMap, cellValue, direction) {
2405
2449
  } = cellValue;
2406
2450
  return direction === 'backward' ? startColumn === firstCell.startColumn && startRow === firstCell.startRow : startColumn === lastCell.startColumn && startRow === lastCell.startRow;
2407
2451
  }
2408
- function getExitingToNode(anchorNode, direction, tableNode) {
2452
+ function $getExitingToNode(anchorNode, direction, tableNode) {
2409
2453
  const parentNode = utils.$findMatchingParent(anchorNode, n => lexical.$isElementNode(n) && !n.isInline());
2410
2454
  if (!parentNode) {
2411
2455
  return undefined;
@@ -2424,9 +2468,18 @@ function $insertParagraphAtTableEdge(edgePosition, tableNode, children) {
2424
2468
  paragraphNode.selectEnd();
2425
2469
  }
2426
2470
  function $getTableEdgeCursorPosition(editor, selection, tableNode) {
2471
+ const tableNodeParent = tableNode.getParent();
2472
+ if (!tableNodeParent) {
2473
+ return undefined;
2474
+ }
2475
+ const tableNodeParentDOM = editor.getElementByKey(tableNodeParent.getKey());
2476
+ if (!tableNodeParentDOM) {
2477
+ return undefined;
2478
+ }
2479
+
2427
2480
  // TODO: Add support for nested tables
2428
2481
  const domSelection = window.getSelection();
2429
- if (!domSelection || domSelection.anchorNode !== editor.getRootElement()) {
2482
+ if (!domSelection || domSelection.anchorNode !== tableNodeParentDOM) {
2430
2483
  return undefined;
2431
2484
  }
2432
2485
  const anchorCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
@@ -2462,6 +2515,7 @@ function $getTableEdgeCursorPosition(editor, selection, tableNode) {
2462
2515
  * LICENSE file in the root directory of this source tree.
2463
2516
  *
2464
2517
  */
2518
+
2465
2519
  /** @noInheritDoc */
2466
2520
  class TableNode extends lexical.ElementNode {
2467
2521
  static getType() {
@@ -2473,7 +2527,7 @@ class TableNode extends lexical.ElementNode {
2473
2527
  static importDOM() {
2474
2528
  return {
2475
2529
  table: _node => ({
2476
- conversion: convertTableElement,
2530
+ conversion: $convertTableElement,
2477
2531
  priority: 1
2478
2532
  })
2479
2533
  };
@@ -2613,7 +2667,7 @@ function $getElementForTableNode(editor, tableNode) {
2613
2667
  }
2614
2668
  return getTable(tableElement);
2615
2669
  }
2616
- function convertTableElement(_domNode) {
2670
+ function $convertTableElement(_domNode) {
2617
2671
  return {
2618
2672
  node: $createTableNode()
2619
2673
  };
@@ -2626,6 +2680,7 @@ function $isTableNode(node) {
2626
2680
  }
2627
2681
 
2628
2682
  exports.$computeTableMap = $computeTableMap;
2683
+ exports.$computeTableMapSkipCellCheck = $computeTableMapSkipCellCheck;
2629
2684
  exports.$createTableCellNode = $createTableCellNode;
2630
2685
  exports.$createTableNode = $createTableNode;
2631
2686
  exports.$createTableNodeWithDimensions = $createTableNodeWithDimensions;