@lexical/table 0.1.14 → 0.1.17

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.
@@ -7,48 +7,51 @@
7
7
  'use strict';
8
8
 
9
9
  var lexical = require('lexical');
10
-
11
- /**
12
- * Copyright (c) Meta Platforms, Inc. and affiliates.
13
- *
14
- * This source code is licensed under the MIT license found in the
15
- * LICENSE file in the root directory of this source tree.
16
- *
17
- *
18
- */
19
- function addClassNamesToElement(element, ...classNames) {
20
- classNames.forEach(className => {
21
- if (className != null && typeof className === 'string') {
22
- element.classList.add(...className.split(' '));
23
- }
24
- });
25
- }
26
-
27
- /**
28
- * Copyright (c) Meta Platforms, Inc. and affiliates.
29
- *
30
- * This source code is licensed under the MIT license found in the
31
- * LICENSE file in the root directory of this source tree.
32
- *
33
- *
34
- */
10
+ var utils = require('@lexical/utils');
11
+
12
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
13
+ const TableCellHeaderStates = {
14
+ NO_STATUS: 0,
15
+ ROW: 1,
16
+ COLUMN: 2,
17
+ BOTH: 3
18
+ };
35
19
  class TableCellNode extends lexical.GridCellNode {
36
20
  static getType() {
37
21
  return 'tablecell';
38
22
  }
39
23
 
40
24
  static clone(node) {
41
- return new TableCellNode(new Set(node.__headerStyles), node.__colSpan, node.__key);
25
+ return new TableCellNode(node.__headerState, node.__colSpan, node.__width, node.__key);
42
26
  }
43
27
 
44
- constructor(headerStyles, colSpan = 1, key) {
28
+ static convertDOM() {
29
+ return {
30
+ td: node => ({
31
+ conversion: convertTableCellNodeElement,
32
+ priority: 0
33
+ }),
34
+ th: node => ({
35
+ conversion: convertTableCellNodeElement,
36
+ priority: 0
37
+ })
38
+ };
39
+ }
40
+
41
+ constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
45
42
  super(colSpan, key);
46
- this.__headerStyles = headerStyles || new Set();
43
+ this.__headerState = headerState;
44
+ this.__width = width;
47
45
  }
48
46
 
49
47
  createDOM(config) {
50
48
  const element = document.createElement(this.getTag());
51
- addClassNamesToElement(element, config.theme.tableCell, this.hasHeader() && config.theme.TableCellHeaderStyles);
49
+
50
+ if (this.__width) {
51
+ element.style.width = `${this.__width}px`;
52
+ }
53
+
54
+ utils.addClassNamesToElement(element, config.theme.tableCell, this.hasHeader() && config.theme.tableCellHeader);
52
55
  return element;
53
56
  }
54
57
 
@@ -56,38 +59,49 @@ class TableCellNode extends lexical.GridCellNode {
56
59
  return this.hasHeader() ? 'th' : 'td';
57
60
  }
58
61
 
59
- setHeaderStyles(headerStyles) {
62
+ setHeaderStyles(headerState) {
60
63
  const self = this.getWritable();
61
- self.__headerStyles = new Set(headerStyles);
62
- return this.__headerStyles;
64
+ self.__headerState = headerState;
65
+ return this.__headerState;
63
66
  }
64
67
 
65
68
  getHeaderStyles() {
66
- return this.getLatest().__headerStyles;
69
+ return this.getLatest().__headerState;
70
+ }
71
+
72
+ setWidth(width) {
73
+ const self = this.getWritable();
74
+ self.__width = width;
75
+ return this.__width;
76
+ }
77
+
78
+ getWidth() {
79
+ return this.getLatest().__width;
67
80
  }
68
81
 
69
- toggleHeaderStyle(key) {
82
+ toggleHeaderStyle(headerStateToToggle) {
70
83
  const self = this.getWritable();
71
- const newHeaderValue = self.getHeaderStyles();
72
84
 
73
- if (newHeaderValue.has(key)) {
74
- newHeaderValue.delete(key);
85
+ if ((self.__headerState & headerStateToToggle) === headerStateToToggle) {
86
+ self.__headerState -= headerStateToToggle;
75
87
  } else {
76
- newHeaderValue.add(key);
88
+ self.__headerState += headerStateToToggle;
77
89
  }
78
90
 
79
- self.__headerStyles = new Set(newHeaderValue);
91
+ self.__headerState = self.__headerState;
80
92
  return self;
81
93
  }
82
94
 
83
- hasHeader() {
84
- const headerStyles = this.getLatest().__headerStyles;
95
+ hasHeaderState(headerState) {
96
+ return (this.getHeaderStyles() & headerState) === headerState;
97
+ }
85
98
 
86
- return headerStyles.size > 0;
99
+ hasHeader() {
100
+ return this.getLatest().__headerState !== TableCellHeaderStates.NO_STATUS;
87
101
  }
88
102
 
89
103
  updateDOM(prevNode) {
90
- return prevNode.__headerStyles.size !== this.__headerStyles.size;
104
+ return prevNode.__headerState !== this.__headerState || prevNode.__width !== this.__width;
91
105
  }
92
106
 
93
107
  collapseAtStart() {
@@ -99,8 +113,29 @@ class TableCellNode extends lexical.GridCellNode {
99
113
  }
100
114
 
101
115
  }
102
- function $createTableCellNode(headerStyles) {
103
- return new TableCellNode(headerStyles);
116
+ function convertTableCellNodeElement(domNode) {
117
+ const nodeName = domNode.nodeName.toLowerCase();
118
+ const tableCellNode = $createTableCellNode(nodeName === 'th' ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS);
119
+ return {
120
+ node: tableCellNode,
121
+ forChild: (lexicalNode, parentLexicalNode) => {
122
+ if ($isTableCellNode(parentLexicalNode) && !lexical.$isElementNode(lexicalNode)) {
123
+ const paragraphNode = lexical.$createParagraphNode();
124
+
125
+ if (lexical.$isLineBreakNode(lexicalNode) && lexicalNode.getTextContent() === '\n') {
126
+ return null;
127
+ }
128
+
129
+ paragraphNode.append(lexicalNode);
130
+ return paragraphNode;
131
+ }
132
+
133
+ return lexicalNode;
134
+ }
135
+ };
136
+ }
137
+ function $createTableCellNode(headerState, colSpan = 1, width) {
138
+ return new TableCellNode(headerState, colSpan, width);
104
139
  }
105
140
  function $isTableCellNode(node) {
106
141
  return node instanceof TableCellNode;
@@ -114,18 +149,273 @@ function $isTableCellNode(node) {
114
149
  *
115
150
  *
116
151
  */
117
- function $findMatchingParent(startingNode, findFn) {
118
- let curr = startingNode;
152
+ const getSelection = () => window.getSelection();
119
153
 
120
- while (curr !== lexical.$getRoot() && curr != null) {
121
- if (findFn(curr)) {
122
- return curr;
123
- }
154
+ var getDOMSelection = getSelection;
124
155
 
125
- curr = curr.getParent();
156
+ /**
157
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
158
+ *
159
+ * This source code is licensed under the MIT license found in the
160
+ * LICENSE file in the root directory of this source tree.
161
+ *
162
+ *
163
+ */
164
+ const removeHighlightStyle = document.createElement('style');
165
+ removeHighlightStyle.appendChild(document.createTextNode('::selection{background-color: transparent}'));
166
+ class TableSelection {
167
+ constructor(editor, tableNodeKey) {
168
+ this.isHighlightingCells = false;
169
+ this.startX = -1;
170
+ this.startY = -1;
171
+ this.currentX = -1;
172
+ this.currentY = -1;
173
+ this.listenersToRemove = new Set();
174
+ this.tableNodeKey = tableNodeKey;
175
+ this.editor = editor;
176
+ this.grid = {
177
+ cells: [],
178
+ columns: 0,
179
+ rows: 0
180
+ };
181
+ this.gridSelection = null;
182
+ this.anchorCellNodeKey = null;
183
+ this.focusCellNodeKey = null;
184
+ this.anchorCell = null;
185
+ this.focusCell = null;
186
+ this.trackTableGrid();
187
+ }
188
+
189
+ getGrid() {
190
+ return this.grid;
191
+ }
192
+
193
+ removeListeners() {
194
+ Array.from(this.listenersToRemove).forEach(removeListener => removeListener());
195
+ }
196
+
197
+ trackTableGrid() {
198
+ const observer = new MutationObserver(records => {
199
+ this.editor.update(() => {
200
+ let gridNeedsRedraw = false;
201
+
202
+ for (let i = 0; i < records.length; i++) {
203
+ const record = records[i];
204
+ const target = record.target;
205
+ const nodeName = target.nodeName;
206
+
207
+ if (nodeName === 'TABLE' || nodeName === 'TR') {
208
+ gridNeedsRedraw = true;
209
+ break;
210
+ }
211
+ }
212
+
213
+ if (!gridNeedsRedraw) {
214
+ return;
215
+ }
216
+
217
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
218
+
219
+ if (!tableElement) {
220
+ throw new Error('Expected to find TableElement in DOM');
221
+ }
222
+
223
+ this.grid = getTableGrid(tableElement);
224
+ });
225
+ });
226
+ this.editor.update(() => {
227
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
228
+
229
+ if (!tableElement) {
230
+ throw new Error('Expected to find TableElement in DOM');
231
+ }
232
+
233
+ this.grid = getTableGrid(tableElement);
234
+ observer.observe(tableElement, {
235
+ childList: true,
236
+ subtree: true
237
+ });
238
+ });
239
+ }
240
+
241
+ clearHighlight() {
242
+ this.editor.update(() => {
243
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
244
+
245
+ if (!$isTableNode(tableNode)) {
246
+ throw new Error('Expected TableNode.');
247
+ }
248
+
249
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
250
+
251
+ if (!tableElement) {
252
+ throw new Error('Expected to find TableElement in DOM');
253
+ }
254
+
255
+ const grid = getTableGrid(tableElement);
256
+ this.isHighlightingCells = false;
257
+ this.startX = -1;
258
+ this.startY = -1;
259
+ this.currentX = -1;
260
+ this.currentY = -1;
261
+ this.gridSelection = null;
262
+ this.anchorCellNodeKey = null;
263
+ this.focusCellNodeKey = null;
264
+ this.anchorCell = null;
265
+ this.focusCell = null;
266
+ $updateDOMForSelection(grid, null);
267
+ lexical.$setSelection(null);
268
+ this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND);
269
+ const parent = removeHighlightStyle.parentNode;
270
+
271
+ if (parent != null) {
272
+ parent.removeChild(removeHighlightStyle);
273
+ }
274
+ });
275
+ }
276
+
277
+ adjustFocusCellForSelection(cell, ignoreStart = false) {
278
+ this.editor.update(() => {
279
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
280
+
281
+ if (!$isTableNode(tableNode)) {
282
+ throw new Error('Expected TableNode.');
283
+ }
284
+
285
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
286
+
287
+ if (!tableElement) {
288
+ throw new Error('Expected to find TableElement in DOM');
289
+ }
290
+
291
+ const cellX = cell.x;
292
+ const cellY = cell.y;
293
+ this.focusCell = cell;
294
+ const domSelection = getDOMSelection();
295
+
296
+ if (this.anchorCell !== null) {
297
+ // Collapse the selection
298
+ domSelection.setBaseAndExtent(this.anchorCell.elem, 0, cell.elem, 0);
299
+ }
300
+
301
+ if (!this.isHighlightingCells && (this.startX !== cellX || this.startY !== cellY || ignoreStart)) {
302
+ this.isHighlightingCells = true;
303
+
304
+ if (document.body) {
305
+ document.body.appendChild(removeHighlightStyle);
306
+ }
307
+ } else if (cellX === this.currentX && cellY === this.currentY) {
308
+ return;
309
+ }
310
+
311
+ this.currentX = cellX;
312
+ this.currentY = cellY;
313
+
314
+ if (this.isHighlightingCells) {
315
+ const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
316
+
317
+ if (this.gridSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
318
+ const focusNodeKey = focusTableCellNode.getKey();
319
+ this.gridSelection = lexical.$createGridSelection();
320
+ this.focusCellNodeKey = focusNodeKey;
321
+ this.gridSelection.set(this.tableNodeKey, // $FlowFixMe This is not null, as you can see in the statement above.
322
+ this.anchorCellNodeKey, this.focusCellNodeKey);
323
+ lexical.$setSelection(this.gridSelection);
324
+ this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND);
325
+ $updateDOMForSelection(this.grid, this.gridSelection);
326
+ }
327
+ }
328
+ });
329
+ }
330
+
331
+ setAnchorCellForSelection(cell) {
332
+ this.editor.update(() => {
333
+ this.anchorCell = cell;
334
+ this.startX = cell.x;
335
+ this.startY = cell.y;
336
+ const domSelection = getDOMSelection();
337
+ domSelection.setBaseAndExtent(cell.elem, 0, cell.elem, 0);
338
+ const anchorTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
339
+
340
+ if ($isTableCellNode(anchorTableCellNode)) {
341
+ const anchorNodeKey = anchorTableCellNode.getKey();
342
+ this.gridSelection = lexical.$createGridSelection();
343
+ this.anchorCellNodeKey = anchorNodeKey;
344
+ }
345
+ });
346
+ }
347
+
348
+ formatCells(type) {
349
+ this.editor.update(() => {
350
+ const selection = lexical.$getSelection();
351
+
352
+ if (!lexical.$isGridSelection(selection)) {
353
+ {
354
+ throw Error(`Expected grid selection`);
355
+ }
356
+ } // This is to make Flow play ball.
357
+
358
+
359
+ const formatSelection = lexical.$createRangeSelection();
360
+ const anchor = formatSelection.anchor;
361
+ const focus = formatSelection.focus;
362
+ selection.getNodes().forEach(cellNode => {
363
+ if ($isTableCellNode(cellNode) && cellNode.getTextContentSize() !== 0) {
364
+ anchor.set(cellNode.getKey(), 0, 'element');
365
+ focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
366
+ formatSelection.formatText(type);
367
+ }
368
+ });
369
+ lexical.$setSelection(selection);
370
+ this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND);
371
+ });
372
+ }
373
+
374
+ clearText() {
375
+ this.editor.update(() => {
376
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
377
+
378
+ if (!$isTableNode(tableNode)) {
379
+ throw new Error('Expected TableNode.');
380
+ }
381
+
382
+ const selection = lexical.$getSelection();
383
+
384
+ if (!lexical.$isGridSelection(selection)) {
385
+ {
386
+ throw Error(`Expected grid selection`);
387
+ }
388
+ }
389
+
390
+ const selectedNodes = selection.getNodes().filter($isTableCellNode);
391
+
392
+ if (selectedNodes.length === this.grid.columns * this.grid.rows) {
393
+ tableNode.selectPrevious(); // Delete entire table
394
+
395
+ tableNode.remove();
396
+ this.clearHighlight();
397
+ return;
398
+ }
399
+
400
+ selectedNodes.forEach(cellNode => {
401
+ if (lexical.$isElementNode(cellNode)) {
402
+ const paragraphNode = lexical.$createParagraphNode();
403
+ const textNode = lexical.$createTextNode();
404
+ paragraphNode.append(textNode);
405
+ cellNode.append(paragraphNode);
406
+ cellNode.getChildren().forEach(child => {
407
+ if (child !== paragraphNode) {
408
+ child.remove();
409
+ }
410
+ });
411
+ }
412
+ });
413
+ $updateDOMForSelection(this.grid, null);
414
+ lexical.$setSelection(null);
415
+ this.editor.dispatchCommand(lexical.SELECTION_CHANGE_COMMAND);
416
+ });
126
417
  }
127
418
 
128
- return null;
129
419
  }
130
420
 
131
421
  /**
@@ -136,22 +426,404 @@ function $findMatchingParent(startingNode, findFn) {
136
426
  *
137
427
  *
138
428
  */
139
- const getSelection = () => window.getSelection();
429
+ const CriticalPriority = 4;
430
+ const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
431
+ function applyTableHandlers(tableNode, tableElement, editor) {
432
+ const rootElement = editor.getRootElement();
433
+
434
+ if (rootElement === null) {
435
+ throw new Error('No root element.');
436
+ }
437
+
438
+ const tableSelection = new TableSelection(editor, tableNode.getKey());
439
+ attachTableSelectionToTableElement(tableElement, tableSelection);
440
+ let isMouseDown = false;
441
+ tableElement.addEventListener('dblclick', event => {
442
+ // $FlowFixMe: event.target is always a Node on the DOM
443
+ const cell = getCellFromTarget(event.target);
444
+
445
+ if (cell !== null) {
446
+ event.preventDefault();
447
+ event.stopImmediatePropagation();
448
+ event.stopPropagation();
449
+ tableSelection.setAnchorCellForSelection(cell);
450
+ tableSelection.adjustFocusCellForSelection(cell, true);
451
+ isMouseDown = false;
452
+ }
453
+ }); // This is the anchor of the selection.
454
+
455
+ tableElement.addEventListener('mousedown', event => {
456
+ setTimeout(() => {
457
+ if (event.button !== 0) {
458
+ return;
459
+ } // $FlowFixMe: event.target is always a Node on the DOM
460
+
461
+
462
+ const cell = getCellFromTarget(event.target);
463
+
464
+ if (cell !== null) {
465
+ isMouseDown = true;
466
+ tableSelection.setAnchorCellForSelection(cell);
467
+ document.addEventListener('mouseup', () => {
468
+ isMouseDown = false;
469
+ }, {
470
+ capture: true,
471
+ once: true
472
+ });
473
+ }
474
+ }, 0);
475
+ }); // This is adjusting the focus of the selection.
476
+
477
+ tableElement.addEventListener('mousemove', event => {
478
+ if (isMouseDown) {
479
+ // $FlowFixMe: event.target is always a Node on the DOM
480
+ const cell = getCellFromTarget(event.target);
481
+
482
+ if (cell !== null) {
483
+ const cellX = cell.x;
484
+ const cellY = cell.y;
485
+
486
+ if (isMouseDown && (tableSelection.startX !== cellX || tableSelection.startY !== cellY || tableSelection.isHighlightingCells)) {
487
+ event.preventDefault();
488
+ isMouseDown = true;
489
+ tableSelection.adjustFocusCellForSelection(cell);
490
+ }
491
+ }
492
+ }
493
+ });
494
+ tableElement.addEventListener('mouseup', event => {
495
+ if (isMouseDown) {
496
+ isMouseDown = false;
497
+ }
498
+ }); // Select entire table at this point, when grid selection is ready.
499
+
500
+ tableElement.addEventListener('mouseleave', event => {
501
+ if (isMouseDown) {
502
+ return;
503
+ }
504
+ }); // Clear selection when clicking outside of dom.
505
+
506
+ const mouseDownCallback = event => {
507
+ if (event.button !== 0) {
508
+ return;
509
+ }
510
+
511
+ editor.update(() => {
512
+ const selection = lexical.$getSelection();
513
+
514
+ if (lexical.$isGridSelection(selection) && selection.gridKey === tableSelection.tableNodeKey && rootElement.contains(event.target)) {
515
+ return tableSelection.clearHighlight();
516
+ }
517
+ });
518
+ };
519
+
520
+ window.addEventListener('mousedown', mouseDownCallback);
521
+ tableSelection.listenersToRemove.add(() => window.removeEventListener('mousedown', mouseDownCallback));
522
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, payload => {
523
+ const selection = lexical.$getSelection();
524
+ const event = payload;
525
+ const direction = 'down';
526
+
527
+ if (lexical.$isRangeSelection(selection)) {
528
+ if (selection.isCollapsed()) {
529
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
530
+
531
+ if (!$isTableCellNode(tableCellNode)) {
532
+ return false;
533
+ }
534
+
535
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
536
+ const elementParentNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
537
+
538
+ if (elementParentNode == null) {
539
+ throw new Error('Expected BlockNode Parent');
540
+ }
541
+
542
+ const lastChild = tableCellNode.getLastChild();
543
+ const isSelectionInLastBlock = lastChild && elementParentNode.isParentOf(lastChild) || elementParentNode === lastChild;
544
+
545
+ if (isSelectionInLastBlock || event.shiftKey) {
546
+ event.preventDefault();
547
+ event.stopImmediatePropagation();
548
+ event.stopPropagation(); // Start Selection
549
+
550
+ if (event.shiftKey) {
551
+ tableSelection.setAnchorCellForSelection(tableNode.getCellFromCordsOrThrow(currentCords.x, currentCords.y, tableSelection.grid));
552
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
553
+ }
554
+
555
+ return selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
556
+ }
557
+ }
558
+ } else if (lexical.$isGridSelection(selection) && event.shiftKey) {
559
+ const tableCellNode = selection.focus.getNode();
560
+
561
+ if (!$isTableCellNode(tableCellNode)) {
562
+ return false;
563
+ }
564
+
565
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
566
+ event.preventDefault();
567
+ event.stopImmediatePropagation();
568
+ event.stopPropagation();
569
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
570
+ }
571
+
572
+ return false;
573
+ }, CriticalPriority));
574
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, payload => {
575
+ const selection = lexical.$getSelection();
576
+ const event = payload;
577
+ const direction = 'up';
578
+
579
+ if (lexical.$isRangeSelection(selection)) {
580
+ if (selection.isCollapsed()) {
581
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
582
+
583
+ if (!$isTableCellNode(tableCellNode)) {
584
+ return false;
585
+ }
586
+
587
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
588
+ const elementParentNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
589
+
590
+ if (elementParentNode == null) {
591
+ throw new Error('Expected BlockNode Parent');
592
+ }
593
+
594
+ const lastChild = tableCellNode.getLastChild();
595
+ const isSelectionInLastBlock = lastChild && elementParentNode.isParentOf(lastChild) || elementParentNode === lastChild;
596
+
597
+ if (isSelectionInLastBlock || event.shiftKey) {
598
+ event.preventDefault();
599
+ event.stopImmediatePropagation();
600
+ event.stopPropagation(); // Start Selection
601
+
602
+ if (event.shiftKey) {
603
+ tableSelection.setAnchorCellForSelection(tableNode.getCellFromCordsOrThrow(currentCords.x, currentCords.y, tableSelection.grid));
604
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
605
+ }
606
+
607
+ return selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
608
+ }
609
+ }
610
+ } else if (lexical.$isGridSelection(selection) && event.shiftKey) {
611
+ const tableCellNode = selection.focus.getNode();
612
+
613
+ if (!$isTableCellNode(tableCellNode)) {
614
+ return false;
615
+ }
616
+
617
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
618
+ event.preventDefault();
619
+ event.stopImmediatePropagation();
620
+ event.stopPropagation();
621
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
622
+ }
623
+
624
+ return false;
625
+ }, CriticalPriority));
626
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, payload => {
627
+ const selection = lexical.$getSelection();
628
+ const event = payload;
629
+ const direction = 'backward';
630
+
631
+ if (lexical.$isRangeSelection(selection)) {
632
+ if (selection.isCollapsed()) {
633
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
634
+
635
+ if (!$isTableCellNode(tableCellNode)) {
636
+ return false;
637
+ }
638
+
639
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
640
+ const elementParentNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
641
+
642
+ if (elementParentNode == null) {
643
+ throw new Error('Expected BlockNode Parent');
644
+ }
645
+
646
+ if (selection.anchor.offset === 0 || event.shiftKey) {
647
+ event.preventDefault();
648
+ event.stopImmediatePropagation();
649
+ event.stopPropagation(); // Start Selection
650
+
651
+ if (event.shiftKey) {
652
+ tableSelection.setAnchorCellForSelection(tableNode.getCellFromCordsOrThrow(currentCords.x, currentCords.y, tableSelection.grid));
653
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
654
+ }
655
+
656
+ return selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
657
+ }
658
+ }
659
+ } else if (lexical.$isGridSelection(selection) && event.shiftKey) {
660
+ const tableCellNode = selection.focus.getNode();
661
+
662
+ if (!$isTableCellNode(tableCellNode)) {
663
+ return false;
664
+ }
665
+
666
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
667
+ event.preventDefault();
668
+ event.stopImmediatePropagation();
669
+ event.stopPropagation();
670
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
671
+ }
672
+
673
+ return false;
674
+ }, CriticalPriority));
675
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_ARROW_RIGHT_COMMAND, payload => {
676
+ const selection = lexical.$getSelection();
677
+ const event = payload;
678
+ const direction = 'forward';
679
+
680
+ if (lexical.$isRangeSelection(selection)) {
681
+ if (selection.isCollapsed()) {
682
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
683
+
684
+ if (!$isTableCellNode(tableCellNode)) {
685
+ return false;
686
+ }
687
+
688
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
689
+ const elementParentNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
690
+
691
+ if (elementParentNode == null) {
692
+ throw new Error('Expected BlockNode Parent');
693
+ }
694
+
695
+ if (selection.anchor.offset === selection.anchor.getNode().getTextContentSize() || event.shiftKey) {
696
+ event.preventDefault();
697
+ event.stopImmediatePropagation();
698
+ event.stopPropagation(); // Start Selection
699
+
700
+ if (event.shiftKey) {
701
+ tableSelection.setAnchorCellForSelection(tableNode.getCellFromCordsOrThrow(currentCords.x, currentCords.y, tableSelection.grid));
702
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
703
+ }
704
+
705
+ return selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
706
+ }
707
+ }
708
+ } else if (lexical.$isGridSelection(selection) && event.shiftKey) {
709
+ const tableCellNode = selection.focus.getNode();
710
+
711
+ if (!$isTableCellNode(tableCellNode)) {
712
+ return false;
713
+ }
714
+
715
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
716
+ event.preventDefault();
717
+ event.stopImmediatePropagation();
718
+ event.stopPropagation();
719
+ return adjustFocusNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, direction);
720
+ }
721
+
722
+ return false;
723
+ }, CriticalPriority));
724
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.DELETE_CHARACTER_COMMAND, () => {
725
+ const selection = lexical.$getSelection();
726
+
727
+ if (lexical.$isGridSelection(selection)) {
728
+ tableSelection.clearText();
729
+ return true;
730
+ } else if (lexical.$isRangeSelection(selection)) {
731
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
732
+
733
+ if (!$isTableCellNode(tableCellNode)) {
734
+ return false;
735
+ }
736
+
737
+ if (selection.isCollapsed() && selection.anchor.offset === 0 && selection.anchor.getNode().getPreviousSiblings().length === 0) {
738
+ return true;
739
+ }
740
+ }
741
+
742
+ return false;
743
+ }, CriticalPriority));
744
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, payload => {
745
+ const selection = lexical.$getSelection();
746
+
747
+ if (lexical.$isGridSelection(selection)) {
748
+ const event = payload;
749
+ event.preventDefault();
750
+ event.stopPropagation();
751
+ tableSelection.clearText();
752
+ return true;
753
+ } else if (lexical.$isRangeSelection(selection)) {
754
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
755
+
756
+ if (!$isTableCellNode(tableCellNode)) {
757
+ return false;
758
+ }
759
+ }
760
+
761
+ return false;
762
+ }, CriticalPriority));
763
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.FORMAT_TEXT_COMMAND, payload => {
764
+ const selection = lexical.$getSelection();
765
+
766
+ if (lexical.$isGridSelection(selection)) {
767
+ tableSelection.formatCells(payload);
768
+ return true;
769
+ } else if (lexical.$isRangeSelection(selection)) {
770
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
771
+
772
+ if (!$isTableCellNode(tableCellNode)) {
773
+ return false;
774
+ }
775
+ }
776
+
777
+ return false;
778
+ }, CriticalPriority));
779
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.INSERT_TEXT_COMMAND, payload => {
780
+ const selection = lexical.$getSelection();
781
+
782
+ if (lexical.$isGridSelection(selection)) {
783
+ tableSelection.clearHighlight();
784
+ return false;
785
+ } else if (lexical.$isRangeSelection(selection)) {
786
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
787
+
788
+ if (!$isTableCellNode(tableCellNode)) {
789
+ return false;
790
+ }
791
+ }
792
+
793
+ return false;
794
+ }, CriticalPriority));
795
+ tableSelection.listenersToRemove.add(editor.registerCommand(lexical.KEY_TAB_COMMAND, payload => {
796
+ const selection = lexical.$getSelection();
797
+
798
+ if (lexical.$isRangeSelection(selection)) {
799
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
800
+
801
+ if (!$isTableCellNode(tableCellNode)) {
802
+ return false;
803
+ }
140
804
 
141
- var getDOMSelection = getSelection;
805
+ const event = payload;
142
806
 
143
- /**
144
- * Copyright (c) Meta Platforms, Inc. and affiliates.
145
- *
146
- * This source code is licensed under the MIT license found in the
147
- * LICENSE file in the root directory of this source tree.
148
- *
149
- *
150
- */
151
- const LowPriority = 1;
152
- const CriticalPriority = 4;
153
- const removeHighlightStyle = document.createElement('style');
154
- removeHighlightStyle.appendChild(document.createTextNode('::selection{background-color: transparent}'));
807
+ if (selection.isCollapsed()) {
808
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
809
+ event.preventDefault();
810
+ selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, !event.shiftKey ? 'forward' : 'backward');
811
+ return true;
812
+ }
813
+ }
814
+
815
+ return false;
816
+ }, CriticalPriority));
817
+ return tableSelection;
818
+ }
819
+ function attachTableSelectionToTableElement(tableElement, tableSelection) {
820
+ // $FlowFixMe
821
+ tableElement[LEXICAL_ELEMENT_KEY] = tableSelection;
822
+ }
823
+ function getTableSelectionFromTableElement(tableElement) {
824
+ // $FlowFixMe
825
+ return tableElement[LEXICAL_ELEMENT_KEY];
826
+ }
155
827
  function getCellFromTarget(node) {
156
828
  let currentNode = node;
157
829
 
@@ -174,108 +846,80 @@ function getCellFromTarget(node) {
174
846
 
175
847
  return null;
176
848
  }
177
- function trackTableGrid(tableNode, tableElement, editor) {
849
+ function getTableGrid(tableElement) {
178
850
  const cells = [];
179
851
  const grid = {
180
852
  cells,
181
853
  columns: 0,
182
854
  rows: 0
183
855
  };
856
+ let currentNode = tableElement.firstChild;
857
+ let x = 0;
858
+ let y = 0;
859
+ cells.length = 0;
184
860
 
185
- const calcSize = () => {
186
- let currentNode = tableElement.firstChild;
187
- let x = 0;
188
- let y = 0;
189
- cells.length = 0;
190
-
191
- while (currentNode != null) {
192
- const nodeMame = currentNode.nodeName;
193
-
194
- if (nodeMame === 'TD' || nodeMame === 'TH') {
195
- // $FlowFixMe: TD is always an HTMLElement
196
- const elem = currentNode;
197
- const cell = {
198
- elem,
199
- highlighted: false,
200
- x,
201
- y
202
- }; // $FlowFixMe: internal field
203
-
204
- currentNode._cell = cell;
205
-
206
- if (cells[y] === undefined) {
207
- cells[y] = [];
208
- }
209
-
210
- cells[y][x] = cell;
211
- } else {
212
- const child = currentNode.firstChild;
213
-
214
- if (child != null) {
215
- currentNode = child;
216
- continue;
217
- }
861
+ while (currentNode != null) {
862
+ const nodeMame = currentNode.nodeName;
863
+
864
+ if (nodeMame === 'TD' || nodeMame === 'TH') {
865
+ // $FlowFixMe: TD is always an HTMLElement
866
+ const elem = currentNode;
867
+ const cell = {
868
+ elem,
869
+ highlighted: false,
870
+ x,
871
+ y
872
+ }; // $FlowFixMe: internal field
873
+
874
+ currentNode._cell = cell;
875
+
876
+ if (cells[y] === undefined) {
877
+ cells[y] = [];
218
878
  }
219
879
 
220
- const sibling = currentNode.nextSibling;
880
+ cells[y][x] = cell;
881
+ } else {
882
+ const child = currentNode.firstChild;
221
883
 
222
- if (sibling != null) {
223
- x++;
224
- currentNode = sibling;
884
+ if (child != null) {
885
+ currentNode = child;
225
886
  continue;
226
887
  }
888
+ }
227
889
 
228
- const parent = currentNode.parentNode;
229
-
230
- if (parent != null) {
231
- const parentSibling = parent.nextSibling;
232
-
233
- if (parentSibling == null) {
234
- break;
235
- }
890
+ const sibling = currentNode.nextSibling;
236
891
 
237
- y++;
238
- x = 0;
239
- currentNode = parentSibling;
240
- }
892
+ if (sibling != null) {
893
+ x++;
894
+ currentNode = sibling;
895
+ continue;
241
896
  }
242
897
 
243
- grid.columns = x + 1;
244
- grid.rows = y + 1;
245
- tableNode.setGrid(grid);
246
- };
247
-
248
- const observer = new MutationObserver(records => {
249
- editor.update(() => {
250
- let gridNeedsRedraw = false;
898
+ const parent = currentNode.parentNode;
251
899
 
252
- for (let i = 0; i < records.length; i++) {
253
- const record = records[i];
254
- const target = record.target;
255
- const nodeName = target.nodeName;
900
+ if (parent != null) {
901
+ const parentSibling = parent.nextSibling;
256
902
 
257
- if (nodeName === 'TABLE' || nodeName === 'TR') {
258
- gridNeedsRedraw = true;
259
- break;
260
- }
903
+ if (parentSibling == null) {
904
+ break;
261
905
  }
262
906
 
263
- if (!gridNeedsRedraw) {
264
- return;
265
- }
907
+ y++;
908
+ x = 0;
909
+ currentNode = parentSibling;
910
+ }
911
+ }
266
912
 
267
- calcSize();
268
- });
269
- });
270
- observer.observe(tableElement, {
271
- childList: true,
272
- subtree: true
273
- });
274
- calcSize();
913
+ grid.columns = x + 1;
914
+ grid.rows = y + 1;
275
915
  return grid;
276
916
  }
277
- function updateCells(fromX, toX, fromY, toY, cells) {
278
- const highlighted = [];
917
+ function $updateDOMForSelection(grid, gridSelection) {
918
+ const highlightedCells = [];
919
+ const {
920
+ cells
921
+ } = grid;
922
+ const selectedCellNodes = new Set(gridSelection ? gridSelection.getNodes() : []);
279
923
 
280
924
  for (let y = 0; y < cells.length; y++) {
281
925
  const row = cells[y];
@@ -283,16 +927,14 @@ function updateCells(fromX, toX, fromY, toY, cells) {
283
927
  for (let x = 0; x < row.length; x++) {
284
928
  const cell = row[x];
285
929
  const elemStyle = cell.elem.style;
930
+ const lexicalNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
286
931
 
287
- if (x >= fromX && x <= toX && y >= fromY && y <= toY) {
288
- if (!cell.highlighted) {
289
- cell.highlighted = true;
290
- elemStyle.setProperty('background-color', 'rgb(163, 187, 255)');
291
- elemStyle.setProperty('caret-color', 'transparent');
292
- }
293
-
294
- highlighted.push(cell);
295
- } else if (cell.highlighted) {
932
+ if (lexicalNode && selectedCellNodes.has(lexicalNode)) {
933
+ cell.highlighted = true;
934
+ elemStyle.setProperty('background-color', 'rgb(163, 187, 255)');
935
+ elemStyle.setProperty('caret-color', 'transparent');
936
+ highlightedCells.push(cell);
937
+ } else {
296
938
  cell.highlighted = false;
297
939
  elemStyle.removeProperty('background-color');
298
940
  elemStyle.removeProperty('caret-color');
@@ -304,319 +946,103 @@ function updateCells(fromX, toX, fromY, toY, cells) {
304
946
  }
305
947
  }
306
948
 
307
- return highlighted;
949
+ return highlightedCells;
308
950
  }
309
- function $applyCustomTableHandlers(tableNode, tableElement, editor) {
310
- const rootElement = editor.getRootElement();
311
-
312
- if (rootElement === null) {
313
- throw new Error('No root element.');
314
- }
315
-
316
- trackTableGrid(tableNode, tableElement, editor);
317
- const grid = tableNode.getGrid();
318
- let isSelected = false;
319
- let isHighlightingCells = false;
320
- let startX = -1;
321
- let startY = -1;
322
- let currentX = -1;
323
- let currentY = -1;
324
- let highlightedCells = [];
325
- const editorListeners = new Set();
326
- let deleteCharacterListener = null;
327
-
328
- if (grid == null) {
329
- throw new Error('Table grid not found.');
330
- }
331
-
332
- tableElement.addEventListener('mousemove', event => {
333
- if (isSelected) {
334
- // $FlowFixMe: event.target is always a Node on the DOM
335
- const cell = getCellFromTarget(event.target);
336
-
337
- if (cell !== null) {
338
- const cellX = cell.x;
339
- const cellY = cell.y;
340
-
341
- if (!isHighlightingCells && (startX !== cellX || startY !== cellY)) {
342
- event.preventDefault();
343
- const domSelection = getDOMSelection();
344
- const anchorNode = domSelection.anchorNode;
345
951
 
346
- if (anchorNode !== null) {
347
- // Collapse the selection
348
- domSelection.setBaseAndExtent(anchorNode, 0, anchorNode, 0);
349
- }
350
-
351
- isHighlightingCells = true;
352
-
353
- if (document.body) {
354
- document.body.appendChild(removeHighlightStyle);
355
- }
952
+ const selectGridNodeInDirection = (tableSelection, tableNode, x, y, direction) => {
953
+ switch (direction) {
954
+ case 'backward':
955
+ case 'forward':
956
+ {
957
+ const isForward = direction === 'forward';
356
958
 
357
- if (deleteCharacterListener === null) {
358
- deleteCharacterListener = editor.addListener('command', (type, payload) => {
359
- if (type === 'deleteCharacter') {
360
- if (highlightedCells.length === grid.columns * grid.rows) {
361
- tableNode.selectPrevious(); // Delete entire table
362
-
363
- tableNode.remove();
364
- clearHighlight();
365
- return true;
366
- }
367
-
368
- highlightedCells.forEach(({
369
- elem
370
- }) => {
371
- const cellNode = lexical.$getNearestNodeFromDOMNode(elem);
372
-
373
- if (lexical.$isElementNode(cellNode)) {
374
- const paragraphNode = lexical.$createParagraphNode();
375
- const textNode = lexical.$createTextNode();
376
- paragraphNode.append(textNode);
377
- cellNode.append(paragraphNode);
378
- cellNode.getChildren().forEach(child => {
379
- if (child !== paragraphNode) {
380
- child.remove();
381
- }
382
- });
383
- }
384
- });
385
- tableNode.setSelectionState(null);
386
- lexical.$setSelection(null);
387
- return true;
388
- } else if (type === 'formatText') {
389
- formatCells(payload);
390
- return true;
391
- } else if (type === 'insertText') {
392
- clearHighlight();
393
- return false;
394
- }
395
-
396
- return false;
397
- }, LowPriority);
398
- editorListeners.add(deleteCharacterListener);
959
+ if (x !== (isForward ? tableSelection.grid.columns - 1 : 0)) {
960
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableSelection.grid));
961
+ } else {
962
+ if (y !== (isForward ? tableSelection.grid.rows - 1 : 0)) {
963
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : tableSelection.grid.columns - 1, y + (isForward ? 1 : -1), tableSelection.grid));
964
+ } else if (!isForward) {
965
+ tableNode.selectPrevious();
966
+ } else {
967
+ tableNode.selectNext();
399
968
  }
400
- } else if (cellX === currentX && cellY === currentY) {
401
- return;
402
- }
403
-
404
- currentX = cellX;
405
- currentY = cellY;
406
-
407
- if (isHighlightingCells) {
408
- const fromX = Math.min(startX, currentX);
409
- const toX = Math.max(startX, currentX);
410
- const fromY = Math.min(startY, currentY);
411
- const toY = Math.max(startY, currentY);
412
- editor.update(() => {
413
- highlightedCells = tableNode.setSelectionState({
414
- fromX,
415
- fromY,
416
- toX,
417
- toY
418
- });
419
- });
420
969
  }
421
- }
422
- }
423
- });
424
-
425
- const clearHighlight = () => {
426
- editor.update(() => {
427
- isHighlightingCells = false;
428
- isSelected = false;
429
- startX = -1;
430
- startY = -1;
431
- currentX = -1;
432
- currentY = -1;
433
- editor.update(() => {
434
- tableNode.setSelectionState(null);
435
- });
436
- highlightedCells = [];
437
-
438
- if (deleteCharacterListener !== null) {
439
- deleteCharacterListener();
440
- deleteCharacterListener = null;
441
- editorListeners.delete(deleteCharacterListener);
442
- }
443
-
444
- const parent = removeHighlightStyle.parentNode;
445
-
446
- if (parent != null) {
447
- parent.removeChild(removeHighlightStyle);
448
- }
449
- });
450
- };
451
-
452
- tableElement.addEventListener('mouseleave', event => {
453
- if (isSelected) {
454
- return;
455
- }
456
- });
457
-
458
- const formatCells = type => {
459
- let selection = lexical.$getSelection();
460
-
461
- if (!lexical.$isRangeSelection(selection)) {
462
- selection = lexical.$createRangeSelection();
463
- } // This is to make Flow play ball.
464
-
465
970
 
466
- const formatSelection = selection;
467
- const anchor = formatSelection.anchor;
468
- const focus = formatSelection.focus;
469
- highlightedCells.forEach(highlightedCell => {
470
- const cellNode = lexical.$getNearestNodeFromDOMNode(highlightedCell.elem);
471
-
472
- if (lexical.$isElementNode(cellNode) && cellNode.getTextContentSize() !== 0) {
473
- anchor.set(cellNode.getKey(), 0, 'element');
474
- focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
475
- formatSelection.formatText(type);
971
+ return true;
476
972
  }
477
- }); // Collapse selection
478
973
 
479
- selection.anchor.set(selection.anchor.key, selection.anchor.offset, selection.anchor.type);
480
- selection.focus.set(selection.anchor.key, selection.anchor.offset, selection.anchor.type);
481
- lexical.$setSelection(selection);
482
- };
974
+ case 'up':
975
+ {
976
+ if (y !== 0) {
977
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableSelection.grid));
978
+ } else {
979
+ tableNode.selectPrevious();
980
+ }
483
981
 
484
- tableElement.addEventListener('mousedown', event => {
485
- if (isSelected) {
486
- if (isHighlightingCells) {
487
- clearHighlight();
982
+ return true;
488
983
  }
489
984
 
490
- return;
491
- }
492
-
493
- setTimeout(() => {
494
- if (isHighlightingCells) {
495
- clearHighlight();
496
- } // $FlowFixMe: event.target is always a Node on the DOM
497
-
498
-
499
- const cell = getCellFromTarget(event.target);
985
+ case 'down':
986
+ {
987
+ if (y !== tableSelection.grid.rows - 1) {
988
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableSelection.grid));
989
+ } else {
990
+ tableNode.selectNext();
991
+ }
500
992
 
501
- if (cell !== null) {
502
- isSelected = true;
503
- startX = cell.x;
504
- startY = cell.y;
505
- document.addEventListener('mouseup', () => {
506
- isSelected = false;
507
- }, {
508
- capture: true,
509
- once: true
510
- });
993
+ return true;
511
994
  }
512
- }, 0);
513
- });
514
- window.addEventListener('click', e => {
515
- if (highlightedCells.length > 0 && !tableElement.contains(e.target) && rootElement.contains(e.target)) {
516
- editor.update(() => {
517
- tableNode.setSelectionState(null);
518
- });
519
- }
520
- });
995
+ }
521
996
 
522
- const selectGridNodeInDirection = (x, y, direction) => {
523
- switch (direction) {
524
- case 'backward':
525
- case 'forward':
526
- {
527
- const isForward = direction === 'forward';
997
+ return false;
998
+ };
528
999
 
529
- if (x !== (isForward ? grid.columns - 1 : 0)) {
530
- tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y).select();
531
- } else {
532
- if (y !== (isForward ? grid.rows - 1 : 0)) {
533
- tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : grid.columns - 1, y + (isForward ? 1 : -1)).select();
534
- } else if (!isForward) {
535
- tableNode.selectPrevious();
536
- } else {
537
- tableNode.selectNext();
538
- }
539
- }
1000
+ const adjustFocusNodeInDirection = (tableSelection, tableNode, x, y, direction) => {
1001
+ switch (direction) {
1002
+ case 'backward':
1003
+ case 'forward':
1004
+ {
1005
+ const isForward = direction === 'forward';
540
1006
 
541
- return true;
1007
+ if (x !== (isForward ? tableSelection.grid.columns - 1 : 0)) {
1008
+ tableSelection.adjustFocusCellForSelection(tableNode.getCellFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableSelection.grid));
542
1009
  }
543
1010
 
544
- case 'up':
545
- {
546
- if (y !== 0) {
547
- tableNode.getCellNodeFromCordsOrThrow(x, y - 1).select();
548
- } else {
549
- tableNode.selectPrevious();
550
- }
1011
+ return true;
1012
+ }
551
1013
 
1014
+ case 'up':
1015
+ {
1016
+ if (y !== 0) {
1017
+ tableSelection.adjustFocusCellForSelection(tableNode.getCellFromCordsOrThrow(x, y - 1, tableSelection.grid));
552
1018
  return true;
1019
+ } else {
1020
+ return false;
553
1021
  }
1022
+ }
554
1023
 
555
- case 'down':
556
- {
557
- if (y !== grid.rows - 1) {
558
- tableNode.getCellNodeFromCordsOrThrow(x, y + 1).select();
559
- } else {
560
- tableNode.selectNext();
561
- }
562
-
1024
+ case 'down':
1025
+ {
1026
+ if (y !== tableSelection.grid.rows - 1) {
1027
+ tableSelection.adjustFocusCellForSelection(tableNode.getCellFromCordsOrThrow(x, y + 1, tableSelection.grid));
563
1028
  return true;
1029
+ } else {
1030
+ return false;
564
1031
  }
565
- }
566
-
567
- return false;
568
- };
569
-
570
- const genericCommandListener = editor.addListener('command', (type, payload) => {
571
- const selection = lexical.$getSelection();
572
-
573
- if (!lexical.$isRangeSelection(selection)) {
574
- return false;
575
- }
576
-
577
- const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
578
-
579
- if (!$isTableCellNode(tableCellNode)) {
580
- return false;
581
- }
582
-
583
- if (type === 'deleteCharacter') {
584
- if (highlightedCells.length === 0 && selection.isCollapsed() && selection.anchor.offset === 0 && selection.anchor.getNode().getPreviousSiblings().length === 0) {
585
- return true;
586
- }
587
- }
588
-
589
- if (type === 'keyTab') {
590
- const event = payload;
591
-
592
- if (selection.isCollapsed() && highlightedCells.length === 0) {
593
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode);
594
- event.preventDefault();
595
- selectGridNodeInDirection(currentCords.x, currentCords.y, !event.shiftKey && type === 'keyTab' ? 'forward' : 'backward');
596
- return true;
597
1032
  }
598
- }
599
-
600
- if (type === 'keyArrowDown' || type === 'keyArrowUp') {
601
- const event = payload;
1033
+ }
602
1034
 
603
- if (selection.isCollapsed() && highlightedCells.length === 0) {
604
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode);
605
- const elementParentNode = $findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
1035
+ return false;
1036
+ };
606
1037
 
607
- if (type === 'keyArrowUp' && elementParentNode === tableCellNode.getFirstChild() || type === 'keyArrowDown' && elementParentNode === tableCellNode.getLastChild()) {
608
- event.preventDefault();
609
- event.stopImmediatePropagation();
610
- selectGridNodeInDirection(currentCords.x, currentCords.y, type === 'keyArrowUp' ? 'up' : 'down');
611
- return true;
612
- }
613
- }
614
- }
1038
+ function selectTableCellNode(tableCell) {
1039
+ const possibleParagraph = tableCell.getChildren().find(n => lexical.$isParagraphNode(n));
615
1040
 
616
- return false;
617
- }, CriticalPriority);
618
- editorListeners.add(genericCommandListener);
619
- return () => Array.from(editorListeners).forEach(removeListener => removeListener ? removeListener() : null);
1041
+ if (lexical.$isParagraphNode(possibleParagraph)) {
1042
+ possibleParagraph.selectEnd();
1043
+ } else {
1044
+ tableCell.selectEnd();
1045
+ }
620
1046
  }
621
1047
 
622
1048
  /**
@@ -632,21 +1058,26 @@ class TableNode extends lexical.GridNode {
632
1058
  return 'table';
633
1059
  }
634
1060
 
635
- static clone(node, selectionShape, grid) {
636
- // TODO: selectionShape and grid aren't being deeply cloned?
637
- // They shouldn't really be on the table node IMO.
638
- return new TableNode(node.__selectionShape, node.__grid, node.__key);
1061
+ static clone(node) {
1062
+ return new TableNode(node.__key);
1063
+ }
1064
+
1065
+ static convertDOM() {
1066
+ return {
1067
+ table: node => ({
1068
+ conversion: convertTableElement,
1069
+ priority: 0
1070
+ })
1071
+ };
639
1072
  }
640
1073
 
641
- constructor(selectionShape, grid, key) {
1074
+ constructor(key) {
642
1075
  super(key);
643
- this.__selectionShape = selectionShape;
644
- this.__grid = grid;
645
1076
  }
646
1077
 
647
1078
  createDOM(config, editor) {
648
1079
  const element = document.createElement('table');
649
- addClassNamesToElement(element, config.theme.table);
1080
+ utils.addClassNamesToElement(element, config.theme.table);
650
1081
  return element;
651
1082
  }
652
1083
 
@@ -662,26 +1093,7 @@ class TableNode extends lexical.GridNode {
662
1093
  return false;
663
1094
  }
664
1095
 
665
- setSelectionState(selectionShape) {
666
- const self = this.getWritable();
667
- self.__selectionShape = selectionShape;
668
- const grid = this.getGrid();
669
- if (grid == null) return [];
670
-
671
- if (!selectionShape) {
672
- return updateCells(-1, -1, -1, -1, grid.cells);
673
- }
674
-
675
- return updateCells(selectionShape.fromX, selectionShape.toX, selectionShape.fromY, selectionShape.toY, grid.cells);
676
- }
677
-
678
- getSelectionState() {
679
- return this.getLatest().__selectionShape;
680
- }
681
-
682
- getCordsFromCellNode(tableCellNode) {
683
- const grid = this.getGrid();
684
-
1096
+ getCordsFromCellNode(tableCellNode, grid) {
685
1097
  if (!grid) {
686
1098
  throw Error(`Grid not found.`);
687
1099
  }
@@ -716,9 +1128,7 @@ class TableNode extends lexical.GridNode {
716
1128
  throw new Error('Cell not found in table.');
717
1129
  }
718
1130
 
719
- getCellNodeFromCords(x, y) {
720
- const grid = this.getGrid();
721
-
1131
+ getCellFromCords(x, y, grid) {
722
1132
  if (!grid) {
723
1133
  throw Error(`Grid not found.`);
724
1134
  }
@@ -738,6 +1148,26 @@ class TableNode extends lexical.GridNode {
738
1148
  return null;
739
1149
  }
740
1150
 
1151
+ return cell;
1152
+ }
1153
+
1154
+ getCellFromCordsOrThrow(x, y, grid) {
1155
+ const cell = this.getCellFromCords(x, y, grid);
1156
+
1157
+ if (!cell) {
1158
+ throw new Error('Cell not found at cords.');
1159
+ }
1160
+
1161
+ return cell;
1162
+ }
1163
+
1164
+ getCellNodeFromCords(x, y, grid) {
1165
+ const cell = this.getCellFromCords(x, y, grid);
1166
+
1167
+ if (cell == null) {
1168
+ return null;
1169
+ }
1170
+
741
1171
  const node = lexical.$getNearestNodeFromDOMNode(cell.elem);
742
1172
 
743
1173
  if ($isTableCellNode(node)) {
@@ -747,8 +1177,8 @@ class TableNode extends lexical.GridNode {
747
1177
  return null;
748
1178
  }
749
1179
 
750
- getCellNodeFromCordsOrThrow(x, y) {
751
- const node = this.getCellNodeFromCords(x, y);
1180
+ getCellNodeFromCordsOrThrow(x, y, grid) {
1181
+ const node = this.getCellNodeFromCords(x, y, grid);
752
1182
 
753
1183
  if (!node) {
754
1184
  throw new Error('Node at cords not TableCellNode.');
@@ -757,20 +1187,24 @@ class TableNode extends lexical.GridNode {
757
1187
  return node;
758
1188
  }
759
1189
 
760
- setGrid(grid) {
761
- const self = this.getWritable();
762
- self.__grid = grid;
763
- return self;
1190
+ canSelectBefore() {
1191
+ return true;
764
1192
  }
765
1193
 
766
- getGrid() {
767
- return this.getLatest().__grid;
768
- }
1194
+ }
1195
+ function $getElementGridForTableNode(editor, tableNode) {
1196
+ const tableElement = editor.getElementByKey(tableNode.getKey());
769
1197
 
770
- canSelectBefore() {
771
- return true;
1198
+ if (tableElement == null) {
1199
+ throw new Error('Table Element Not Found');
772
1200
  }
773
1201
 
1202
+ return getTableGrid(tableElement);
1203
+ }
1204
+ function convertTableElement(domNode) {
1205
+ return {
1206
+ node: $createTableNode()
1207
+ };
774
1208
  }
775
1209
  function $createTableNode() {
776
1210
  return new TableNode();
@@ -793,21 +1227,46 @@ class TableRowNode extends lexical.GridRowNode {
793
1227
  }
794
1228
 
795
1229
  static clone(node) {
796
- return new TableRowNode(node.__key);
1230
+ return new TableRowNode(node.__height, node.__key);
797
1231
  }
798
1232
 
799
- constructor(key) {
1233
+ static convertDOM() {
1234
+ return {
1235
+ tr: node => ({
1236
+ conversion: convertTableRowElement,
1237
+ priority: 0
1238
+ })
1239
+ };
1240
+ }
1241
+
1242
+ constructor(height, key) {
800
1243
  super(key);
1244
+ this.__height = height;
801
1245
  }
802
1246
 
803
1247
  createDOM(config) {
804
1248
  const element = document.createElement('tr');
805
- addClassNamesToElement(element, config.theme.tableRow);
1249
+
1250
+ if (this.__height) {
1251
+ element.style.height = `${this.__height}px`;
1252
+ }
1253
+
1254
+ utils.addClassNamesToElement(element, config.theme.tableRow);
806
1255
  return element;
807
1256
  }
808
1257
 
809
- updateDOM() {
810
- return false;
1258
+ setHeight(height) {
1259
+ const self = this.getWritable();
1260
+ self.__height = height;
1261
+ return this.__height;
1262
+ }
1263
+
1264
+ getHeight() {
1265
+ return this.getLatest().__height;
1266
+ }
1267
+
1268
+ updateDOM(prevNode) {
1269
+ return prevNode.__height !== this.__height;
811
1270
  }
812
1271
 
813
1272
  canBeEmpty() {
@@ -815,8 +1274,13 @@ class TableRowNode extends lexical.GridRowNode {
815
1274
  }
816
1275
 
817
1276
  }
818
- function $createTableRowNode() {
819
- return new TableRowNode();
1277
+ function convertTableRowElement(domNode) {
1278
+ return {
1279
+ node: $createTableRowNode()
1280
+ };
1281
+ }
1282
+ function $createTableRowNode(height) {
1283
+ return new TableRowNode(height);
820
1284
  }
821
1285
  function $isTableRowNode(node) {
822
1286
  return node instanceof TableRowNode;
@@ -837,14 +1301,14 @@ function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders =
837
1301
  const tableRowNode = $createTableRowNode();
838
1302
 
839
1303
  for (let iColumn = 0; iColumn < columnCount; iColumn++) {
840
- const headerStyles = new Set();
1304
+ let headerState = TableCellHeaderStates.NO_STATUS;
841
1305
 
842
1306
  if (includeHeaders) {
843
- if (iRow === 0) headerStyles.add('row');
844
- if (iColumn === 0) headerStyles.add('column');
1307
+ if (iRow === 0) headerState |= TableCellHeaderStates.ROW;
1308
+ if (iColumn === 0) headerState |= TableCellHeaderStates.COLUMN;
845
1309
  }
846
1310
 
847
- const tableCellNode = $createTableCellNode(headerStyles);
1311
+ const tableCellNode = $createTableCellNode(headerState);
848
1312
  const paragraphNode = lexical.$createParagraphNode();
849
1313
  paragraphNode.append(lexical.$createTextNode());
850
1314
  tableCellNode.append(paragraphNode);
@@ -857,7 +1321,7 @@ function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders =
857
1321
  return tableNode;
858
1322
  }
859
1323
  function $getTableCellNodeFromLexicalNode(startingNode) {
860
- const node = $findMatchingParent(startingNode, n => $isTableCellNode(n));
1324
+ const node = utils.$findMatchingParent(startingNode, n => $isTableCellNode(n));
861
1325
 
862
1326
  if ($isTableCellNode(node)) {
863
1327
  return node;
@@ -866,7 +1330,7 @@ function $getTableCellNodeFromLexicalNode(startingNode) {
866
1330
  return null;
867
1331
  }
868
1332
  function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
869
- const node = $findMatchingParent(startingNode, n => $isTableRowNode(n));
1333
+ const node = utils.$findMatchingParent(startingNode, n => $isTableRowNode(n));
870
1334
 
871
1335
  if ($isTableRowNode(node)) {
872
1336
  return node;
@@ -875,7 +1339,7 @@ function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
875
1339
  throw new Error('Expected table cell to be inside of table row.');
876
1340
  }
877
1341
  function $getTableNodeFromLexicalNodeOrThrow(startingNode) {
878
- const node = $findMatchingParent(startingNode, n => $isTableNode(n));
1342
+ const node = utils.$findMatchingParent(startingNode, n => $isTableNode(n));
879
1343
 
880
1344
  if ($isTableNode(node)) {
881
1345
  return node;
@@ -892,17 +1356,17 @@ function $getTableColumnIndexFromTableCellNode(tableCellNode) {
892
1356
  const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
893
1357
  return tableRowNode.getChildren().findIndex(n => n.is(tableCellNode));
894
1358
  }
895
- function $getTableCellSiblingsFromTableCellNode(tableCellNode) {
1359
+ function $getTableCellSiblingsFromTableCellNode(tableCellNode, grid) {
896
1360
  const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
897
1361
  const {
898
1362
  x,
899
1363
  y
900
- } = tableNode.getCordsFromCellNode(tableCellNode);
1364
+ } = tableNode.getCordsFromCellNode(tableCellNode, grid);
901
1365
  return {
902
- above: tableNode.getCellNodeFromCords(x, y - 1),
903
- below: tableNode.getCellNodeFromCords(x, y + 1),
904
- left: tableNode.getCellNodeFromCords(x - 1, y),
905
- right: tableNode.getCellNodeFromCords(x + 1, y)
1366
+ above: tableNode.getCellNodeFromCords(x, y - 1, grid),
1367
+ below: tableNode.getCellNodeFromCords(x, y + 1, grid),
1368
+ left: tableNode.getCellNodeFromCords(x - 1, y, grid),
1369
+ right: tableNode.getCellNodeFromCords(x + 1, y, grid)
906
1370
  };
907
1371
  }
908
1372
  function $removeTableRowAtIndex(tableNode, indexToDelete) {
@@ -916,7 +1380,7 @@ function $removeTableRowAtIndex(tableNode, indexToDelete) {
916
1380
  targetRowNode.remove();
917
1381
  return tableNode;
918
1382
  }
919
- function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount) {
1383
+ function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, grid) {
920
1384
  const tableRows = tableNode.getChildren();
921
1385
 
922
1386
  if (targetIndex >= tableRows.length || targetIndex < 0) {
@@ -941,14 +1405,15 @@ function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCo
941
1405
  const {
942
1406
  above,
943
1407
  below
944
- } = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow);
945
- const headerStyles = new Set();
1408
+ } = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow, grid);
1409
+ let headerState = TableCellHeaderStates.NO_STATUS;
1410
+ const width = above && above.getWidth() || below && below.getWidth() || null;
946
1411
 
947
- if (above && above.getHeaderStyles().has('column') || below && below.getHeaderStyles().has('column')) {
948
- headerStyles.add('column');
1412
+ if (above && above.hasHeaderState(TableCellHeaderStates.COLUMN) || below && below.hasHeaderState(TableCellHeaderStates.COLUMN)) {
1413
+ headerState |= TableCellHeaderStates.COLUMN;
949
1414
  }
950
1415
 
951
- const tableCellNode = $createTableCellNode(headerStyles);
1416
+ const tableCellNode = $createTableCellNode(headerState, 1, width);
952
1417
  tableCellNode.append(lexical.$createParagraphNode());
953
1418
  newTableRowNode.append(tableCellNode);
954
1419
  }
@@ -973,13 +1438,13 @@ function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, co
973
1438
 
974
1439
  if ($isTableRowNode(currentTableRowNode)) {
975
1440
  for (let c = 0; c < columnCount; c++) {
976
- const headerStyles = new Set();
1441
+ let headerState = TableCellHeaderStates.NO_STATUS;
977
1442
 
978
1443
  if (r === 0) {
979
- headerStyles.add('row');
1444
+ headerState |= TableCellHeaderStates.ROW;
980
1445
  }
981
1446
 
982
- const newTableCell = $createTableCellNode(headerStyles);
1447
+ const newTableCell = $createTableCellNode(headerState);
983
1448
  newTableCell.append(lexical.$createParagraphNode());
984
1449
  const tableRowChildren = currentTableRowNode.getChildren();
985
1450
 
@@ -1020,12 +1485,22 @@ function $deleteTableColumn(tableNode, targetIndex) {
1020
1485
  return tableNode;
1021
1486
  }
1022
1487
 
1023
- exports.$applyCustomTableHandlers = $applyCustomTableHandlers;
1488
+ /**
1489
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1490
+ *
1491
+ * This source code is licensed under the MIT license found in the
1492
+ * LICENSE file in the root directory of this source tree.
1493
+ *
1494
+ *
1495
+ */
1496
+ const INSERT_TABLE_COMMAND = lexical.createCommand();
1497
+
1024
1498
  exports.$createTableCellNode = $createTableCellNode;
1025
1499
  exports.$createTableNode = $createTableNode;
1026
1500
  exports.$createTableNodeWithDimensions = $createTableNodeWithDimensions;
1027
1501
  exports.$createTableRowNode = $createTableRowNode;
1028
1502
  exports.$deleteTableColumn = $deleteTableColumn;
1503
+ exports.$getElementGridForTableNode = $getElementGridForTableNode;
1029
1504
  exports.$getTableCellNodeFromLexicalNode = $getTableCellNodeFromLexicalNode;
1030
1505
  exports.$getTableColumnIndexFromTableCellNode = $getTableColumnIndexFromTableCellNode;
1031
1506
  exports.$getTableNodeFromLexicalNodeOrThrow = $getTableNodeFromLexicalNodeOrThrow;
@@ -1037,6 +1512,12 @@ exports.$isTableCellNode = $isTableCellNode;
1037
1512
  exports.$isTableNode = $isTableNode;
1038
1513
  exports.$isTableRowNode = $isTableRowNode;
1039
1514
  exports.$removeTableRowAtIndex = $removeTableRowAtIndex;
1515
+ exports.INSERT_TABLE_COMMAND = INSERT_TABLE_COMMAND;
1516
+ exports.TableCellHeaderStates = TableCellHeaderStates;
1040
1517
  exports.TableCellNode = TableCellNode;
1041
1518
  exports.TableNode = TableNode;
1042
1519
  exports.TableRowNode = TableRowNode;
1520
+ exports.TableSelection = TableSelection;
1521
+ exports.applyTableHandlers = applyTableHandlers;
1522
+ exports.getCellFromTarget = getCellFromTarget;
1523
+ exports.getTableSelectionFromTableElement = getTableSelectionFromTableElement;