@lexical/table 0.1.13 → 0.1.16

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