@lexical/table 0.1.12 → 0.1.15

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.
@@ -6,58 +6,89 @@
6
6
  */
7
7
  'use strict';
8
8
 
9
+ var utils = require('@lexical/utils');
9
10
  var lexical = require('lexical');
10
11
 
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
- */
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(false, node.__colSpan, node.__key);
25
+ return new TableCellNode(node.__headerState, node.__colSpan, node.__width, node.__key);
42
26
  }
43
27
 
44
- constructor(isHeader = false, colSpan = 1, key) {
28
+ constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
45
29
  super(colSpan, key);
46
- this.__isHeader = isHeader;
47
- }
48
-
49
- getTag() {
50
- return this.__isHeader ? 'th' : 'td';
30
+ this.__headerState = headerState;
31
+ this.__width = width;
51
32
  }
52
33
 
53
34
  createDOM(config) {
54
35
  const element = document.createElement(this.getTag());
55
- addClassNamesToElement(element, config.theme.tableCell, this.__isHeader === true && config.theme.tableCellHeader);
36
+
37
+ if (this.__width) {
38
+ element.style.width = `${this.__width}px`;
39
+ }
40
+
41
+ utils.addClassNamesToElement(element, config.theme.tableCell, this.hasHeader() && config.theme.tableCellHeader);
56
42
  return element;
57
43
  }
58
44
 
59
- updateDOM() {
60
- return false;
45
+ getTag() {
46
+ return this.hasHeader() ? 'th' : 'td';
47
+ }
48
+
49
+ setHeaderStyles(headerState) {
50
+ const self = this.getWritable();
51
+ self.__headerState = headerState;
52
+ return this.__headerState;
53
+ }
54
+
55
+ getHeaderStyles() {
56
+ return this.getLatest().__headerState;
57
+ }
58
+
59
+ setWidth(width) {
60
+ const self = this.getWritable();
61
+ self.__width = width;
62
+ return this.__width;
63
+ }
64
+
65
+ getWidth() {
66
+ return this.getLatest().__width;
67
+ }
68
+
69
+ toggleHeaderStyle(headerStateToToggle) {
70
+ const self = this.getWritable();
71
+
72
+ if ((self.__headerState & headerStateToToggle) === headerStateToToggle) {
73
+ self.__headerState -= headerStateToToggle;
74
+ } else {
75
+ self.__headerState += headerStateToToggle;
76
+ }
77
+
78
+ self.__headerState = self.__headerState;
79
+ return self;
80
+ }
81
+
82
+ hasHeaderState(headerState) {
83
+ return (this.getHeaderStyles() & headerState) === headerState;
84
+ }
85
+
86
+ hasHeader() {
87
+ return this.getLatest().__headerState !== TableCellHeaderStates.NO_STATUS;
88
+ }
89
+
90
+ updateDOM(prevNode) {
91
+ return prevNode.__headerState !== this.__headerState || prevNode.__width !== this.__width;
61
92
  }
62
93
 
63
94
  collapseAtStart() {
@@ -69,8 +100,8 @@ class TableCellNode extends lexical.GridCellNode {
69
100
  }
70
101
 
71
102
  }
72
- function $createTableCellNode(isHeader) {
73
- return new TableCellNode(isHeader);
103
+ function $createTableCellNode(headerState, colSpan = 1, width) {
104
+ return new TableCellNode(headerState, colSpan, width);
74
105
  }
75
106
  function $isTableCellNode(node) {
76
107
  return node instanceof TableCellNode;
@@ -84,19 +115,9 @@ function $isTableCellNode(node) {
84
115
  *
85
116
  *
86
117
  */
87
- function $findMatchingParent(startingNode, findFn) {
88
- let curr = startingNode;
118
+ const getSelection = () => window.getSelection();
89
119
 
90
- while (curr !== lexical.$getRoot() && curr != null) {
91
- if (findFn(curr)) {
92
- return curr;
93
- }
94
-
95
- curr = curr.getParent();
96
- }
97
-
98
- return null;
99
- }
120
+ var getDOMSelection = getSelection;
100
121
 
101
122
  /**
102
123
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -106,189 +127,307 @@ function $findMatchingParent(startingNode, findFn) {
106
127
  *
107
128
  *
108
129
  */
109
- const LowPriority = 1;
110
- const CriticalPriority = 4;
111
130
  const removeHighlightStyle = document.createElement('style');
112
131
  removeHighlightStyle.appendChild(document.createTextNode('::selection{background-color: transparent}'));
113
- function getCellFromTarget(node) {
114
- let currentNode = node;
115
-
116
- while (currentNode != null) {
117
- const nodeName = currentNode.nodeName;
118
-
119
- if (nodeName === 'TD' || nodeName === 'TH') {
120
- // $FlowFixMe: internal field
121
- const cell = currentNode._cell;
122
-
123
- if (cell === undefined) {
124
- return null;
125
- }
132
+ class TableSelection {
133
+ constructor(editor, tableNodeKey) {
134
+ this.isMouseDown = false;
135
+ this.isHighlightingCells = false;
136
+ this.startX = -1;
137
+ this.startY = -1;
138
+ this.currentX = -1;
139
+ this.currentY = -1;
140
+ this.listenersToRemove = new Set();
141
+ this.tableNodeKey = tableNodeKey;
142
+ this.editor = editor;
143
+ this.grid = {
144
+ cells: [],
145
+ columns: 0,
146
+ rows: 0
147
+ };
148
+ this.gridSelection = null;
149
+ this.anchorCellNodeKey = null;
150
+ this.focusCellNodeKey = null;
151
+ this.trackTableGrid();
152
+ }
126
153
 
127
- return cell;
128
- }
154
+ getGrid() {
155
+ return this.grid;
156
+ }
129
157
 
130
- currentNode = currentNode.parentNode;
158
+ removeListeners() {
159
+ Array.from(this.listenersToRemove).forEach(removeListener => removeListener());
131
160
  }
132
161
 
133
- return null;
134
- }
135
- function trackTableGrid(tableNode, tableElement, editor) {
136
- const cells = [];
137
- const grid = {
138
- cells,
139
- columns: 0,
140
- rows: 0
141
- };
162
+ trackTableGrid() {
163
+ const observer = new MutationObserver(records => {
164
+ this.editor.update(() => {
165
+ let gridNeedsRedraw = false;
142
166
 
143
- const calcSize = () => {
144
- let currentNode = tableElement.firstChild;
145
- let x = 0;
146
- let y = 0;
147
- cells.length = 0;
148
-
149
- while (currentNode != null) {
150
- const nodeMame = currentNode.nodeName;
151
-
152
- if (nodeMame === 'TD' || nodeMame === 'TH') {
153
- // $FlowFixMe: TD is always an HTMLElement
154
- const elem = currentNode;
155
- const cell = {
156
- elem,
157
- highlighted: false,
158
- x,
159
- y
160
- }; // $FlowFixMe: internal field
167
+ for (let i = 0; i < records.length; i++) {
168
+ const record = records[i];
169
+ const target = record.target;
170
+ const nodeName = target.nodeName;
161
171
 
162
- currentNode._cell = cell;
172
+ if (nodeName === 'TABLE' || nodeName === 'TR') {
173
+ gridNeedsRedraw = true;
174
+ break;
175
+ }
176
+ }
163
177
 
164
- if (cells[y] === undefined) {
165
- cells[y] = [];
178
+ if (!gridNeedsRedraw) {
179
+ return;
166
180
  }
167
181
 
168
- cells[y][x] = cell;
169
- } else {
170
- const child = currentNode.firstChild;
182
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
171
183
 
172
- if (child != null) {
173
- currentNode = child;
174
- continue;
184
+ if (!tableElement) {
185
+ throw new Error('Expected to find TableElement in DOM');
175
186
  }
187
+
188
+ this.grid = getTableGrid(tableElement);
189
+ });
190
+ });
191
+ this.editor.update(() => {
192
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
193
+
194
+ if (!tableElement) {
195
+ throw new Error('Expected to find TableElement in DOM');
176
196
  }
177
197
 
178
- const sibling = currentNode.nextSibling;
198
+ this.grid = getTableGrid(tableElement);
199
+ observer.observe(tableElement, {
200
+ childList: true,
201
+ subtree: true
202
+ });
203
+ });
204
+ }
179
205
 
180
- if (sibling != null) {
181
- x++;
182
- currentNode = sibling;
183
- continue;
206
+ clearHighlight() {
207
+ this.editor.update(() => {
208
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
209
+
210
+ if (!$isTableNode(tableNode)) {
211
+ throw new Error('Expected TableNode.');
184
212
  }
185
213
 
186
- const parent = currentNode.parentNode;
214
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
215
+
216
+ if (!tableElement) {
217
+ throw new Error('Expected to find TableElement in DOM');
218
+ }
219
+
220
+ const grid = getTableGrid(tableElement);
221
+ this.isHighlightingCells = false;
222
+ this.isMouseDown = false;
223
+ this.startX = -1;
224
+ this.startY = -1;
225
+ this.currentX = -1;
226
+ this.currentY = -1;
227
+ $updateDOMForSelection(grid, null);
228
+ this.gridSelection = null;
229
+ lexical.$setSelection(null);
230
+ const parent = removeHighlightStyle.parentNode;
187
231
 
188
232
  if (parent != null) {
189
- const parentSibling = parent.nextSibling;
233
+ parent.removeChild(removeHighlightStyle);
234
+ }
235
+ });
236
+ }
190
237
 
191
- if (parentSibling == null) {
192
- break;
193
- }
238
+ adjustFocusCellForSelection(cell, ignoreStart = false) {
239
+ this.editor.update(() => {
240
+ this.isMouseDown = true;
241
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
194
242
 
195
- y++;
196
- x = 0;
197
- currentNode = parentSibling;
243
+ if (!$isTableNode(tableNode)) {
244
+ throw new Error('Expected TableNode.');
198
245
  }
199
- }
200
246
 
201
- grid.columns = x + 1;
202
- grid.rows = y + 1;
203
- tableNode.setGrid(grid);
204
- };
247
+ const tableElement = this.editor.getElementByKey(this.tableNodeKey);
205
248
 
206
- const observer = new MutationObserver(records => {
207
- editor.update(() => {
208
- let gridNeedsRedraw = false;
249
+ if (!tableElement) {
250
+ throw new Error('Expected to find TableElement in DOM');
251
+ }
209
252
 
210
- for (let i = 0; i < records.length; i++) {
211
- const record = records[i];
212
- const target = record.target;
213
- const nodeName = target.nodeName;
253
+ const cellX = cell.x;
254
+ const cellY = cell.y;
214
255
 
215
- if (nodeName === 'TABLE' || nodeName === 'TR') {
216
- gridNeedsRedraw = true;
217
- break;
256
+ if (!this.isHighlightingCells && (this.startX !== cellX || this.startY !== cellY || ignoreStart)) {
257
+ const domSelection = getDOMSelection();
258
+ const anchorNode = domSelection.anchorNode;
259
+
260
+ if (anchorNode !== null) {
261
+ // Collapse the selection
262
+ domSelection.setBaseAndExtent(anchorNode, 0, anchorNode, 0);
218
263
  }
219
- }
220
264
 
221
- if (!gridNeedsRedraw) {
265
+ this.isHighlightingCells = true;
266
+
267
+ if (document.body) {
268
+ document.body.appendChild(removeHighlightStyle);
269
+ }
270
+ } else if (cellX === this.currentX && cellY === this.currentY) {
222
271
  return;
223
272
  }
224
273
 
225
- calcSize();
274
+ this.currentX = cellX;
275
+ this.currentY = cellY;
276
+
277
+ if (this.isHighlightingCells) {
278
+ const focusTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
279
+
280
+ if (this.gridSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
281
+ const focusNodeKey = focusTableCellNode.getKey();
282
+ this.gridSelection = lexical.$createGridSelection();
283
+ this.focusCellNodeKey = focusNodeKey;
284
+ this.gridSelection.set(this.tableNodeKey, // $FlowFixMe This is not null, as you can see in the statement above.
285
+ this.anchorCellNodeKey, this.focusCellNodeKey);
286
+ lexical.$setSelection(this.gridSelection);
287
+ $updateDOMForSelection(this.grid, this.gridSelection);
288
+ }
289
+ }
226
290
  });
227
- });
228
- observer.observe(tableElement, {
229
- childList: true,
230
- subtree: true
231
- });
232
- calcSize();
233
- return grid;
234
- }
235
- function updateCells(fromX, toX, fromY, toY, cells) {
236
- const highlighted = [];
291
+ }
237
292
 
238
- for (let y = 0; y < cells.length; y++) {
239
- const row = cells[y];
293
+ setAnchorCellForSelection(cell) {
294
+ this.editor.update(() => {
295
+ this.startX = cell.x;
296
+ this.startY = cell.y;
297
+ this.isMouseDown = true;
298
+ const anchorTableCellNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
299
+
300
+ if ($isTableCellNode(anchorTableCellNode)) {
301
+ const anchorNodeKey = anchorTableCellNode.getKey();
302
+ this.gridSelection = lexical.$createGridSelection();
303
+ this.anchorCellNodeKey = anchorNodeKey;
304
+ }
305
+ });
306
+ document.addEventListener('mouseup', () => {
307
+ this.isMouseDown = false;
308
+ }, {
309
+ capture: true,
310
+ once: true
311
+ });
312
+ }
240
313
 
241
- for (let x = 0; x < row.length; x++) {
242
- const cell = row[x];
243
- const elemStyle = cell.elem.style;
314
+ formatCells(type) {
315
+ this.editor.update(() => {
316
+ const selection = lexical.$getSelection();
244
317
 
245
- if (x >= fromX && x <= toX && y >= fromY && y <= toY) {
246
- if (!cell.highlighted) {
247
- cell.highlighted = true;
248
- elemStyle.setProperty('background-color', 'rgb(163, 187, 255)');
249
- elemStyle.setProperty('caret-color', 'transparent');
318
+ if (!lexical.$isGridSelection(selection)) {
319
+ {
320
+ throw Error(`Expected grid selection`);
250
321
  }
322
+ } // This is to make Flow play ball.
251
323
 
252
- highlighted.push(cell);
253
- } else if (cell.highlighted) {
254
- cell.highlighted = false;
255
- elemStyle.removeProperty('background-color');
256
- elemStyle.removeProperty('caret-color');
257
324
 
258
- if (!cell.elem.getAttribute('style')) {
259
- cell.elem.removeAttribute('style');
325
+ const formatSelection = lexical.$createRangeSelection();
326
+ const anchor = formatSelection.anchor;
327
+ const focus = formatSelection.focus;
328
+ selection.getNodes().forEach(cellNode => {
329
+ if (lexical.$isElementNode(cellNode) && cellNode.getTextContentSize() !== 0) {
330
+ anchor.set(cellNode.getKey(), 0, 'element');
331
+ focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
332
+ formatSelection.formatText(type);
260
333
  }
334
+ });
335
+ lexical.$setSelection(selection);
336
+ });
337
+ }
338
+
339
+ clearText() {
340
+ this.editor.update(() => {
341
+ const tableNode = lexical.$getNodeByKey(this.tableNodeKey);
342
+
343
+ if (!$isTableNode(tableNode)) {
344
+ throw new Error('Expected TableNode.');
261
345
  }
262
- }
346
+
347
+ const selection = lexical.$getSelection();
348
+
349
+ if (!lexical.$isGridSelection(selection)) {
350
+ {
351
+ throw Error(`Expected grid selection`);
352
+ }
353
+ }
354
+
355
+ const selectedNodes = selection.getNodes();
356
+
357
+ if (selectedNodes.length === this.grid.columns * this.grid.rows) {
358
+ tableNode.selectPrevious(); // Delete entire table
359
+
360
+ tableNode.remove();
361
+ this.clearHighlight();
362
+ return;
363
+ }
364
+
365
+ selectedNodes.forEach(cellNode => {
366
+ if (lexical.$isElementNode(cellNode)) {
367
+ const paragraphNode = lexical.$createParagraphNode();
368
+ const textNode = lexical.$createTextNode();
369
+ paragraphNode.append(textNode);
370
+ cellNode.append(paragraphNode);
371
+ cellNode.getChildren().forEach(child => {
372
+ if (child !== paragraphNode) {
373
+ child.remove();
374
+ }
375
+ });
376
+ }
377
+ });
378
+ $updateDOMForSelection(this.grid, null);
379
+ lexical.$setSelection(null);
380
+ });
263
381
  }
264
382
 
265
- return highlighted;
266
383
  }
267
- function $applyCustomTableHandlers(tableNode, tableElement, editor) {
384
+
385
+ /**
386
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
387
+ *
388
+ * This source code is licensed under the MIT license found in the
389
+ * LICENSE file in the root directory of this source tree.
390
+ *
391
+ *
392
+ */
393
+ const CriticalPriority = 4;
394
+ const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
395
+ function applyTableHandlers(tableNode, tableElement, editor) {
268
396
  const rootElement = editor.getRootElement();
269
397
 
270
398
  if (rootElement === null) {
271
399
  throw new Error('No root element.');
272
400
  }
273
401
 
274
- trackTableGrid(tableNode, tableElement, editor);
275
- const grid = tableNode.getGrid();
276
- let isSelected = false;
277
- let isHighlightingCells = false;
278
- let startX = -1;
279
- let startY = -1;
280
- let currentX = -1;
281
- let currentY = -1;
282
- let highlightedCells = [];
283
- const editorListeners = new Set();
284
- let deleteCharacterListener = null;
402
+ const tableSelection = new TableSelection(editor, tableNode.getKey());
403
+ attachTableSelectionToTableElement(tableElement, tableSelection);
404
+ tableElement.addEventListener('dblclick', event => {
405
+ // $FlowFixMe: event.target is always a Node on the DOM
406
+ const cell = getCellFromTarget(event.target);
407
+
408
+ if (cell !== null) {
409
+ event.preventDefault();
410
+ event.stopImmediatePropagation();
411
+ event.stopPropagation();
412
+ tableSelection.setAnchorCellForSelection(cell);
413
+ tableSelection.adjustFocusCellForSelection(cell, true);
414
+ tableSelection.isMouseDown = false;
415
+ }
416
+ }); // This is the anchor of the selection.
285
417
 
286
- if (grid == null) {
287
- throw new Error('Table grid not found.');
288
- }
418
+ tableElement.addEventListener('mousedown', event => {
419
+ setTimeout(() => {
420
+ // $FlowFixMe: event.target is always a Node on the DOM
421
+ const cell = getCellFromTarget(event.target);
422
+
423
+ if (cell !== null) {
424
+ tableSelection.setAnchorCellForSelection(cell);
425
+ }
426
+ }, 0);
427
+ }); // This is adjusting the focus of the selection.
289
428
 
290
429
  tableElement.addEventListener('mousemove', event => {
291
- if (isSelected) {
430
+ if (tableSelection.isMouseDown) {
292
431
  // $FlowFixMe: event.target is always a Node on the DOM
293
432
  const cell = getCellFromTarget(event.target);
294
433
 
@@ -296,280 +435,306 @@ function $applyCustomTableHandlers(tableNode, tableElement, editor) {
296
435
  const cellX = cell.x;
297
436
  const cellY = cell.y;
298
437
 
299
- if (!isHighlightingCells && (startX !== cellX || startY !== cellY)) {
438
+ if (tableSelection.isMouseDown && (tableSelection.startX !== cellX || tableSelection.startY !== cellY || tableSelection.isHighlightingCells)) {
300
439
  event.preventDefault();
301
- const windowSelection = window.getSelection(); // Collapse the selection
302
-
303
- windowSelection.setBaseAndExtent(windowSelection.anchorNode, 0, windowSelection.anchorNode, 0);
304
- isHighlightingCells = true;
305
-
306
- if (document.body) {
307
- document.body.appendChild(removeHighlightStyle);
308
- }
309
-
310
- if (deleteCharacterListener === null) {
311
- deleteCharacterListener = editor.addListener('command', (type, payload) => {
312
- if (type === 'deleteCharacter') {
313
- if (highlightedCells.length === grid.columns * grid.rows) {
314
- tableNode.selectPrevious(); // Delete entire table
315
-
316
- tableNode.remove();
317
- clearHighlight();
318
- return true;
319
- }
320
-
321
- highlightedCells.forEach(({
322
- elem
323
- }) => {
324
- const cellNode = lexical.$getNearestNodeFromDOMNode(elem);
325
-
326
- if (lexical.$isElementNode(cellNode)) {
327
- const paragraphNode = lexical.$createParagraphNode();
328
- const textNode = lexical.$createTextNode();
329
- paragraphNode.append(textNode);
330
- cellNode.append(paragraphNode);
331
- cellNode.getChildren().forEach(child => {
332
- if (child !== paragraphNode) {
333
- child.remove();
334
- }
335
- });
336
- }
337
- });
338
- tableNode.setSelectionState(null);
339
- lexical.$setSelection(null);
340
- return true;
341
- } else if (type === 'formatText') {
342
- formatCells(payload);
343
- return true;
344
- } else if (type === 'insertText') {
345
- clearHighlight();
346
- return false;
347
- }
348
-
349
- return false;
350
- }, LowPriority);
351
- editorListeners.add(deleteCharacterListener);
352
- }
353
- } else if (cellX === currentX && cellY === currentY) {
354
- return;
355
- }
356
-
357
- currentX = cellX;
358
- currentY = cellY;
359
-
360
- if (isHighlightingCells) {
361
- const fromX = Math.min(startX, currentX);
362
- const toX = Math.max(startX, currentX);
363
- const fromY = Math.min(startY, currentY);
364
- const toY = Math.max(startY, currentY);
365
- editor.update(() => {
366
- highlightedCells = tableNode.setSelectionState({
367
- fromX,
368
- fromY,
369
- toX,
370
- toY
371
- });
372
- });
440
+ tableSelection.adjustFocusCellForSelection(cell);
373
441
  }
374
442
  }
375
443
  }
376
444
  });
445
+ tableElement.addEventListener('mouseup', event => {
446
+ if (tableSelection.isMouseDown) {
447
+ tableSelection.isMouseDown = false;
448
+ }
449
+ }); // Select entire table at this point, when grid selection is ready.
377
450
 
378
- const clearHighlight = () => {
379
- editor.update(() => {
380
- isHighlightingCells = false;
381
- isSelected = false;
382
- startX = -1;
383
- startY = -1;
384
- currentX = -1;
385
- currentY = -1;
386
- editor.update(() => {
387
- tableNode.setSelectionState(null);
388
- });
389
- highlightedCells = [];
390
-
391
- if (deleteCharacterListener !== null) {
392
- deleteCharacterListener();
393
- deleteCharacterListener = null;
394
- editorListeners.delete(deleteCharacterListener);
395
- }
451
+ tableElement.addEventListener('mouseleave', event => {
452
+ if (tableSelection.isMouseDown) {
453
+ return;
454
+ }
455
+ }); // Clear selection when clicking outside of dom.
396
456
 
397
- const parent = removeHighlightStyle.parentNode;
457
+ const mouseDownCallback = e => {
458
+ editor.update(() => {
459
+ const selection = lexical.$getSelection();
398
460
 
399
- if (parent != null) {
400
- parent.removeChild(removeHighlightStyle);
461
+ if (lexical.$isGridSelection(selection) && selection.gridKey === tableSelection.tableNodeKey && rootElement.contains(e.target)) {
462
+ return tableSelection.clearHighlight();
401
463
  }
402
464
  });
403
465
  };
404
466
 
405
- tableElement.addEventListener('mouseleave', event => {
406
- if (isSelected) {
407
- return;
408
- }
409
- });
467
+ window.addEventListener('mousedown', mouseDownCallback);
468
+ tableSelection.listenersToRemove.add(() => window.removeEventListener(mouseDownCallback));
469
+ tableSelection.listenersToRemove.add(editor.addListener('command', (type, payload) => {
470
+ const selection = lexical.$getSelection();
410
471
 
411
- const formatCells = type => {
412
- let selection = lexical.$getSelection();
472
+ if (lexical.$isGridSelection(selection)) {
473
+ if (type === 'deleteCharacter' || type === 'keyBackspace') {
474
+ const event = payload;
475
+ event.preventDefault();
476
+ event.stopPropagation();
477
+ tableSelection.clearText();
478
+ return true;
479
+ } else if (type === 'formatText') {
480
+ tableSelection.formatCells(payload);
481
+ return true;
482
+ } else if (type === 'insertText') {
483
+ tableSelection.clearHighlight();
484
+ return false;
485
+ }
486
+ } else if (lexical.$isRangeSelection(selection)) {
487
+ const tableCellNode = utils.$findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
413
488
 
414
- if (!lexical.$isRangeSelection(selection)) {
415
- selection = lexical.$createRangeSelection();
416
- } // This is to make Flow play ball.
489
+ if (!$isTableCellNode(tableCellNode)) {
490
+ return false;
491
+ }
417
492
 
493
+ if (type === 'deleteCharacter') {
494
+ if (selection.isCollapsed() && selection.anchor.offset === 0 && selection.anchor.getNode().getPreviousSiblings().length === 0) {
495
+ return true;
496
+ }
497
+ }
418
498
 
419
- const formatSelection = selection;
420
- const anchor = formatSelection.anchor;
421
- const focus = formatSelection.focus;
422
- highlightedCells.forEach(highlightedCell => {
423
- const cellNode = lexical.$getNearestNodeFromDOMNode(highlightedCell.elem);
499
+ if (type === 'keyTab') {
500
+ const event = payload;
424
501
 
425
- if (lexical.$isElementNode(cellNode) && cellNode.getTextContentSize() !== 0) {
426
- anchor.set(cellNode.getKey(), 0, 'element');
427
- focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
428
- formatSelection.formatText(type);
502
+ if (selection.isCollapsed()) {
503
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
504
+ event.preventDefault();
505
+ selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, !event.shiftKey && type === 'keyTab' ? 'forward' : 'backward');
506
+ return true;
507
+ }
429
508
  }
430
- }); // Collapse selection
431
509
 
432
- selection.anchor.set(selection.anchor.key, selection.anchor.offset, selection.anchor.type);
433
- selection.focus.set(selection.anchor.key, selection.anchor.offset, selection.anchor.type);
434
- lexical.$setSelection(selection);
435
- };
510
+ if (type === 'keyArrowDown' || type === 'keyArrowUp' || type === 'keyArrowLeft' || type === 'keyArrowRight') {
511
+ const event = payload;
436
512
 
437
- tableElement.addEventListener('mousedown', event => {
438
- if (isSelected) {
439
- if (isHighlightingCells) {
440
- clearHighlight();
441
- }
513
+ if (selection.isCollapsed()) {
514
+ const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableSelection.grid);
515
+ const elementParentNode = utils.$findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
442
516
 
443
- return;
517
+ if (elementParentNode == null) {
518
+ throw new Error('Expected BlockNode Parent');
519
+ }
520
+
521
+ const firstChild = tableCellNode.getFirstChild();
522
+ const lastChild = tableCellNode.getLastChild();
523
+ const isSelectionInFirstBlock = firstChild && elementParentNode.isParentOf(firstChild) || elementParentNode === firstChild;
524
+ const isSelectionInLastBlock = lastChild && elementParentNode.isParentOf(lastChild) || elementParentNode === lastChild;
525
+
526
+ if (type === 'keyArrowUp' && isSelectionInFirstBlock || type === 'keyArrowDown' && isSelectionInLastBlock) {
527
+ event.preventDefault();
528
+ event.stopImmediatePropagation();
529
+ event.stopPropagation();
530
+ selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, type === 'keyArrowUp' ? 'up' : 'down');
531
+ return true;
532
+ }
533
+
534
+ if (type === 'keyArrowLeft' && selection.anchor.offset === 0 || type === 'keyArrowRight' && selection.anchor.offset === selection.anchor.getNode().getTextContentSize()) {
535
+ event.preventDefault();
536
+ event.stopImmediatePropagation();
537
+ event.stopPropagation();
538
+ selectGridNodeInDirection(tableSelection, tableNode, currentCords.x, currentCords.y, type === 'keyArrowLeft' ? 'backward' : 'forward');
539
+ return true;
540
+ }
541
+ }
542
+ }
444
543
  }
445
544
 
446
- setTimeout(() => {
447
- if (isHighlightingCells) {
448
- clearHighlight();
449
- } // $FlowFixMe: event.target is always a Node on the DOM
545
+ return false;
546
+ }, CriticalPriority));
547
+ return tableSelection;
548
+ }
549
+ function attachTableSelectionToTableElement(tableElement, tableSelection) {
550
+ // $FlowFixMe
551
+ tableElement[LEXICAL_ELEMENT_KEY] = tableSelection;
552
+ }
553
+ function getTableSelectionFromTableElement(tableElement) {
554
+ // $FlowFixMe
555
+ return tableElement[LEXICAL_ELEMENT_KEY];
556
+ }
557
+ function getCellFromTarget(node) {
558
+ let currentNode = node;
450
559
 
560
+ while (currentNode != null) {
561
+ const nodeName = currentNode.nodeName;
451
562
 
452
- const cell = getCellFromTarget(event.target);
563
+ if (nodeName === 'TD' || nodeName === 'TH') {
564
+ // $FlowFixMe: internal field
565
+ const cell = currentNode._cell;
453
566
 
454
- if (cell !== null) {
455
- isSelected = true;
456
- startX = cell.x;
457
- startY = cell.y;
458
- document.addEventListener('mouseup', () => {
459
- isSelected = false;
460
- }, {
461
- capture: true,
462
- once: true
463
- });
567
+ if (cell === undefined) {
568
+ return null;
464
569
  }
465
- }, 0);
466
- });
467
- window.addEventListener('click', e => {
468
- if (highlightedCells.length > 0 && !tableElement.contains(e.target) && rootElement.contains(e.target)) {
469
- editor.update(() => {
470
- tableNode.setSelectionState(null);
471
- });
570
+
571
+ return cell;
472
572
  }
473
- });
474
573
 
475
- const selectGridNodeInDirection = (x, y, direction) => {
476
- switch (direction) {
477
- case 'backward':
478
- case 'forward':
479
- {
480
- const isForward = direction === 'forward';
574
+ currentNode = currentNode.parentNode;
575
+ }
481
576
 
482
- if (x !== (isForward ? grid.columns - 1 : 0)) {
483
- tableNode.getCellNodeFromCords(x + (isForward ? 1 : -1), y).select();
484
- } else {
485
- if (y !== (isForward ? grid.rows - 1 : 0)) {
486
- tableNode.getCellNodeFromCords(isForward ? 0 : grid.columns - 1, y + (isForward ? 1 : -1)).select();
487
- } else if (!isForward) {
488
- tableNode.selectPrevious();
489
- } else {
490
- tableNode.selectNext();
491
- }
492
- }
577
+ return null;
578
+ }
579
+ function getTableGrid(tableElement) {
580
+ const cells = [];
581
+ const grid = {
582
+ cells,
583
+ columns: 0,
584
+ rows: 0
585
+ };
586
+ let currentNode = tableElement.firstChild;
587
+ let x = 0;
588
+ let y = 0;
589
+ cells.length = 0;
493
590
 
494
- return true;
495
- }
591
+ while (currentNode != null) {
592
+ const nodeMame = currentNode.nodeName;
593
+
594
+ if (nodeMame === 'TD' || nodeMame === 'TH') {
595
+ // $FlowFixMe: TD is always an HTMLElement
596
+ const elem = currentNode;
597
+ const cell = {
598
+ elem,
599
+ highlighted: false,
600
+ x,
601
+ y
602
+ }; // $FlowFixMe: internal field
603
+
604
+ currentNode._cell = cell;
605
+
606
+ if (cells[y] === undefined) {
607
+ cells[y] = [];
608
+ }
496
609
 
497
- case 'up':
498
- {
499
- if (y !== 0) {
500
- tableNode.getCellNodeFromCords(x, y - 1).select();
501
- } else {
502
- tableNode.selectPrevious();
503
- }
610
+ cells[y][x] = cell;
611
+ } else {
612
+ const child = currentNode.firstChild;
504
613
 
505
- return true;
506
- }
614
+ if (child != null) {
615
+ currentNode = child;
616
+ continue;
617
+ }
618
+ }
507
619
 
508
- case 'down':
509
- {
510
- if (y !== grid.rows - 1) {
511
- tableNode.getCellNodeFromCords(x, y + 1).select();
512
- } else {
513
- tableNode.selectNext();
514
- }
620
+ const sibling = currentNode.nextSibling;
515
621
 
516
- return true;
517
- }
622
+ if (sibling != null) {
623
+ x++;
624
+ currentNode = sibling;
625
+ continue;
518
626
  }
519
627
 
520
- return false;
521
- };
628
+ const parent = currentNode.parentNode;
522
629
 
523
- const genericCommandListener = editor.addListener('command', (type, payload) => {
524
- const selection = lexical.$getSelection();
630
+ if (parent != null) {
631
+ const parentSibling = parent.nextSibling;
632
+
633
+ if (parentSibling == null) {
634
+ break;
635
+ }
525
636
 
526
- if (!lexical.$isRangeSelection(selection)) {
527
- return false;
637
+ y++;
638
+ x = 0;
639
+ currentNode = parentSibling;
528
640
  }
641
+ }
529
642
 
530
- const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
643
+ grid.columns = x + 1;
644
+ grid.rows = y + 1;
645
+ return grid;
646
+ }
647
+ function $updateDOMForSelection(grid, gridSelection) {
648
+ const highlightedCells = [];
649
+ const {
650
+ cells
651
+ } = grid;
652
+ const selectedCellNodes = new Set(gridSelection ? gridSelection.getNodes() : []);
531
653
 
532
- if (!$isTableCellNode(tableCellNode)) {
533
- return false;
534
- }
654
+ for (let y = 0; y < cells.length; y++) {
655
+ const row = cells[y];
535
656
 
536
- if (type === 'deleteCharacter') {
537
- if (highlightedCells.length === 0 && selection.isCollapsed() && selection.anchor.offset === 0 && selection.anchor.getNode().getPreviousSiblings().length === 0) {
538
- return true;
657
+ for (let x = 0; x < row.length; x++) {
658
+ const cell = row[x];
659
+ const elemStyle = cell.elem.style;
660
+ const lexicalNode = lexical.$getNearestNodeFromDOMNode(cell.elem);
661
+
662
+ if (lexicalNode && selectedCellNodes.has(lexicalNode)) {
663
+ cell.highlighted = true;
664
+ elemStyle.setProperty('background-color', 'rgb(163, 187, 255)');
665
+ elemStyle.setProperty('caret-color', 'transparent');
666
+ highlightedCells.push(cell);
667
+ } else {
668
+ cell.highlighted = false;
669
+ elemStyle.removeProperty('background-color');
670
+ elemStyle.removeProperty('caret-color');
671
+
672
+ if (!cell.elem.getAttribute('style')) {
673
+ cell.elem.removeAttribute('style');
674
+ }
539
675
  }
540
676
  }
677
+ }
541
678
 
542
- if (type === 'keyTab') {
543
- const event = payload;
679
+ return highlightedCells;
680
+ }
681
+
682
+ const selectGridNodeInDirection = (tableSelection, tableNode, x, y, direction) => {
683
+ switch (direction) {
684
+ case 'backward':
685
+ case 'forward':
686
+ {
687
+ const isForward = direction === 'forward';
688
+
689
+ if (x !== (isForward ? tableSelection.grid.columns - 1 : 0)) {
690
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableSelection.grid));
691
+ } else {
692
+ if (y !== (isForward ? tableSelection.grid.rows - 1 : 0)) {
693
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : tableSelection.grid.columns - 1, y + (isForward ? 1 : -1), tableSelection.grid));
694
+ } else if (!isForward) {
695
+ tableNode.selectPrevious();
696
+ } else {
697
+ tableNode.selectNext();
698
+ }
699
+ }
544
700
 
545
- if (selection.isCollapsed() && highlightedCells.length === 0) {
546
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode);
547
- event.preventDefault();
548
- selectGridNodeInDirection(currentCords.x, currentCords.y, !event.shiftKey && type === 'keyTab' ? 'forward' : 'backward');
549
701
  return true;
550
702
  }
551
- }
552
703
 
553
- if (type === 'keyArrowDown' || type === 'keyArrowUp') {
554
- const event = payload;
704
+ case 'up':
705
+ {
706
+ if (y !== 0) {
707
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableSelection.grid));
708
+ } else {
709
+ tableNode.selectPrevious();
710
+ }
555
711
 
556
- if (selection.isCollapsed() && highlightedCells.length === 0) {
557
- const currentCords = tableNode.getCordsFromCellNode(tableCellNode);
558
- const elementParentNode = $findMatchingParent(selection.anchor.getNode(), n => lexical.$isElementNode(n));
712
+ return true;
713
+ }
559
714
 
560
- if (type === 'keyArrowUp' && elementParentNode === tableCellNode.getFirstChild() || type === 'keyArrowDown' && elementParentNode === tableCellNode.getLastChild()) {
561
- event.preventDefault();
562
- event.stopImmediatePropagation();
563
- selectGridNodeInDirection(currentCords.x, currentCords.y, type === 'keyArrowUp' ? 'up' : 'down');
564
- return true;
715
+ case 'down':
716
+ {
717
+ if (y !== tableSelection.grid.rows - 1) {
718
+ selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableSelection.grid));
719
+ } else {
720
+ tableNode.selectNext();
565
721
  }
722
+
723
+ return true;
566
724
  }
567
- }
725
+ }
568
726
 
569
- return false;
570
- }, CriticalPriority);
571
- editorListeners.add(genericCommandListener);
572
- return () => Array.from(editorListeners).forEach(removeListener => removeListener ? removeListener() : null);
727
+ return false;
728
+ };
729
+
730
+ function selectTableCellNode(tableCell) {
731
+ const possibleParagraph = tableCell.getChildren().find(n => lexical.$isParagraphNode(n));
732
+
733
+ if (lexical.$isParagraphNode(possibleParagraph)) {
734
+ possibleParagraph.selectEnd();
735
+ } else {
736
+ tableCell.selectEnd();
737
+ }
573
738
  }
574
739
 
575
740
  /**
@@ -585,21 +750,17 @@ class TableNode extends lexical.GridNode {
585
750
  return 'table';
586
751
  }
587
752
 
588
- static clone(node, selectionShape, grid) {
589
- // TODO: selectionShape and grid aren't being deeply cloned?
590
- // They shouldn't really be on the table node IMO.
591
- return new TableNode(node.__key, node.__selectionShape, node.__grid);
753
+ static clone(node) {
754
+ return new TableNode(node.__key);
592
755
  }
593
756
 
594
- constructor(key, selectionShape, grid) {
757
+ constructor(key) {
595
758
  super(key);
596
- this.__selectionShape = selectionShape;
597
- this.__grid = grid;
598
759
  }
599
760
 
600
761
  createDOM(config, editor) {
601
762
  const element = document.createElement('table');
602
- addClassNamesToElement(element, config.theme.table);
763
+ utils.addClassNamesToElement(element, config.theme.table);
603
764
  return element;
604
765
  }
605
766
 
@@ -615,26 +776,7 @@ class TableNode extends lexical.GridNode {
615
776
  return false;
616
777
  }
617
778
 
618
- setSelectionState(selectionShape) {
619
- const self = this.getWritable();
620
- self.__selectionShape = selectionShape;
621
- const grid = this.getGrid();
622
- if (grid == null) return [];
623
-
624
- if (!selectionShape) {
625
- return updateCells(-1, -1, -1, -1, grid.cells);
626
- }
627
-
628
- return updateCells(selectionShape.fromX, selectionShape.toX, selectionShape.fromY, selectionShape.toY, grid.cells);
629
- }
630
-
631
- getSelectionState() {
632
- return this.getLatest().__selectionShape;
633
- }
634
-
635
- getCordsFromCellNode(tableCellNode) {
636
- const grid = this.getGrid();
637
-
779
+ getCordsFromCellNode(tableCellNode, grid) {
638
780
  if (!grid) {
639
781
  throw Error(`Grid not found.`);
640
782
  }
@@ -669,9 +811,7 @@ class TableNode extends lexical.GridNode {
669
811
  throw new Error('Cell not found in table.');
670
812
  }
671
813
 
672
- getCellNodeFromCords(x, y) {
673
- const grid = this.getGrid();
674
-
814
+ getCellNodeFromCords(x, y, grid) {
675
815
  if (!grid) {
676
816
  throw Error(`Grid not found.`);
677
817
  }
@@ -682,13 +822,13 @@ class TableNode extends lexical.GridNode {
682
822
  const row = cells[y];
683
823
 
684
824
  if (row == null) {
685
- throw new Error(`Table row x:"${y}" not found.`);
825
+ return null;
686
826
  }
687
827
 
688
828
  const cell = row[x];
689
829
 
690
830
  if (cell == null) {
691
- throw new Error(`Table cell y:"${x}" in row x:"${y}" not found.`);
831
+ return null;
692
832
  }
693
833
 
694
834
  const node = lexical.$getNearestNodeFromDOMNode(cell.elem);
@@ -697,17 +837,17 @@ class TableNode extends lexical.GridNode {
697
837
  return node;
698
838
  }
699
839
 
700
- throw new Error('Node at cords not TableCellNode.');
840
+ return null;
701
841
  }
702
842
 
703
- setGrid(grid) {
704
- const self = this.getWritable();
705
- self.__grid = grid;
706
- return self;
707
- }
843
+ getCellNodeFromCordsOrThrow(x, y, grid) {
844
+ const node = this.getCellNodeFromCords(x, y, grid);
708
845
 
709
- getGrid() {
710
- return this.getLatest().__grid;
846
+ if (!node) {
847
+ throw new Error('Node at cords not TableCellNode.');
848
+ }
849
+
850
+ return node;
711
851
  }
712
852
 
713
853
  canSelectBefore() {
@@ -715,6 +855,15 @@ class TableNode extends lexical.GridNode {
715
855
  }
716
856
 
717
857
  }
858
+ function $getElementGridForTableNode(editor, tableNode) {
859
+ const tableElement = editor.getElementByKey(tableNode.getKey());
860
+
861
+ if (tableElement == null) {
862
+ throw new Error('Table Element Not Found');
863
+ }
864
+
865
+ return getTableGrid(tableElement);
866
+ }
718
867
  function $createTableNode() {
719
868
  return new TableNode();
720
869
  }
@@ -736,21 +885,37 @@ class TableRowNode extends lexical.GridRowNode {
736
885
  }
737
886
 
738
887
  static clone(node) {
739
- return new TableRowNode(node.__key);
888
+ return new TableRowNode(node.__height, node.__key);
740
889
  }
741
890
 
742
- constructor(key) {
891
+ constructor(height, key) {
743
892
  super(key);
893
+ this.__height = height;
744
894
  }
745
895
 
746
896
  createDOM(config) {
747
897
  const element = document.createElement('tr');
748
- addClassNamesToElement(element, config.theme.tableRow);
898
+
899
+ if (this.__height) {
900
+ element.style.height = `${this.__height}px`;
901
+ }
902
+
903
+ utils.addClassNamesToElement(element, config.theme.tableRow);
749
904
  return element;
750
905
  }
751
906
 
752
- updateDOM() {
753
- return false;
907
+ setHeight(height) {
908
+ const self = this.getWritable();
909
+ self.__height = height;
910
+ return this.__height;
911
+ }
912
+
913
+ getHeight() {
914
+ return this.getLatest().__height;
915
+ }
916
+
917
+ updateDOM(prevNode) {
918
+ return prevNode.__height !== this.__height;
754
919
  }
755
920
 
756
921
  canBeEmpty() {
@@ -758,8 +923,8 @@ class TableRowNode extends lexical.GridRowNode {
758
923
  }
759
924
 
760
925
  }
761
- function $createTableRowNode() {
762
- return new TableRowNode();
926
+ function $createTableRowNode(height) {
927
+ return new TableRowNode(height);
763
928
  }
764
929
  function $isTableRowNode(node) {
765
930
  return node instanceof TableRowNode;
@@ -773,14 +938,21 @@ function $isTableRowNode(node) {
773
938
  *
774
939
  *
775
940
  */
776
- function $createTableNodeWithDimensions(rowCount, columnCount, includeHeader = true) {
941
+ function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders = true) {
777
942
  const tableNode = $createTableNode();
778
943
 
779
944
  for (let iRow = 0; iRow < rowCount; iRow++) {
780
945
  const tableRowNode = $createTableRowNode();
781
946
 
782
947
  for (let iColumn = 0; iColumn < columnCount; iColumn++) {
783
- const tableCellNode = $createTableCellNode(iRow === 0 && includeHeader);
948
+ let headerState = TableCellHeaderStates.NO_STATUS;
949
+
950
+ if (includeHeaders) {
951
+ if (iRow === 0) headerState |= TableCellHeaderStates.ROW;
952
+ if (iColumn === 0) headerState |= TableCellHeaderStates.COLUMN;
953
+ }
954
+
955
+ const tableCellNode = $createTableCellNode(headerState);
784
956
  const paragraphNode = lexical.$createParagraphNode();
785
957
  paragraphNode.append(lexical.$createTextNode());
786
958
  tableCellNode.append(paragraphNode);
@@ -793,7 +965,7 @@ function $createTableNodeWithDimensions(rowCount, columnCount, includeHeader = t
793
965
  return tableNode;
794
966
  }
795
967
  function $getTableCellNodeFromLexicalNode(startingNode) {
796
- const node = $findMatchingParent(startingNode, n => $isTableCellNode(n));
968
+ const node = utils.$findMatchingParent(startingNode, n => $isTableCellNode(n));
797
969
 
798
970
  if ($isTableCellNode(node)) {
799
971
  return node;
@@ -802,7 +974,7 @@ function $getTableCellNodeFromLexicalNode(startingNode) {
802
974
  return null;
803
975
  }
804
976
  function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
805
- const node = $findMatchingParent(startingNode, n => $isTableRowNode(n));
977
+ const node = utils.$findMatchingParent(startingNode, n => $isTableRowNode(n));
806
978
 
807
979
  if ($isTableRowNode(node)) {
808
980
  return node;
@@ -811,7 +983,7 @@ function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
811
983
  throw new Error('Expected table cell to be inside of table row.');
812
984
  }
813
985
  function $getTableNodeFromLexicalNodeOrThrow(startingNode) {
814
- const node = $findMatchingParent(startingNode, n => $isTableNode(n));
986
+ const node = utils.$findMatchingParent(startingNode, n => $isTableNode(n));
815
987
 
816
988
  if ($isTableNode(node)) {
817
989
  return node;
@@ -828,6 +1000,19 @@ function $getTableColumnIndexFromTableCellNode(tableCellNode) {
828
1000
  const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
829
1001
  return tableRowNode.getChildren().findIndex(n => n.is(tableCellNode));
830
1002
  }
1003
+ function $getTableCellSiblingsFromTableCellNode(tableCellNode, grid) {
1004
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
1005
+ const {
1006
+ x,
1007
+ y
1008
+ } = tableNode.getCordsFromCellNode(tableCellNode, grid);
1009
+ return {
1010
+ above: tableNode.getCellNodeFromCords(x, y - 1, grid),
1011
+ below: tableNode.getCellNodeFromCords(x, y + 1, grid),
1012
+ left: tableNode.getCellNodeFromCords(x - 1, y, grid),
1013
+ right: tableNode.getCellNodeFromCords(x + 1, y, grid)
1014
+ };
1015
+ }
831
1016
  function $removeTableRowAtIndex(tableNode, indexToDelete) {
832
1017
  const tableRows = tableNode.getChildren();
833
1018
 
@@ -839,7 +1024,7 @@ function $removeTableRowAtIndex(tableNode, indexToDelete) {
839
1024
  targetRowNode.remove();
840
1025
  return tableNode;
841
1026
  }
842
- function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount) {
1027
+ function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, grid) {
843
1028
  const tableRows = tableNode.getChildren();
844
1029
 
845
1030
  if (targetIndex >= tableRows.length || targetIndex < 0) {
@@ -849,12 +1034,30 @@ function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCo
849
1034
  const targetRowNode = tableRows[targetIndex];
850
1035
 
851
1036
  if ($isTableRowNode(targetRowNode)) {
852
- for (let i = 0; i < rowCount; i++) {
853
- const tableColumnCount = targetRowNode.getChildren().length;
1037
+ for (let r = 0; r < rowCount; r++) {
1038
+ const tableRowCells = targetRowNode.getChildren();
1039
+ const tableColumnCount = tableRowCells.length;
854
1040
  const newTableRowNode = $createTableRowNode();
855
1041
 
856
- for (let j = 0; j < tableColumnCount; j++) {
857
- const tableCellNode = $createTableCellNode(false);
1042
+ for (let c = 0; c < tableColumnCount; c++) {
1043
+ const tableCellFromTargetRow = tableRowCells[c];
1044
+
1045
+ if (!$isTableCellNode(tableCellFromTargetRow)) {
1046
+ throw Error(`Expected table cell`);
1047
+ }
1048
+
1049
+ const {
1050
+ above,
1051
+ below
1052
+ } = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow, grid);
1053
+ let headerState = TableCellHeaderStates.NO_STATUS;
1054
+ const width = above && above.getWidth() || below && below.getWidth() || null;
1055
+
1056
+ if (above && above.hasHeaderState(TableCellHeaderStates.COLUMN) || below && below.hasHeaderState(TableCellHeaderStates.COLUMN)) {
1057
+ headerState |= TableCellHeaderStates.COLUMN;
1058
+ }
1059
+
1060
+ const tableCellNode = $createTableCellNode(headerState, 1, width);
858
1061
  tableCellNode.append(lexical.$createParagraphNode());
859
1062
  newTableRowNode.append(tableCellNode);
860
1063
  }
@@ -874,12 +1077,18 @@ function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCo
874
1077
  function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, columnCount) {
875
1078
  const tableRows = tableNode.getChildren();
876
1079
 
877
- for (let i = 0; i < tableRows.length; i++) {
878
- const currentTableRowNode = tableRows[i];
1080
+ for (let r = 0; r < tableRows.length; r++) {
1081
+ const currentTableRowNode = tableRows[r];
879
1082
 
880
1083
  if ($isTableRowNode(currentTableRowNode)) {
881
- for (let j = 0; j < columnCount; j++) {
882
- const newTableCell = $createTableCellNode(i === 0);
1084
+ for (let c = 0; c < columnCount; c++) {
1085
+ let headerState = TableCellHeaderStates.NO_STATUS;
1086
+
1087
+ if (r === 0) {
1088
+ headerState |= TableCellHeaderStates.ROW;
1089
+ }
1090
+
1091
+ const newTableCell = $createTableCellNode(headerState);
883
1092
  newTableCell.append(lexical.$createParagraphNode());
884
1093
  const tableRowChildren = currentTableRowNode.getChildren();
885
1094
 
@@ -920,12 +1129,12 @@ function $deleteTableColumn(tableNode, targetIndex) {
920
1129
  return tableNode;
921
1130
  }
922
1131
 
923
- exports.$applyCustomTableHandlers = $applyCustomTableHandlers;
924
1132
  exports.$createTableCellNode = $createTableCellNode;
925
1133
  exports.$createTableNode = $createTableNode;
926
1134
  exports.$createTableNodeWithDimensions = $createTableNodeWithDimensions;
927
1135
  exports.$createTableRowNode = $createTableRowNode;
928
1136
  exports.$deleteTableColumn = $deleteTableColumn;
1137
+ exports.$getElementGridForTableNode = $getElementGridForTableNode;
929
1138
  exports.$getTableCellNodeFromLexicalNode = $getTableCellNodeFromLexicalNode;
930
1139
  exports.$getTableColumnIndexFromTableCellNode = $getTableColumnIndexFromTableCellNode;
931
1140
  exports.$getTableNodeFromLexicalNodeOrThrow = $getTableNodeFromLexicalNodeOrThrow;
@@ -937,6 +1146,11 @@ exports.$isTableCellNode = $isTableCellNode;
937
1146
  exports.$isTableNode = $isTableNode;
938
1147
  exports.$isTableRowNode = $isTableRowNode;
939
1148
  exports.$removeTableRowAtIndex = $removeTableRowAtIndex;
1149
+ exports.TableCellHeaderStates = TableCellHeaderStates;
940
1150
  exports.TableCellNode = TableCellNode;
941
1151
  exports.TableNode = TableNode;
942
1152
  exports.TableRowNode = TableRowNode;
1153
+ exports.TableSelection = TableSelection;
1154
+ exports.applyTableHandlers = applyTableHandlers;
1155
+ exports.getCellFromTarget = getCellFromTarget;
1156
+ exports.getTableSelectionFromTableElement = getTableSelectionFromTableElement;