@lexical/table 0.21.1-nightly.20241206.0 → 0.21.1-nightly.20241210.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.
@@ -265,7 +265,7 @@ function $convertTableCellNodeElement(domNode) {
265
265
  node: tableCellNode
266
266
  };
267
267
  }
268
- function $createTableCellNode(headerState, colSpan = 1, width) {
268
+ function $createTableCellNode(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width) {
269
269
  return lexical.$applyNodeReplacement(new TableCellNode(headerState, colSpan, width));
270
270
  }
271
271
  function $isTableCellNode(node) {
@@ -282,28 +282,6 @@ function $isTableCellNode(node) {
282
282
 
283
283
  const INSERT_TABLE_COMMAND = lexical.createCommand('INSERT_TABLE_COMMAND');
284
284
 
285
- /**
286
- * Copyright (c) Meta Platforms, Inc. and affiliates.
287
- *
288
- * This source code is licensed under the MIT license found in the
289
- * LICENSE file in the root directory of this source tree.
290
- *
291
- */
292
-
293
- const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
294
-
295
- /**
296
- * Copyright (c) Meta Platforms, Inc. and affiliates.
297
- *
298
- * This source code is licensed under the MIT license found in the
299
- * LICENSE file in the root directory of this source tree.
300
- *
301
- */
302
-
303
- const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
304
- const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
305
- CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
306
-
307
285
  /**
308
286
  * Copyright (c) Meta Platforms, Inc. and affiliates.
309
287
  *
@@ -386,6 +364,7 @@ function $convertTableRowElement(domNode) {
386
364
  height = parseFloat(domNode_.style.height);
387
365
  }
388
366
  return {
367
+ after: children => utils.$descendantsMatching(children, $isTableCellNode),
389
368
  node: $createTableRowNode(height)
390
369
  };
391
370
  }
@@ -396,6 +375,28 @@ function $isTableRowNode(node) {
396
375
  return node instanceof TableRowNode;
397
376
  }
398
377
 
378
+ /**
379
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
380
+ *
381
+ * This source code is licensed under the MIT license found in the
382
+ * LICENSE file in the root directory of this source tree.
383
+ *
384
+ */
385
+
386
+ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
387
+
388
+ /**
389
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
390
+ *
391
+ * This source code is licensed under the MIT license found in the
392
+ * LICENSE file in the root directory of this source tree.
393
+ *
394
+ */
395
+
396
+ const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
397
+ const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
398
+ CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
399
+
399
400
  /**
400
401
  * Copyright (c) Meta Platforms, Inc. and affiliates.
401
402
  *
@@ -3458,6 +3459,7 @@ function $convertTableElement(domNode) {
3458
3459
  }
3459
3460
  }
3460
3461
  return {
3462
+ after: children => utils.$descendantsMatching(children, $isTableRowNode),
3461
3463
  node: tableNode
3462
3464
  };
3463
3465
  }
@@ -3468,6 +3470,195 @@ function $isTableNode(node) {
3468
3470
  return node instanceof TableNode;
3469
3471
  }
3470
3472
 
3473
+ /**
3474
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3475
+ *
3476
+ * This source code is licensed under the MIT license found in the
3477
+ * LICENSE file in the root directory of this source tree.
3478
+ *
3479
+ */
3480
+
3481
+ function $insertTableCommandListener({
3482
+ rows,
3483
+ columns,
3484
+ includeHeaders
3485
+ }) {
3486
+ const tableNode = $createTableNodeWithDimensions(Number(rows), Number(columns), includeHeaders);
3487
+ utils.$insertNodeToNearestRoot(tableNode);
3488
+ const firstDescendant = tableNode.getFirstDescendant();
3489
+ if (lexical.$isTextNode(firstDescendant)) {
3490
+ firstDescendant.select();
3491
+ }
3492
+ return true;
3493
+ }
3494
+ function $tableCellTransform(node) {
3495
+ if (!$isTableRowNode(node.getParent())) {
3496
+ // TableCellNode must be a child of TableRowNode.
3497
+ node.remove();
3498
+ } else if (node.isEmpty()) {
3499
+ // TableCellNode should never be empty
3500
+ node.append(lexical.$createParagraphNode());
3501
+ }
3502
+ }
3503
+ function $tableRowTransform(node) {
3504
+ if (!$isTableNode(node.getParent())) {
3505
+ // TableRowNode must be a child of TableNode.
3506
+ // TODO: Future support of tbody/thead/tfoot may change this
3507
+ node.remove();
3508
+ } else {
3509
+ utils.$unwrapAndFilterDescendants(node, $isTableCellNode);
3510
+ }
3511
+ }
3512
+ function $tableTransform(node) {
3513
+ // TableRowNode is the only valid child for TableNode
3514
+ // TODO: Future support of tbody/thead/tfoot/caption may change this
3515
+ utils.$unwrapAndFilterDescendants(node, $isTableRowNode);
3516
+ const [gridMap] = $computeTableMapSkipCellCheck(node, null, null);
3517
+ const maxRowLength = gridMap.reduce((curLength, row) => {
3518
+ return Math.max(curLength, row.length);
3519
+ }, 0);
3520
+ const rowNodes = node.getChildren();
3521
+ for (let i = 0; i < gridMap.length; ++i) {
3522
+ const rowNode = rowNodes[i];
3523
+ if (!rowNode) {
3524
+ continue;
3525
+ }
3526
+ if (!$isTableRowNode(rowNode)) {
3527
+ throw Error(`TablePlugin: Expecting all children of TableNode to be TableRowNode, found ${rowNode.constructor.name} (type ${rowNode.getType()})`);
3528
+ }
3529
+ const rowLength = gridMap[i].reduce((acc, cell) => cell ? 1 + acc : acc, 0);
3530
+ if (rowLength === maxRowLength) {
3531
+ continue;
3532
+ }
3533
+ for (let j = rowLength; j < maxRowLength; ++j) {
3534
+ // TODO: inherit header state from another header or body
3535
+ const newCell = $createTableCellNode();
3536
+ newCell.append(lexical.$createParagraphNode());
3537
+ rowNode.append(newCell);
3538
+ }
3539
+ }
3540
+ }
3541
+
3542
+ /**
3543
+ * Register a transform to ensure that all TableCellNode have a colSpan and rowSpan of 1.
3544
+ * This should only be registered when you do not want to support merged cells.
3545
+ *
3546
+ * @param editor The editor
3547
+ * @returns An unregister callback
3548
+ */
3549
+ function registerTableCellUnmergeTransform(editor) {
3550
+ return editor.registerNodeTransform(TableCellNode, node => {
3551
+ if (node.getColSpan() > 1 || node.getRowSpan() > 1) {
3552
+ // When we have rowSpan we have to map the entire Table to understand where the new Cells
3553
+ // fit best; let's analyze all Cells at once to save us from further transform iterations
3554
+ const [,, gridNode] = $getNodeTriplet(node);
3555
+ const [gridMap] = $computeTableMap(gridNode, node, node);
3556
+ // TODO this function expects Tables to be normalized. Look into this once it exists
3557
+ const rowsCount = gridMap.length;
3558
+ const columnsCount = gridMap[0].length;
3559
+ let row = gridNode.getFirstChild();
3560
+ if (!$isTableRowNode(row)) {
3561
+ throw Error(`Expected TableNode first child to be a RowNode`);
3562
+ }
3563
+ const unmerged = [];
3564
+ for (let i = 0; i < rowsCount; i++) {
3565
+ if (i !== 0) {
3566
+ row = row.getNextSibling();
3567
+ if (!$isTableRowNode(row)) {
3568
+ throw Error(`Expected TableNode first child to be a RowNode`);
3569
+ }
3570
+ }
3571
+ let lastRowCell = null;
3572
+ for (let j = 0; j < columnsCount; j++) {
3573
+ const cellMap = gridMap[i][j];
3574
+ const cell = cellMap.cell;
3575
+ if (cellMap.startRow === i && cellMap.startColumn === j) {
3576
+ lastRowCell = cell;
3577
+ unmerged.push(cell);
3578
+ } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) {
3579
+ if (!$isTableCellNode(cell)) {
3580
+ throw Error(`Expected TableNode cell to be a TableCellNode`);
3581
+ }
3582
+ const newCell = $createTableCellNode(cell.__headerState);
3583
+ if (lastRowCell !== null) {
3584
+ lastRowCell.insertAfter(newCell);
3585
+ } else {
3586
+ utils.$insertFirst(row, newCell);
3587
+ }
3588
+ }
3589
+ }
3590
+ }
3591
+ for (const cell of unmerged) {
3592
+ cell.setColSpan(1);
3593
+ cell.setRowSpan(1);
3594
+ }
3595
+ }
3596
+ });
3597
+ }
3598
+ function registerTableSelectionObserver(editor, hasTabHandler = true) {
3599
+ const tableSelections = new Map();
3600
+ const initializeTableNode = (tableNode, nodeKey, dom) => {
3601
+ const tableElement = getTableElement(tableNode, dom);
3602
+ const tableSelection = applyTableHandlers(tableNode, tableElement, editor, hasTabHandler);
3603
+ tableSelections.set(nodeKey, [tableSelection, tableElement]);
3604
+ };
3605
+ const unregisterMutationListener = editor.registerMutationListener(TableNode, nodeMutations => {
3606
+ editor.getEditorState().read(() => {
3607
+ for (const [nodeKey, mutation] of nodeMutations) {
3608
+ const tableSelection = tableSelections.get(nodeKey);
3609
+ if (mutation === 'created' || mutation === 'updated') {
3610
+ const {
3611
+ tableNode,
3612
+ tableElement
3613
+ } = $getTableAndElementByKey(nodeKey);
3614
+ if (tableSelection === undefined) {
3615
+ initializeTableNode(tableNode, nodeKey, tableElement);
3616
+ } else if (tableElement !== tableSelection[1]) {
3617
+ // The update created a new DOM node, destroy the existing TableObserver
3618
+ tableSelection[0].removeListeners();
3619
+ tableSelections.delete(nodeKey);
3620
+ initializeTableNode(tableNode, nodeKey, tableElement);
3621
+ }
3622
+ } else if (mutation === 'destroyed') {
3623
+ if (tableSelection !== undefined) {
3624
+ tableSelection[0].removeListeners();
3625
+ tableSelections.delete(nodeKey);
3626
+ }
3627
+ }
3628
+ }
3629
+ }, {
3630
+ editor
3631
+ });
3632
+ }, {
3633
+ skipInitialization: false
3634
+ });
3635
+ return () => {
3636
+ unregisterMutationListener();
3637
+ // Hook might be called multiple times so cleaning up tables listeners as well,
3638
+ // as it'll be reinitialized during recurring call
3639
+ for (const [, [tableSelection]] of tableSelections) {
3640
+ tableSelection.removeListeners();
3641
+ }
3642
+ };
3643
+ }
3644
+
3645
+ /**
3646
+ * Register the INSERT_TABLE_COMMAND listener and the table integrity transforms. The
3647
+ * table selection observer should be registered separately after this with
3648
+ * {@link registerTableSelectionObserver}.
3649
+ *
3650
+ * @param editor The editor
3651
+ * @returns An unregister callback
3652
+ */
3653
+ function registerTablePlugin(editor) {
3654
+ if (!editor.hasNodes([TableNode])) {
3655
+ {
3656
+ throw Error(`TablePlugin: TableNode is not registered on editor`);
3657
+ }
3658
+ }
3659
+ return utils.mergeRegister(editor.registerCommand(INSERT_TABLE_COMMAND, $insertTableCommandListener, lexical.COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(TableNode, $tableTransform), editor.registerNodeTransform(TableRowNode, $tableRowTransform), editor.registerNodeTransform(TableCellNode, $tableCellTransform));
3660
+ }
3661
+
3471
3662
  exports.$computeTableMap = $computeTableMap;
3472
3663
  exports.$computeTableMapSkipCellCheck = $computeTableMapSkipCellCheck;
3473
3664
  exports.$createTableCellNode = $createTableCellNode;
@@ -3510,4 +3701,7 @@ exports.applyTableHandlers = applyTableHandlers;
3510
3701
  exports.getDOMCellFromTarget = getDOMCellFromTarget;
3511
3702
  exports.getTableElement = getTableElement;
3512
3703
  exports.getTableObserverFromTableElement = getTableObserverFromTableElement;
3704
+ exports.registerTableCellUnmergeTransform = registerTableCellUnmergeTransform;
3705
+ exports.registerTablePlugin = registerTablePlugin;
3706
+ exports.registerTableSelectionObserver = registerTableSelectionObserver;
3513
3707
  exports.setScrollableTablesActive = setScrollableTablesActive;
@@ -6,8 +6,8 @@
6
6
  *
7
7
  */
8
8
 
9
- import { addClassNamesToElement, $findMatchingParent, removeClassNamesFromElement, objectKlassEquals, isHTMLElement as isHTMLElement$1 } from '@lexical/utils';
10
- import { ElementNode, isHTMLElement, $createParagraphNode, $isElementNode, $isLineBreakNode, $isTextNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $createPoint, $isParagraphNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getNodeByKey, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $createRangeSelection, $getRoot, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_CRITICAL, CUT_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $getPreviousSelection, $getNearestNodeFromDOMNode, $createRangeSelectionFromDom, INSERT_PARAGRAPH_COMMAND, $isRootOrShadowRoot, $isDecoratorNode, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, setDOMUnmanaged } from 'lexical';
9
+ import { addClassNamesToElement, $descendantsMatching, $findMatchingParent, removeClassNamesFromElement, objectKlassEquals, isHTMLElement as isHTMLElement$1, $insertFirst as $insertFirst$1, mergeRegister, $insertNodeToNearestRoot, $unwrapAndFilterDescendants } from '@lexical/utils';
10
+ import { ElementNode, isHTMLElement, $createParagraphNode, $isElementNode, $isLineBreakNode, $isTextNode, $applyNodeReplacement, createCommand, $createTextNode, $getSelection, $isRangeSelection, $createPoint, $isParagraphNode, $normalizeSelection__EXPERIMENTAL, isCurrentlyReadOnlyMode, TEXT_TYPE_TO_FORMAT, $getNodeByKey, $getEditor, $setSelection, SELECTION_CHANGE_COMMAND, getDOMSelection, $createRangeSelection, $getRoot, COMMAND_PRIORITY_HIGH, KEY_ESCAPE_COMMAND, COMMAND_PRIORITY_CRITICAL, CUT_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $getPreviousSelection, $getNearestNodeFromDOMNode, $createRangeSelectionFromDom, INSERT_PARAGRAPH_COMMAND, $isRootOrShadowRoot, $isDecoratorNode, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, setDOMUnmanaged, COMMAND_PRIORITY_EDITOR } from 'lexical';
11
11
  import { copyToClipboard, $getClipboardDataFromSelection } from '@lexical/clipboard';
12
12
 
13
13
  /**
@@ -263,7 +263,7 @@ function $convertTableCellNodeElement(domNode) {
263
263
  node: tableCellNode
264
264
  };
265
265
  }
266
- function $createTableCellNode(headerState, colSpan = 1, width) {
266
+ function $createTableCellNode(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width) {
267
267
  return $applyNodeReplacement(new TableCellNode(headerState, colSpan, width));
268
268
  }
269
269
  function $isTableCellNode(node) {
@@ -280,28 +280,6 @@ function $isTableCellNode(node) {
280
280
 
281
281
  const INSERT_TABLE_COMMAND = createCommand('INSERT_TABLE_COMMAND');
282
282
 
283
- /**
284
- * Copyright (c) Meta Platforms, Inc. and affiliates.
285
- *
286
- * This source code is licensed under the MIT license found in the
287
- * LICENSE file in the root directory of this source tree.
288
- *
289
- */
290
-
291
- const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
292
-
293
- /**
294
- * Copyright (c) Meta Platforms, Inc. and affiliates.
295
- *
296
- * This source code is licensed under the MIT license found in the
297
- * LICENSE file in the root directory of this source tree.
298
- *
299
- */
300
-
301
- const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
302
- const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
303
- CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
304
-
305
283
  /**
306
284
  * Copyright (c) Meta Platforms, Inc. and affiliates.
307
285
  *
@@ -384,6 +362,7 @@ function $convertTableRowElement(domNode) {
384
362
  height = parseFloat(domNode_.style.height);
385
363
  }
386
364
  return {
365
+ after: children => $descendantsMatching(children, $isTableCellNode),
387
366
  node: $createTableRowNode(height)
388
367
  };
389
368
  }
@@ -394,6 +373,28 @@ function $isTableRowNode(node) {
394
373
  return node instanceof TableRowNode;
395
374
  }
396
375
 
376
+ /**
377
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
378
+ *
379
+ * This source code is licensed under the MIT license found in the
380
+ * LICENSE file in the root directory of this source tree.
381
+ *
382
+ */
383
+
384
+ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
385
+
386
+ /**
387
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
388
+ *
389
+ * This source code is licensed under the MIT license found in the
390
+ * LICENSE file in the root directory of this source tree.
391
+ *
392
+ */
393
+
394
+ const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
395
+ const IS_FIREFOX = CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
396
+ CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
397
+
397
398
  /**
398
399
  * Copyright (c) Meta Platforms, Inc. and affiliates.
399
400
  *
@@ -3456,6 +3457,7 @@ function $convertTableElement(domNode) {
3456
3457
  }
3457
3458
  }
3458
3459
  return {
3460
+ after: children => $descendantsMatching(children, $isTableRowNode),
3459
3461
  node: tableNode
3460
3462
  };
3461
3463
  }
@@ -3466,4 +3468,193 @@ function $isTableNode(node) {
3466
3468
  return node instanceof TableNode;
3467
3469
  }
3468
3470
 
3469
- export { $computeTableMap, $computeTableMapSkipCellCheck, $createTableCellNode, $createTableNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $findCellNode, $findTableNode, $getElementForTableNode, $getNodeTriplet, $getTableAndElementByKey, $getTableCellNodeFromLexicalNode, $getTableCellNodeRect, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $getTableRowNodeFromTableCellNodeOrThrow, $insertTableColumn, $insertTableColumn__EXPERIMENTAL, $insertTableRow, $insertTableRow__EXPERIMENTAL, $isScrollableTablesActive, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, $removeTableRowAtIndex, $unmergeCell, INSERT_TABLE_COMMAND, TableCellHeaderStates, TableCellNode, TableNode, TableObserver, TableRowNode, applyTableHandlers, getDOMCellFromTarget, getTableElement, getTableObserverFromTableElement, setScrollableTablesActive };
3471
+ /**
3472
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3473
+ *
3474
+ * This source code is licensed under the MIT license found in the
3475
+ * LICENSE file in the root directory of this source tree.
3476
+ *
3477
+ */
3478
+
3479
+ function $insertTableCommandListener({
3480
+ rows,
3481
+ columns,
3482
+ includeHeaders
3483
+ }) {
3484
+ const tableNode = $createTableNodeWithDimensions(Number(rows), Number(columns), includeHeaders);
3485
+ $insertNodeToNearestRoot(tableNode);
3486
+ const firstDescendant = tableNode.getFirstDescendant();
3487
+ if ($isTextNode(firstDescendant)) {
3488
+ firstDescendant.select();
3489
+ }
3490
+ return true;
3491
+ }
3492
+ function $tableCellTransform(node) {
3493
+ if (!$isTableRowNode(node.getParent())) {
3494
+ // TableCellNode must be a child of TableRowNode.
3495
+ node.remove();
3496
+ } else if (node.isEmpty()) {
3497
+ // TableCellNode should never be empty
3498
+ node.append($createParagraphNode());
3499
+ }
3500
+ }
3501
+ function $tableRowTransform(node) {
3502
+ if (!$isTableNode(node.getParent())) {
3503
+ // TableRowNode must be a child of TableNode.
3504
+ // TODO: Future support of tbody/thead/tfoot may change this
3505
+ node.remove();
3506
+ } else {
3507
+ $unwrapAndFilterDescendants(node, $isTableCellNode);
3508
+ }
3509
+ }
3510
+ function $tableTransform(node) {
3511
+ // TableRowNode is the only valid child for TableNode
3512
+ // TODO: Future support of tbody/thead/tfoot/caption may change this
3513
+ $unwrapAndFilterDescendants(node, $isTableRowNode);
3514
+ const [gridMap] = $computeTableMapSkipCellCheck(node, null, null);
3515
+ const maxRowLength = gridMap.reduce((curLength, row) => {
3516
+ return Math.max(curLength, row.length);
3517
+ }, 0);
3518
+ const rowNodes = node.getChildren();
3519
+ for (let i = 0; i < gridMap.length; ++i) {
3520
+ const rowNode = rowNodes[i];
3521
+ if (!rowNode) {
3522
+ continue;
3523
+ }
3524
+ if (!$isTableRowNode(rowNode)) {
3525
+ throw Error(`TablePlugin: Expecting all children of TableNode to be TableRowNode, found ${rowNode.constructor.name} (type ${rowNode.getType()})`);
3526
+ }
3527
+ const rowLength = gridMap[i].reduce((acc, cell) => cell ? 1 + acc : acc, 0);
3528
+ if (rowLength === maxRowLength) {
3529
+ continue;
3530
+ }
3531
+ for (let j = rowLength; j < maxRowLength; ++j) {
3532
+ // TODO: inherit header state from another header or body
3533
+ const newCell = $createTableCellNode();
3534
+ newCell.append($createParagraphNode());
3535
+ rowNode.append(newCell);
3536
+ }
3537
+ }
3538
+ }
3539
+
3540
+ /**
3541
+ * Register a transform to ensure that all TableCellNode have a colSpan and rowSpan of 1.
3542
+ * This should only be registered when you do not want to support merged cells.
3543
+ *
3544
+ * @param editor The editor
3545
+ * @returns An unregister callback
3546
+ */
3547
+ function registerTableCellUnmergeTransform(editor) {
3548
+ return editor.registerNodeTransform(TableCellNode, node => {
3549
+ if (node.getColSpan() > 1 || node.getRowSpan() > 1) {
3550
+ // When we have rowSpan we have to map the entire Table to understand where the new Cells
3551
+ // fit best; let's analyze all Cells at once to save us from further transform iterations
3552
+ const [,, gridNode] = $getNodeTriplet(node);
3553
+ const [gridMap] = $computeTableMap(gridNode, node, node);
3554
+ // TODO this function expects Tables to be normalized. Look into this once it exists
3555
+ const rowsCount = gridMap.length;
3556
+ const columnsCount = gridMap[0].length;
3557
+ let row = gridNode.getFirstChild();
3558
+ if (!$isTableRowNode(row)) {
3559
+ throw Error(`Expected TableNode first child to be a RowNode`);
3560
+ }
3561
+ const unmerged = [];
3562
+ for (let i = 0; i < rowsCount; i++) {
3563
+ if (i !== 0) {
3564
+ row = row.getNextSibling();
3565
+ if (!$isTableRowNode(row)) {
3566
+ throw Error(`Expected TableNode first child to be a RowNode`);
3567
+ }
3568
+ }
3569
+ let lastRowCell = null;
3570
+ for (let j = 0; j < columnsCount; j++) {
3571
+ const cellMap = gridMap[i][j];
3572
+ const cell = cellMap.cell;
3573
+ if (cellMap.startRow === i && cellMap.startColumn === j) {
3574
+ lastRowCell = cell;
3575
+ unmerged.push(cell);
3576
+ } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) {
3577
+ if (!$isTableCellNode(cell)) {
3578
+ throw Error(`Expected TableNode cell to be a TableCellNode`);
3579
+ }
3580
+ const newCell = $createTableCellNode(cell.__headerState);
3581
+ if (lastRowCell !== null) {
3582
+ lastRowCell.insertAfter(newCell);
3583
+ } else {
3584
+ $insertFirst$1(row, newCell);
3585
+ }
3586
+ }
3587
+ }
3588
+ }
3589
+ for (const cell of unmerged) {
3590
+ cell.setColSpan(1);
3591
+ cell.setRowSpan(1);
3592
+ }
3593
+ }
3594
+ });
3595
+ }
3596
+ function registerTableSelectionObserver(editor, hasTabHandler = true) {
3597
+ const tableSelections = new Map();
3598
+ const initializeTableNode = (tableNode, nodeKey, dom) => {
3599
+ const tableElement = getTableElement(tableNode, dom);
3600
+ const tableSelection = applyTableHandlers(tableNode, tableElement, editor, hasTabHandler);
3601
+ tableSelections.set(nodeKey, [tableSelection, tableElement]);
3602
+ };
3603
+ const unregisterMutationListener = editor.registerMutationListener(TableNode, nodeMutations => {
3604
+ editor.getEditorState().read(() => {
3605
+ for (const [nodeKey, mutation] of nodeMutations) {
3606
+ const tableSelection = tableSelections.get(nodeKey);
3607
+ if (mutation === 'created' || mutation === 'updated') {
3608
+ const {
3609
+ tableNode,
3610
+ tableElement
3611
+ } = $getTableAndElementByKey(nodeKey);
3612
+ if (tableSelection === undefined) {
3613
+ initializeTableNode(tableNode, nodeKey, tableElement);
3614
+ } else if (tableElement !== tableSelection[1]) {
3615
+ // The update created a new DOM node, destroy the existing TableObserver
3616
+ tableSelection[0].removeListeners();
3617
+ tableSelections.delete(nodeKey);
3618
+ initializeTableNode(tableNode, nodeKey, tableElement);
3619
+ }
3620
+ } else if (mutation === 'destroyed') {
3621
+ if (tableSelection !== undefined) {
3622
+ tableSelection[0].removeListeners();
3623
+ tableSelections.delete(nodeKey);
3624
+ }
3625
+ }
3626
+ }
3627
+ }, {
3628
+ editor
3629
+ });
3630
+ }, {
3631
+ skipInitialization: false
3632
+ });
3633
+ return () => {
3634
+ unregisterMutationListener();
3635
+ // Hook might be called multiple times so cleaning up tables listeners as well,
3636
+ // as it'll be reinitialized during recurring call
3637
+ for (const [, [tableSelection]] of tableSelections) {
3638
+ tableSelection.removeListeners();
3639
+ }
3640
+ };
3641
+ }
3642
+
3643
+ /**
3644
+ * Register the INSERT_TABLE_COMMAND listener and the table integrity transforms. The
3645
+ * table selection observer should be registered separately after this with
3646
+ * {@link registerTableSelectionObserver}.
3647
+ *
3648
+ * @param editor The editor
3649
+ * @returns An unregister callback
3650
+ */
3651
+ function registerTablePlugin(editor) {
3652
+ if (!editor.hasNodes([TableNode])) {
3653
+ {
3654
+ throw Error(`TablePlugin: TableNode is not registered on editor`);
3655
+ }
3656
+ }
3657
+ return mergeRegister(editor.registerCommand(INSERT_TABLE_COMMAND, $insertTableCommandListener, COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(TableNode, $tableTransform), editor.registerNodeTransform(TableRowNode, $tableRowTransform), editor.registerNodeTransform(TableCellNode, $tableCellTransform));
3658
+ }
3659
+
3660
+ export { $computeTableMap, $computeTableMapSkipCellCheck, $createTableCellNode, $createTableNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $findCellNode, $findTableNode, $getElementForTableNode, $getNodeTriplet, $getTableAndElementByKey, $getTableCellNodeFromLexicalNode, $getTableCellNodeRect, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $getTableRowNodeFromTableCellNodeOrThrow, $insertTableColumn, $insertTableColumn__EXPERIMENTAL, $insertTableRow, $insertTableRow__EXPERIMENTAL, $isScrollableTablesActive, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, $removeTableRowAtIndex, $unmergeCell, INSERT_TABLE_COMMAND, TableCellHeaderStates, TableCellNode, TableNode, TableObserver, TableRowNode, applyTableHandlers, getDOMCellFromTarget, getTableElement, getTableObserverFromTableElement, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive };
@@ -75,7 +75,7 @@ declare export class TableCellNode extends ElementNode {
75
75
  canBeEmpty(): false;
76
76
  }
77
77
  declare export function $createTableCellNode(
78
- headerState: TableCellHeaderState,
78
+ headerState?: TableCellHeaderState,
79
79
  colSpan?: number,
80
80
  width?: ?number,
81
81
  ): TableCellNode;
@@ -350,4 +350,14 @@ export type InsertTableCommandPayload = $ReadOnly<{
350
350
  includeHeaders?: InsertTableCommandPayloadHeaders;
351
351
  }>;
352
352
 
353
- declare export var INSERT_TABLE_COMMAND: LexicalCommand<InsertTableCommandPayload>;
353
+ declare export var INSERT_TABLE_COMMAND: LexicalCommand<InsertTableCommandPayload>;
354
+
355
+ /**
356
+ * LexicalTablePluginHelpers
357
+ */
358
+
359
+ declare export function registerTableCellUnmergeTransform(editor: LexicalEditor): () => void;
360
+
361
+ declare export function registerTablePlugin(editor: LexicalEditor): () => void;
362
+
363
+ declare export function registerTableSelectionObserver(editor: LexicalEditor, hasTabHandler?: boolean): () => void;
package/LexicalTable.mjs CHANGED
@@ -51,4 +51,7 @@ export const applyTableHandlers = mod.applyTableHandlers;
51
51
  export const getDOMCellFromTarget = mod.getDOMCellFromTarget;
52
52
  export const getTableElement = mod.getTableElement;
53
53
  export const getTableObserverFromTableElement = mod.getTableObserverFromTableElement;
54
+ export const registerTableCellUnmergeTransform = mod.registerTableCellUnmergeTransform;
55
+ export const registerTablePlugin = mod.registerTablePlugin;
56
+ export const registerTableSelectionObserver = mod.registerTableSelectionObserver;
54
57
  export const setScrollableTablesActive = mod.setScrollableTablesActive;
@@ -49,4 +49,7 @@ export const applyTableHandlers = mod.applyTableHandlers;
49
49
  export const getDOMCellFromTarget = mod.getDOMCellFromTarget;
50
50
  export const getTableElement = mod.getTableElement;
51
51
  export const getTableObserverFromTableElement = mod.getTableObserverFromTableElement;
52
+ export const registerTableCellUnmergeTransform = mod.registerTableCellUnmergeTransform;
53
+ export const registerTablePlugin = mod.registerTablePlugin;
54
+ export const registerTableSelectionObserver = mod.registerTableSelectionObserver;
52
55
  export const setScrollableTablesActive = mod.setScrollableTablesActive;