@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.
- package/LexicalTable.dev.js +217 -23
- package/LexicalTable.dev.mjs +217 -26
- package/LexicalTable.js.flow +12 -2
- package/LexicalTable.mjs +3 -0
- package/LexicalTable.node.mjs +3 -0
- package/LexicalTable.prod.js +110 -105
- package/LexicalTable.prod.mjs +1 -1
- package/LexicalTableCellNode.d.ts +1 -1
- package/LexicalTablePluginHelpers.d.ts +26 -0
- package/index.d.ts +1 -0
- package/package.json +4 -4
package/LexicalTable.dev.js
CHANGED
@@ -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;
|
package/LexicalTable.dev.mjs
CHANGED
@@ -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
|
-
|
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 };
|
package/LexicalTable.js.flow
CHANGED
@@ -75,7 +75,7 @@ declare export class TableCellNode extends ElementNode {
|
|
75
75
|
canBeEmpty(): false;
|
76
76
|
}
|
77
77
|
declare export function $createTableCellNode(
|
78
|
-
headerState
|
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;
|
package/LexicalTable.node.mjs
CHANGED
@@ -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;
|