@lexical/table 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LexicalTable.dev.esm.js +2433 -0
- package/LexicalTable.dev.js +56 -12
- package/LexicalTable.esm.js +45 -0
- package/LexicalTable.js +1 -1
- package/LexicalTable.js.flow +1 -1
- package/LexicalTable.prod.esm.js +7 -0
- package/LexicalTable.prod.js +71 -70
- package/LexicalTableNode.d.ts +0 -1
- package/LexicalTableRowNode.d.ts +3 -3
- package/README.md +2 -0
- package/package.json +6 -4
@@ -0,0 +1,2433 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
3
|
+
*
|
4
|
+
* This source code is licensed under the MIT license found in the
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
6
|
+
*/
|
7
|
+
import { addClassNamesToElement, $findMatchingParent, removeClassNamesFromElement, isHTMLElement } from '@lexical/utils';
|
8
|
+
import { ElementNode, $applyNodeReplacement, $createParagraphNode, $isElementNode, $isLineBreakNode, createCommand, $createTextNode, $getSelection, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $getNodeByKey, isCurrentlyReadOnlyMode, $createPoint, $setSelection, SELECTION_CHANGE_COMMAND, $getNearestNodeFromDOMNode, $createRangeSelection, $getRoot, KEY_ARROW_DOWN_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ARROW_UP_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ESCAPE_COMMAND, DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_TAB_COMMAND, FOCUS_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, $isTextNode, $getPreviousSelection } from 'lexical';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
12
|
+
*
|
13
|
+
* This source code is licensed under the MIT license found in the
|
14
|
+
* LICENSE file in the root directory of this source tree.
|
15
|
+
*
|
16
|
+
*/
|
17
|
+
|
18
|
+
const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/;
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
22
|
+
*
|
23
|
+
* This source code is licensed under the MIT license found in the
|
24
|
+
* LICENSE file in the root directory of this source tree.
|
25
|
+
*
|
26
|
+
*/
|
27
|
+
const TableCellHeaderStates = {
|
28
|
+
BOTH: 3,
|
29
|
+
COLUMN: 2,
|
30
|
+
NO_STATUS: 0,
|
31
|
+
ROW: 1
|
32
|
+
};
|
33
|
+
/** @noInheritDoc */
|
34
|
+
class TableCellNode extends ElementNode {
|
35
|
+
/** @internal */
|
36
|
+
|
37
|
+
/** @internal */
|
38
|
+
|
39
|
+
/** @internal */
|
40
|
+
|
41
|
+
/** @internal */
|
42
|
+
|
43
|
+
/** @internal */
|
44
|
+
|
45
|
+
static getType() {
|
46
|
+
return 'tablecell';
|
47
|
+
}
|
48
|
+
static clone(node) {
|
49
|
+
const cellNode = new TableCellNode(node.__headerState, node.__colSpan, node.__width, node.__key);
|
50
|
+
cellNode.__rowSpan = node.__rowSpan;
|
51
|
+
cellNode.__backgroundColor = node.__backgroundColor;
|
52
|
+
return cellNode;
|
53
|
+
}
|
54
|
+
static importDOM() {
|
55
|
+
return {
|
56
|
+
td: node => ({
|
57
|
+
conversion: convertTableCellNodeElement,
|
58
|
+
priority: 0
|
59
|
+
}),
|
60
|
+
th: node => ({
|
61
|
+
conversion: convertTableCellNodeElement,
|
62
|
+
priority: 0
|
63
|
+
})
|
64
|
+
};
|
65
|
+
}
|
66
|
+
static importJSON(serializedNode) {
|
67
|
+
const colSpan = serializedNode.colSpan || 1;
|
68
|
+
const rowSpan = serializedNode.rowSpan || 1;
|
69
|
+
const cellNode = $createTableCellNode(serializedNode.headerState, colSpan, serializedNode.width || undefined);
|
70
|
+
cellNode.__rowSpan = rowSpan;
|
71
|
+
cellNode.__backgroundColor = serializedNode.backgroundColor || null;
|
72
|
+
return cellNode;
|
73
|
+
}
|
74
|
+
constructor(headerState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width, key) {
|
75
|
+
super(key);
|
76
|
+
this.__colSpan = colSpan;
|
77
|
+
this.__rowSpan = 1;
|
78
|
+
this.__headerState = headerState;
|
79
|
+
this.__width = width;
|
80
|
+
this.__backgroundColor = null;
|
81
|
+
}
|
82
|
+
createDOM(config) {
|
83
|
+
const element = document.createElement(this.getTag());
|
84
|
+
if (this.__width) {
|
85
|
+
element.style.width = `${this.__width}px`;
|
86
|
+
}
|
87
|
+
if (this.__colSpan > 1) {
|
88
|
+
element.colSpan = this.__colSpan;
|
89
|
+
}
|
90
|
+
if (this.__rowSpan > 1) {
|
91
|
+
element.rowSpan = this.__rowSpan;
|
92
|
+
}
|
93
|
+
if (this.__backgroundColor !== null) {
|
94
|
+
element.style.backgroundColor = this.__backgroundColor;
|
95
|
+
}
|
96
|
+
addClassNamesToElement(element, config.theme.tableCell, this.hasHeader() && config.theme.tableCellHeader);
|
97
|
+
return element;
|
98
|
+
}
|
99
|
+
exportDOM(editor) {
|
100
|
+
const {
|
101
|
+
element
|
102
|
+
} = super.exportDOM(editor);
|
103
|
+
if (element) {
|
104
|
+
const element_ = element;
|
105
|
+
const maxWidth = 700;
|
106
|
+
const colCount = this.getParentOrThrow().getChildrenSize();
|
107
|
+
element_.style.border = '1px solid black';
|
108
|
+
if (this.__colSpan > 1) {
|
109
|
+
element_.colSpan = this.__colSpan;
|
110
|
+
}
|
111
|
+
if (this.__rowSpan > 1) {
|
112
|
+
element_.rowSpan = this.__rowSpan;
|
113
|
+
}
|
114
|
+
element_.style.width = `${this.getWidth() || Math.max(90, maxWidth / colCount)}px`;
|
115
|
+
element_.style.verticalAlign = 'top';
|
116
|
+
element_.style.textAlign = 'start';
|
117
|
+
const backgroundColor = this.getBackgroundColor();
|
118
|
+
if (backgroundColor !== null) {
|
119
|
+
element_.style.backgroundColor = backgroundColor;
|
120
|
+
} else if (this.hasHeader()) {
|
121
|
+
element_.style.backgroundColor = '#f2f3f5';
|
122
|
+
}
|
123
|
+
}
|
124
|
+
return {
|
125
|
+
element
|
126
|
+
};
|
127
|
+
}
|
128
|
+
exportJSON() {
|
129
|
+
return {
|
130
|
+
...super.exportJSON(),
|
131
|
+
backgroundColor: this.getBackgroundColor(),
|
132
|
+
colSpan: this.__colSpan,
|
133
|
+
headerState: this.__headerState,
|
134
|
+
rowSpan: this.__rowSpan,
|
135
|
+
type: 'tablecell',
|
136
|
+
width: this.getWidth()
|
137
|
+
};
|
138
|
+
}
|
139
|
+
getColSpan() {
|
140
|
+
return this.__colSpan;
|
141
|
+
}
|
142
|
+
setColSpan(colSpan) {
|
143
|
+
this.getWritable().__colSpan = colSpan;
|
144
|
+
return this;
|
145
|
+
}
|
146
|
+
getRowSpan() {
|
147
|
+
return this.__rowSpan;
|
148
|
+
}
|
149
|
+
setRowSpan(rowSpan) {
|
150
|
+
this.getWritable().__rowSpan = rowSpan;
|
151
|
+
return this;
|
152
|
+
}
|
153
|
+
getTag() {
|
154
|
+
return this.hasHeader() ? 'th' : 'td';
|
155
|
+
}
|
156
|
+
setHeaderStyles(headerState) {
|
157
|
+
const self = this.getWritable();
|
158
|
+
self.__headerState = headerState;
|
159
|
+
return this.__headerState;
|
160
|
+
}
|
161
|
+
getHeaderStyles() {
|
162
|
+
return this.getLatest().__headerState;
|
163
|
+
}
|
164
|
+
setWidth(width) {
|
165
|
+
const self = this.getWritable();
|
166
|
+
self.__width = width;
|
167
|
+
return this.__width;
|
168
|
+
}
|
169
|
+
getWidth() {
|
170
|
+
return this.getLatest().__width;
|
171
|
+
}
|
172
|
+
getBackgroundColor() {
|
173
|
+
return this.getLatest().__backgroundColor;
|
174
|
+
}
|
175
|
+
setBackgroundColor(newBackgroundColor) {
|
176
|
+
this.getWritable().__backgroundColor = newBackgroundColor;
|
177
|
+
}
|
178
|
+
toggleHeaderStyle(headerStateToToggle) {
|
179
|
+
const self = this.getWritable();
|
180
|
+
if ((self.__headerState & headerStateToToggle) === headerStateToToggle) {
|
181
|
+
self.__headerState -= headerStateToToggle;
|
182
|
+
} else {
|
183
|
+
self.__headerState += headerStateToToggle;
|
184
|
+
}
|
185
|
+
return self;
|
186
|
+
}
|
187
|
+
hasHeaderState(headerState) {
|
188
|
+
return (this.getHeaderStyles() & headerState) === headerState;
|
189
|
+
}
|
190
|
+
hasHeader() {
|
191
|
+
return this.getLatest().__headerState !== TableCellHeaderStates.NO_STATUS;
|
192
|
+
}
|
193
|
+
updateDOM(prevNode) {
|
194
|
+
return prevNode.__headerState !== this.__headerState || prevNode.__width !== this.__width || prevNode.__colSpan !== this.__colSpan || prevNode.__rowSpan !== this.__rowSpan || prevNode.__backgroundColor !== this.__backgroundColor;
|
195
|
+
}
|
196
|
+
isShadowRoot() {
|
197
|
+
return true;
|
198
|
+
}
|
199
|
+
collapseAtStart() {
|
200
|
+
return true;
|
201
|
+
}
|
202
|
+
canBeEmpty() {
|
203
|
+
return false;
|
204
|
+
}
|
205
|
+
canIndent() {
|
206
|
+
return false;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
function convertTableCellNodeElement(domNode) {
|
210
|
+
const domNode_ = domNode;
|
211
|
+
const nodeName = domNode.nodeName.toLowerCase();
|
212
|
+
let width = undefined;
|
213
|
+
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) {
|
214
|
+
width = parseFloat(domNode_.style.width);
|
215
|
+
}
|
216
|
+
const tableCellNode = $createTableCellNode(nodeName === 'th' ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS, domNode_.colSpan, width);
|
217
|
+
tableCellNode.__rowSpan = domNode_.rowSpan;
|
218
|
+
const backgroundColor = domNode_.style.backgroundColor;
|
219
|
+
if (backgroundColor !== '') {
|
220
|
+
tableCellNode.__backgroundColor = backgroundColor;
|
221
|
+
}
|
222
|
+
return {
|
223
|
+
after: childLexicalNodes => {
|
224
|
+
if (childLexicalNodes.length === 0) {
|
225
|
+
childLexicalNodes.push($createParagraphNode());
|
226
|
+
}
|
227
|
+
return childLexicalNodes;
|
228
|
+
},
|
229
|
+
forChild: (lexicalNode, parentLexicalNode) => {
|
230
|
+
if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) {
|
231
|
+
const paragraphNode = $createParagraphNode();
|
232
|
+
if ($isLineBreakNode(lexicalNode) && lexicalNode.getTextContent() === '\n') {
|
233
|
+
return null;
|
234
|
+
}
|
235
|
+
paragraphNode.append(lexicalNode);
|
236
|
+
return paragraphNode;
|
237
|
+
}
|
238
|
+
return lexicalNode;
|
239
|
+
},
|
240
|
+
node: tableCellNode
|
241
|
+
};
|
242
|
+
}
|
243
|
+
function $createTableCellNode(headerState, colSpan = 1, width) {
|
244
|
+
return $applyNodeReplacement(new TableCellNode(headerState, colSpan, width));
|
245
|
+
}
|
246
|
+
function $isTableCellNode(node) {
|
247
|
+
return node instanceof TableCellNode;
|
248
|
+
}
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
252
|
+
*
|
253
|
+
* This source code is licensed under the MIT license found in the
|
254
|
+
* LICENSE file in the root directory of this source tree.
|
255
|
+
*
|
256
|
+
*/
|
257
|
+
const INSERT_TABLE_COMMAND = createCommand('INSERT_TABLE_COMMAND');
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
261
|
+
*
|
262
|
+
* This source code is licensed under the MIT license found in the
|
263
|
+
* LICENSE file in the root directory of this source tree.
|
264
|
+
*
|
265
|
+
*/
|
266
|
+
/** @noInheritDoc */
|
267
|
+
class TableRowNode extends ElementNode {
|
268
|
+
/** @internal */
|
269
|
+
|
270
|
+
static getType() {
|
271
|
+
return 'tablerow';
|
272
|
+
}
|
273
|
+
static clone(node) {
|
274
|
+
return new TableRowNode(node.__height, node.__key);
|
275
|
+
}
|
276
|
+
static importDOM() {
|
277
|
+
return {
|
278
|
+
tr: node => ({
|
279
|
+
conversion: convertTableRowElement,
|
280
|
+
priority: 0
|
281
|
+
})
|
282
|
+
};
|
283
|
+
}
|
284
|
+
static importJSON(serializedNode) {
|
285
|
+
return $createTableRowNode(serializedNode.height);
|
286
|
+
}
|
287
|
+
constructor(height, key) {
|
288
|
+
super(key);
|
289
|
+
this.__height = height;
|
290
|
+
}
|
291
|
+
exportJSON() {
|
292
|
+
return {
|
293
|
+
...super.exportJSON(),
|
294
|
+
...(this.getHeight() && {
|
295
|
+
height: this.getHeight()
|
296
|
+
}),
|
297
|
+
type: 'tablerow',
|
298
|
+
version: 1
|
299
|
+
};
|
300
|
+
}
|
301
|
+
createDOM(config) {
|
302
|
+
const element = document.createElement('tr');
|
303
|
+
if (this.__height) {
|
304
|
+
element.style.height = `${this.__height}px`;
|
305
|
+
}
|
306
|
+
addClassNamesToElement(element, config.theme.tableRow);
|
307
|
+
return element;
|
308
|
+
}
|
309
|
+
isShadowRoot() {
|
310
|
+
return true;
|
311
|
+
}
|
312
|
+
setHeight(height) {
|
313
|
+
const self = this.getWritable();
|
314
|
+
self.__height = height;
|
315
|
+
return this.__height;
|
316
|
+
}
|
317
|
+
getHeight() {
|
318
|
+
return this.getLatest().__height;
|
319
|
+
}
|
320
|
+
updateDOM(prevNode) {
|
321
|
+
return prevNode.__height !== this.__height;
|
322
|
+
}
|
323
|
+
canBeEmpty() {
|
324
|
+
return false;
|
325
|
+
}
|
326
|
+
canIndent() {
|
327
|
+
return false;
|
328
|
+
}
|
329
|
+
}
|
330
|
+
function convertTableRowElement(domNode) {
|
331
|
+
const domNode_ = domNode;
|
332
|
+
let height = undefined;
|
333
|
+
if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) {
|
334
|
+
height = parseFloat(domNode_.style.height);
|
335
|
+
}
|
336
|
+
return {
|
337
|
+
node: $createTableRowNode(height)
|
338
|
+
};
|
339
|
+
}
|
340
|
+
function $createTableRowNode(height) {
|
341
|
+
return $applyNodeReplacement(new TableRowNode(height));
|
342
|
+
}
|
343
|
+
function $isTableRowNode(node) {
|
344
|
+
return node instanceof TableRowNode;
|
345
|
+
}
|
346
|
+
|
347
|
+
/**
|
348
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
349
|
+
*
|
350
|
+
* This source code is licensed under the MIT license found in the
|
351
|
+
* LICENSE file in the root directory of this source tree.
|
352
|
+
*
|
353
|
+
*/
|
354
|
+
|
355
|
+
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
356
|
+
|
357
|
+
/**
|
358
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
359
|
+
*
|
360
|
+
* This source code is licensed under the MIT license found in the
|
361
|
+
* LICENSE file in the root directory of this source tree.
|
362
|
+
*
|
363
|
+
*/
|
364
|
+
function $createTableNodeWithDimensions(rowCount, columnCount, includeHeaders = true) {
|
365
|
+
const tableNode = $createTableNode();
|
366
|
+
for (let iRow = 0; iRow < rowCount; iRow++) {
|
367
|
+
const tableRowNode = $createTableRowNode();
|
368
|
+
for (let iColumn = 0; iColumn < columnCount; iColumn++) {
|
369
|
+
let headerState = TableCellHeaderStates.NO_STATUS;
|
370
|
+
if (typeof includeHeaders === 'object') {
|
371
|
+
if (iRow === 0 && includeHeaders.rows) {
|
372
|
+
headerState |= TableCellHeaderStates.ROW;
|
373
|
+
}
|
374
|
+
if (iColumn === 0 && includeHeaders.columns) {
|
375
|
+
headerState |= TableCellHeaderStates.COLUMN;
|
376
|
+
}
|
377
|
+
} else if (includeHeaders) {
|
378
|
+
if (iRow === 0) {
|
379
|
+
headerState |= TableCellHeaderStates.ROW;
|
380
|
+
}
|
381
|
+
if (iColumn === 0) {
|
382
|
+
headerState |= TableCellHeaderStates.COLUMN;
|
383
|
+
}
|
384
|
+
}
|
385
|
+
const tableCellNode = $createTableCellNode(headerState);
|
386
|
+
const paragraphNode = $createParagraphNode();
|
387
|
+
paragraphNode.append($createTextNode());
|
388
|
+
tableCellNode.append(paragraphNode);
|
389
|
+
tableRowNode.append(tableCellNode);
|
390
|
+
}
|
391
|
+
tableNode.append(tableRowNode);
|
392
|
+
}
|
393
|
+
return tableNode;
|
394
|
+
}
|
395
|
+
function $getTableCellNodeFromLexicalNode(startingNode) {
|
396
|
+
const node = $findMatchingParent(startingNode, n => $isTableCellNode(n));
|
397
|
+
if ($isTableCellNode(node)) {
|
398
|
+
return node;
|
399
|
+
}
|
400
|
+
return null;
|
401
|
+
}
|
402
|
+
function $getTableRowNodeFromTableCellNodeOrThrow(startingNode) {
|
403
|
+
const node = $findMatchingParent(startingNode, n => $isTableRowNode(n));
|
404
|
+
if ($isTableRowNode(node)) {
|
405
|
+
return node;
|
406
|
+
}
|
407
|
+
throw new Error('Expected table cell to be inside of table row.');
|
408
|
+
}
|
409
|
+
function $getTableNodeFromLexicalNodeOrThrow(startingNode) {
|
410
|
+
const node = $findMatchingParent(startingNode, n => $isTableNode(n));
|
411
|
+
if ($isTableNode(node)) {
|
412
|
+
return node;
|
413
|
+
}
|
414
|
+
throw new Error('Expected table cell to be inside of table.');
|
415
|
+
}
|
416
|
+
function $getTableRowIndexFromTableCellNode(tableCellNode) {
|
417
|
+
const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
|
418
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableRowNode);
|
419
|
+
return tableNode.getChildren().findIndex(n => n.is(tableRowNode));
|
420
|
+
}
|
421
|
+
function $getTableColumnIndexFromTableCellNode(tableCellNode) {
|
422
|
+
const tableRowNode = $getTableRowNodeFromTableCellNodeOrThrow(tableCellNode);
|
423
|
+
return tableRowNode.getChildren().findIndex(n => n.is(tableCellNode));
|
424
|
+
}
|
425
|
+
function $getTableCellSiblingsFromTableCellNode(tableCellNode, table) {
|
426
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
427
|
+
const {
|
428
|
+
x,
|
429
|
+
y
|
430
|
+
} = tableNode.getCordsFromCellNode(tableCellNode, table);
|
431
|
+
return {
|
432
|
+
above: tableNode.getCellNodeFromCords(x, y - 1, table),
|
433
|
+
below: tableNode.getCellNodeFromCords(x, y + 1, table),
|
434
|
+
left: tableNode.getCellNodeFromCords(x - 1, y, table),
|
435
|
+
right: tableNode.getCellNodeFromCords(x + 1, y, table)
|
436
|
+
};
|
437
|
+
}
|
438
|
+
function $removeTableRowAtIndex(tableNode, indexToDelete) {
|
439
|
+
const tableRows = tableNode.getChildren();
|
440
|
+
if (indexToDelete >= tableRows.length || indexToDelete < 0) {
|
441
|
+
throw new Error('Expected table cell to be inside of table row.');
|
442
|
+
}
|
443
|
+
const targetRowNode = tableRows[indexToDelete];
|
444
|
+
targetRowNode.remove();
|
445
|
+
return tableNode;
|
446
|
+
}
|
447
|
+
function $insertTableRow(tableNode, targetIndex, shouldInsertAfter = true, rowCount, table) {
|
448
|
+
const tableRows = tableNode.getChildren();
|
449
|
+
if (targetIndex >= tableRows.length || targetIndex < 0) {
|
450
|
+
throw new Error('Table row target index out of range');
|
451
|
+
}
|
452
|
+
const targetRowNode = tableRows[targetIndex];
|
453
|
+
if ($isTableRowNode(targetRowNode)) {
|
454
|
+
for (let r = 0; r < rowCount; r++) {
|
455
|
+
const tableRowCells = targetRowNode.getChildren();
|
456
|
+
const tableColumnCount = tableRowCells.length;
|
457
|
+
const newTableRowNode = $createTableRowNode();
|
458
|
+
for (let c = 0; c < tableColumnCount; c++) {
|
459
|
+
const tableCellFromTargetRow = tableRowCells[c];
|
460
|
+
if (!$isTableCellNode(tableCellFromTargetRow)) {
|
461
|
+
throw Error(`Expected table cell`);
|
462
|
+
}
|
463
|
+
const {
|
464
|
+
above,
|
465
|
+
below
|
466
|
+
} = $getTableCellSiblingsFromTableCellNode(tableCellFromTargetRow, table);
|
467
|
+
let headerState = TableCellHeaderStates.NO_STATUS;
|
468
|
+
const width = above && above.getWidth() || below && below.getWidth() || undefined;
|
469
|
+
if (above && above.hasHeaderState(TableCellHeaderStates.COLUMN) || below && below.hasHeaderState(TableCellHeaderStates.COLUMN)) {
|
470
|
+
headerState |= TableCellHeaderStates.COLUMN;
|
471
|
+
}
|
472
|
+
const tableCellNode = $createTableCellNode(headerState, 1, width);
|
473
|
+
tableCellNode.append($createParagraphNode());
|
474
|
+
newTableRowNode.append(tableCellNode);
|
475
|
+
}
|
476
|
+
if (shouldInsertAfter) {
|
477
|
+
targetRowNode.insertAfter(newTableRowNode);
|
478
|
+
} else {
|
479
|
+
targetRowNode.insertBefore(newTableRowNode);
|
480
|
+
}
|
481
|
+
}
|
482
|
+
} else {
|
483
|
+
throw new Error('Row before insertion index does not exist.');
|
484
|
+
}
|
485
|
+
return tableNode;
|
486
|
+
}
|
487
|
+
const getHeaderState = (currentState, possibleState) => {
|
488
|
+
if (currentState === TableCellHeaderStates.BOTH || currentState === possibleState) {
|
489
|
+
return possibleState;
|
490
|
+
}
|
491
|
+
return TableCellHeaderStates.NO_STATUS;
|
492
|
+
};
|
493
|
+
function $insertTableRow__EXPERIMENTAL(insertAfter = true) {
|
494
|
+
const selection = $getSelection();
|
495
|
+
if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
|
496
|
+
throw Error(`Expected a RangeSelection or GridSelection`);
|
497
|
+
}
|
498
|
+
const focus = selection.focus.getNode();
|
499
|
+
const [focusCell,, grid] = $getNodeTriplet(focus);
|
500
|
+
const [gridMap, focusCellMap] = $computeTableMap(grid, focusCell, focusCell);
|
501
|
+
const columnCount = gridMap[0].length;
|
502
|
+
const {
|
503
|
+
startRow: focusStartRow
|
504
|
+
} = focusCellMap;
|
505
|
+
if (insertAfter) {
|
506
|
+
const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
|
507
|
+
const focusEndRowMap = gridMap[focusEndRow];
|
508
|
+
const newRow = $createTableRowNode();
|
509
|
+
for (let i = 0; i < columnCount; i++) {
|
510
|
+
const {
|
511
|
+
cell,
|
512
|
+
startRow
|
513
|
+
} = focusEndRowMap[i];
|
514
|
+
if (startRow + cell.__rowSpan - 1 <= focusEndRow) {
|
515
|
+
const currentCell = focusEndRowMap[i].cell;
|
516
|
+
const currentCellHeaderState = currentCell.__headerState;
|
517
|
+
const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.COLUMN);
|
518
|
+
newRow.append($createTableCellNode(headerState).append($createParagraphNode()));
|
519
|
+
} else {
|
520
|
+
cell.setRowSpan(cell.__rowSpan + 1);
|
521
|
+
}
|
522
|
+
}
|
523
|
+
const focusEndRowNode = grid.getChildAtIndex(focusEndRow);
|
524
|
+
if (!$isTableRowNode(focusEndRowNode)) {
|
525
|
+
throw Error(`focusEndRow is not a TableRowNode`);
|
526
|
+
}
|
527
|
+
focusEndRowNode.insertAfter(newRow);
|
528
|
+
} else {
|
529
|
+
const focusStartRowMap = gridMap[focusStartRow];
|
530
|
+
const newRow = $createTableRowNode();
|
531
|
+
for (let i = 0; i < columnCount; i++) {
|
532
|
+
const {
|
533
|
+
cell,
|
534
|
+
startRow
|
535
|
+
} = focusStartRowMap[i];
|
536
|
+
if (startRow === focusStartRow) {
|
537
|
+
const currentCell = focusStartRowMap[i].cell;
|
538
|
+
const currentCellHeaderState = currentCell.__headerState;
|
539
|
+
const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.COLUMN);
|
540
|
+
newRow.append($createTableCellNode(headerState).append($createParagraphNode()));
|
541
|
+
} else {
|
542
|
+
cell.setRowSpan(cell.__rowSpan + 1);
|
543
|
+
}
|
544
|
+
}
|
545
|
+
const focusStartRowNode = grid.getChildAtIndex(focusStartRow);
|
546
|
+
if (!$isTableRowNode(focusStartRowNode)) {
|
547
|
+
throw Error(`focusEndRow is not a TableRowNode`);
|
548
|
+
}
|
549
|
+
focusStartRowNode.insertBefore(newRow);
|
550
|
+
}
|
551
|
+
}
|
552
|
+
function $insertTableColumn(tableNode, targetIndex, shouldInsertAfter = true, columnCount, table) {
|
553
|
+
const tableRows = tableNode.getChildren();
|
554
|
+
const tableCellsToBeInserted = [];
|
555
|
+
for (let r = 0; r < tableRows.length; r++) {
|
556
|
+
const currentTableRowNode = tableRows[r];
|
557
|
+
if ($isTableRowNode(currentTableRowNode)) {
|
558
|
+
for (let c = 0; c < columnCount; c++) {
|
559
|
+
const tableRowChildren = currentTableRowNode.getChildren();
|
560
|
+
if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
|
561
|
+
throw new Error('Table column target index out of range');
|
562
|
+
}
|
563
|
+
const targetCell = tableRowChildren[targetIndex];
|
564
|
+
if (!$isTableCellNode(targetCell)) {
|
565
|
+
throw Error(`Expected table cell`);
|
566
|
+
}
|
567
|
+
const {
|
568
|
+
left,
|
569
|
+
right
|
570
|
+
} = $getTableCellSiblingsFromTableCellNode(targetCell, table);
|
571
|
+
let headerState = TableCellHeaderStates.NO_STATUS;
|
572
|
+
if (left && left.hasHeaderState(TableCellHeaderStates.ROW) || right && right.hasHeaderState(TableCellHeaderStates.ROW)) {
|
573
|
+
headerState |= TableCellHeaderStates.ROW;
|
574
|
+
}
|
575
|
+
const newTableCell = $createTableCellNode(headerState);
|
576
|
+
newTableCell.append($createParagraphNode());
|
577
|
+
tableCellsToBeInserted.push({
|
578
|
+
newTableCell,
|
579
|
+
targetCell
|
580
|
+
});
|
581
|
+
}
|
582
|
+
}
|
583
|
+
}
|
584
|
+
tableCellsToBeInserted.forEach(({
|
585
|
+
newTableCell,
|
586
|
+
targetCell
|
587
|
+
}) => {
|
588
|
+
if (shouldInsertAfter) {
|
589
|
+
targetCell.insertAfter(newTableCell);
|
590
|
+
} else {
|
591
|
+
targetCell.insertBefore(newTableCell);
|
592
|
+
}
|
593
|
+
});
|
594
|
+
return tableNode;
|
595
|
+
}
|
596
|
+
function $insertTableColumn__EXPERIMENTAL(insertAfter = true) {
|
597
|
+
const selection = $getSelection();
|
598
|
+
if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
|
599
|
+
throw Error(`Expected a RangeSelection or GridSelection`);
|
600
|
+
}
|
601
|
+
const anchor = selection.anchor.getNode();
|
602
|
+
const focus = selection.focus.getNode();
|
603
|
+
const [anchorCell] = $getNodeTriplet(anchor);
|
604
|
+
const [focusCell,, grid] = $getNodeTriplet(focus);
|
605
|
+
const [gridMap, focusCellMap, anchorCellMap] = $computeTableMap(grid, focusCell, anchorCell);
|
606
|
+
const rowCount = gridMap.length;
|
607
|
+
const startColumn = insertAfter ? Math.max(focusCellMap.startColumn, anchorCellMap.startColumn) : Math.min(focusCellMap.startColumn, anchorCellMap.startColumn);
|
608
|
+
const insertAfterColumn = insertAfter ? startColumn + focusCell.__colSpan - 1 : startColumn - 1;
|
609
|
+
const gridFirstChild = grid.getFirstChild();
|
610
|
+
if (!$isTableRowNode(gridFirstChild)) {
|
611
|
+
throw Error(`Expected firstTable child to be a row`);
|
612
|
+
}
|
613
|
+
let firstInsertedCell = null;
|
614
|
+
function $createTableCellNodeForInsertTableColumn(headerState = TableCellHeaderStates.NO_STATUS) {
|
615
|
+
const cell = $createTableCellNode(headerState).append($createParagraphNode());
|
616
|
+
if (firstInsertedCell === null) {
|
617
|
+
firstInsertedCell = cell;
|
618
|
+
}
|
619
|
+
return cell;
|
620
|
+
}
|
621
|
+
let loopRow = gridFirstChild;
|
622
|
+
rowLoop: for (let i = 0; i < rowCount; i++) {
|
623
|
+
if (i !== 0) {
|
624
|
+
const currentRow = loopRow.getNextSibling();
|
625
|
+
if (!$isTableRowNode(currentRow)) {
|
626
|
+
throw Error(`Expected row nextSibling to be a row`);
|
627
|
+
}
|
628
|
+
loopRow = currentRow;
|
629
|
+
}
|
630
|
+
const rowMap = gridMap[i];
|
631
|
+
const currentCellHeaderState = rowMap[insertAfterColumn < 0 ? 0 : insertAfterColumn].cell.__headerState;
|
632
|
+
const headerState = getHeaderState(currentCellHeaderState, TableCellHeaderStates.ROW);
|
633
|
+
if (insertAfterColumn < 0) {
|
634
|
+
$insertFirst(loopRow, $createTableCellNodeForInsertTableColumn(headerState));
|
635
|
+
continue;
|
636
|
+
}
|
637
|
+
const {
|
638
|
+
cell: currentCell,
|
639
|
+
startColumn: currentStartColumn,
|
640
|
+
startRow: currentStartRow
|
641
|
+
} = rowMap[insertAfterColumn];
|
642
|
+
if (currentStartColumn + currentCell.__colSpan - 1 <= insertAfterColumn) {
|
643
|
+
let insertAfterCell = currentCell;
|
644
|
+
let insertAfterCellRowStart = currentStartRow;
|
645
|
+
let prevCellIndex = insertAfterColumn;
|
646
|
+
while (insertAfterCellRowStart !== i && insertAfterCell.__rowSpan > 1) {
|
647
|
+
prevCellIndex -= currentCell.__colSpan;
|
648
|
+
if (prevCellIndex >= 0) {
|
649
|
+
const {
|
650
|
+
cell: cell_,
|
651
|
+
startRow: startRow_
|
652
|
+
} = rowMap[prevCellIndex];
|
653
|
+
insertAfterCell = cell_;
|
654
|
+
insertAfterCellRowStart = startRow_;
|
655
|
+
} else {
|
656
|
+
loopRow.append($createTableCellNodeForInsertTableColumn(headerState));
|
657
|
+
continue rowLoop;
|
658
|
+
}
|
659
|
+
}
|
660
|
+
insertAfterCell.insertAfter($createTableCellNodeForInsertTableColumn(headerState));
|
661
|
+
} else {
|
662
|
+
currentCell.setColSpan(currentCell.__colSpan + 1);
|
663
|
+
}
|
664
|
+
}
|
665
|
+
if (firstInsertedCell !== null) {
|
666
|
+
$moveSelectionToCell(firstInsertedCell);
|
667
|
+
}
|
668
|
+
}
|
669
|
+
function $deleteTableColumn(tableNode, targetIndex) {
|
670
|
+
const tableRows = tableNode.getChildren();
|
671
|
+
for (let i = 0; i < tableRows.length; i++) {
|
672
|
+
const currentTableRowNode = tableRows[i];
|
673
|
+
if ($isTableRowNode(currentTableRowNode)) {
|
674
|
+
const tableRowChildren = currentTableRowNode.getChildren();
|
675
|
+
if (targetIndex >= tableRowChildren.length || targetIndex < 0) {
|
676
|
+
throw new Error('Table column target index out of range');
|
677
|
+
}
|
678
|
+
tableRowChildren[targetIndex].remove();
|
679
|
+
}
|
680
|
+
}
|
681
|
+
return tableNode;
|
682
|
+
}
|
683
|
+
function $deleteTableRow__EXPERIMENTAL() {
|
684
|
+
const selection = $getSelection();
|
685
|
+
if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
|
686
|
+
throw Error(`Expected a RangeSelection or GridSelection`);
|
687
|
+
}
|
688
|
+
const anchor = selection.anchor.getNode();
|
689
|
+
const focus = selection.focus.getNode();
|
690
|
+
const [anchorCell,, grid] = $getNodeTriplet(anchor);
|
691
|
+
const [focusCell] = $getNodeTriplet(focus);
|
692
|
+
const [gridMap, anchorCellMap, focusCellMap] = $computeTableMap(grid, anchorCell, focusCell);
|
693
|
+
const {
|
694
|
+
startRow: anchorStartRow
|
695
|
+
} = anchorCellMap;
|
696
|
+
const {
|
697
|
+
startRow: focusStartRow
|
698
|
+
} = focusCellMap;
|
699
|
+
const focusEndRow = focusStartRow + focusCell.__rowSpan - 1;
|
700
|
+
if (gridMap.length === focusEndRow - anchorStartRow + 1) {
|
701
|
+
// Empty grid
|
702
|
+
grid.remove();
|
703
|
+
return;
|
704
|
+
}
|
705
|
+
const columnCount = gridMap[0].length;
|
706
|
+
const nextRow = gridMap[focusEndRow + 1];
|
707
|
+
const nextRowNode = grid.getChildAtIndex(focusEndRow + 1);
|
708
|
+
for (let row = focusEndRow; row >= anchorStartRow; row--) {
|
709
|
+
for (let column = columnCount - 1; column >= 0; column--) {
|
710
|
+
const {
|
711
|
+
cell,
|
712
|
+
startRow: cellStartRow,
|
713
|
+
startColumn: cellStartColumn
|
714
|
+
} = gridMap[row][column];
|
715
|
+
if (cellStartColumn !== column) {
|
716
|
+
// Don't repeat work for the same Cell
|
717
|
+
continue;
|
718
|
+
}
|
719
|
+
// Rows overflowing top have to be trimmed
|
720
|
+
if (row === anchorStartRow && cellStartRow < anchorStartRow) {
|
721
|
+
cell.setRowSpan(cell.__rowSpan - (cellStartRow - anchorStartRow));
|
722
|
+
}
|
723
|
+
// Rows overflowing bottom have to be trimmed and moved to the next row
|
724
|
+
if (cellStartRow >= anchorStartRow && cellStartRow + cell.__rowSpan - 1 > focusEndRow) {
|
725
|
+
cell.setRowSpan(cell.__rowSpan - (focusEndRow - cellStartRow + 1));
|
726
|
+
if (!(nextRowNode !== null)) {
|
727
|
+
throw Error(`Expected nextRowNode not to be null`);
|
728
|
+
}
|
729
|
+
if (column === 0) {
|
730
|
+
$insertFirst(nextRowNode, cell);
|
731
|
+
} else {
|
732
|
+
const {
|
733
|
+
cell: previousCell
|
734
|
+
} = nextRow[column - 1];
|
735
|
+
previousCell.insertAfter(cell);
|
736
|
+
}
|
737
|
+
}
|
738
|
+
}
|
739
|
+
const rowNode = grid.getChildAtIndex(row);
|
740
|
+
if (!$isTableRowNode(rowNode)) {
|
741
|
+
throw Error(`Expected GridNode childAtIndex(${String(row)}) to be RowNode`);
|
742
|
+
}
|
743
|
+
rowNode.remove();
|
744
|
+
}
|
745
|
+
if (nextRow !== undefined) {
|
746
|
+
const {
|
747
|
+
cell
|
748
|
+
} = nextRow[0];
|
749
|
+
$moveSelectionToCell(cell);
|
750
|
+
} else {
|
751
|
+
const previousRow = gridMap[anchorStartRow - 1];
|
752
|
+
const {
|
753
|
+
cell
|
754
|
+
} = previousRow[0];
|
755
|
+
$moveSelectionToCell(cell);
|
756
|
+
}
|
757
|
+
}
|
758
|
+
function $deleteTableColumn__EXPERIMENTAL() {
|
759
|
+
const selection = $getSelection();
|
760
|
+
if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
|
761
|
+
throw Error(`Expected a RangeSelection or GridSelection`);
|
762
|
+
}
|
763
|
+
const anchor = selection.anchor.getNode();
|
764
|
+
const focus = selection.focus.getNode();
|
765
|
+
const [anchorCell,, grid] = $getNodeTriplet(anchor);
|
766
|
+
const [focusCell] = $getNodeTriplet(focus);
|
767
|
+
const [gridMap, anchorCellMap, focusCellMap] = $computeTableMap(grid, anchorCell, focusCell);
|
768
|
+
const {
|
769
|
+
startColumn: anchorStartColumn
|
770
|
+
} = anchorCellMap;
|
771
|
+
const {
|
772
|
+
startRow: focusStartRow,
|
773
|
+
startColumn: focusStartColumn
|
774
|
+
} = focusCellMap;
|
775
|
+
const startColumn = Math.min(anchorStartColumn, focusStartColumn);
|
776
|
+
const endColumn = Math.max(anchorStartColumn + anchorCell.__colSpan - 1, focusStartColumn + focusCell.__colSpan - 1);
|
777
|
+
const selectedColumnCount = endColumn - startColumn + 1;
|
778
|
+
const columnCount = gridMap[0].length;
|
779
|
+
if (columnCount === endColumn - startColumn + 1) {
|
780
|
+
// Empty grid
|
781
|
+
grid.selectPrevious();
|
782
|
+
grid.remove();
|
783
|
+
return;
|
784
|
+
}
|
785
|
+
const rowCount = gridMap.length;
|
786
|
+
for (let row = 0; row < rowCount; row++) {
|
787
|
+
for (let column = startColumn; column <= endColumn; column++) {
|
788
|
+
const {
|
789
|
+
cell,
|
790
|
+
startColumn: cellStartColumn
|
791
|
+
} = gridMap[row][column];
|
792
|
+
if (cellStartColumn < startColumn) {
|
793
|
+
if (column === startColumn) {
|
794
|
+
const overflowLeft = startColumn - cellStartColumn;
|
795
|
+
// Overflowing left
|
796
|
+
cell.setColSpan(cell.__colSpan -
|
797
|
+
// Possible overflow right too
|
798
|
+
Math.min(selectedColumnCount, cell.__colSpan - overflowLeft));
|
799
|
+
}
|
800
|
+
} else if (cellStartColumn + cell.__colSpan - 1 > endColumn) {
|
801
|
+
if (column === endColumn) {
|
802
|
+
// Overflowing right
|
803
|
+
const inSelectedArea = endColumn - cellStartColumn + 1;
|
804
|
+
cell.setColSpan(cell.__colSpan - inSelectedArea);
|
805
|
+
}
|
806
|
+
} else {
|
807
|
+
cell.remove();
|
808
|
+
}
|
809
|
+
}
|
810
|
+
}
|
811
|
+
const focusRowMap = gridMap[focusStartRow];
|
812
|
+
const nextColumn = focusRowMap[focusStartColumn + focusCell.__colSpan];
|
813
|
+
if (nextColumn !== undefined) {
|
814
|
+
const {
|
815
|
+
cell
|
816
|
+
} = nextColumn;
|
817
|
+
$moveSelectionToCell(cell);
|
818
|
+
} else {
|
819
|
+
const previousRow = focusRowMap[focusStartColumn - 1];
|
820
|
+
const {
|
821
|
+
cell
|
822
|
+
} = previousRow;
|
823
|
+
$moveSelectionToCell(cell);
|
824
|
+
}
|
825
|
+
}
|
826
|
+
function $moveSelectionToCell(cell) {
|
827
|
+
const firstDescendant = cell.getFirstDescendant();
|
828
|
+
if (firstDescendant == null) {
|
829
|
+
cell.selectStart();
|
830
|
+
} else {
|
831
|
+
firstDescendant.getParentOrThrow().selectStart();
|
832
|
+
}
|
833
|
+
}
|
834
|
+
function $insertFirst(parent, node) {
|
835
|
+
const firstChild = parent.getFirstChild();
|
836
|
+
if (firstChild !== null) {
|
837
|
+
firstChild.insertBefore(node);
|
838
|
+
} else {
|
839
|
+
parent.append(node);
|
840
|
+
}
|
841
|
+
}
|
842
|
+
function $unmergeCell() {
|
843
|
+
const selection = $getSelection();
|
844
|
+
if (!($isRangeSelection(selection) || $isTableSelection(selection))) {
|
845
|
+
throw Error(`Expected a RangeSelection or GridSelection`);
|
846
|
+
}
|
847
|
+
const anchor = selection.anchor.getNode();
|
848
|
+
const [cell, row, grid] = $getNodeTriplet(anchor);
|
849
|
+
const colSpan = cell.__colSpan;
|
850
|
+
const rowSpan = cell.__rowSpan;
|
851
|
+
if (colSpan > 1) {
|
852
|
+
for (let i = 1; i < colSpan; i++) {
|
853
|
+
cell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
|
854
|
+
}
|
855
|
+
cell.setColSpan(1);
|
856
|
+
}
|
857
|
+
if (rowSpan > 1) {
|
858
|
+
const [map, cellMap] = $computeTableMap(grid, cell, cell);
|
859
|
+
const {
|
860
|
+
startColumn,
|
861
|
+
startRow
|
862
|
+
} = cellMap;
|
863
|
+
let currentRowNode;
|
864
|
+
for (let i = 1; i < rowSpan; i++) {
|
865
|
+
const currentRow = startRow + i;
|
866
|
+
const currentRowMap = map[currentRow];
|
867
|
+
currentRowNode = (currentRowNode || row).getNextSibling();
|
868
|
+
if (!$isTableRowNode(currentRowNode)) {
|
869
|
+
throw Error(`Expected row next sibling to be a row`);
|
870
|
+
}
|
871
|
+
let insertAfterCell = null;
|
872
|
+
for (let column = 0; column < startColumn; column++) {
|
873
|
+
const currentCellMap = currentRowMap[column];
|
874
|
+
const currentCell = currentCellMap.cell;
|
875
|
+
if (currentCellMap.startRow === currentRow) {
|
876
|
+
insertAfterCell = currentCell;
|
877
|
+
}
|
878
|
+
if (currentCell.__colSpan > 1) {
|
879
|
+
column += currentCell.__colSpan - 1;
|
880
|
+
}
|
881
|
+
}
|
882
|
+
if (insertAfterCell === null) {
|
883
|
+
for (let j = 0; j < colSpan; j++) {
|
884
|
+
$insertFirst(currentRowNode, $createTableCellNode(TableCellHeaderStates.NO_STATUS));
|
885
|
+
}
|
886
|
+
} else {
|
887
|
+
for (let j = 0; j < colSpan; j++) {
|
888
|
+
insertAfterCell.insertAfter($createTableCellNode(TableCellHeaderStates.NO_STATUS));
|
889
|
+
}
|
890
|
+
}
|
891
|
+
}
|
892
|
+
cell.setRowSpan(1);
|
893
|
+
}
|
894
|
+
}
|
895
|
+
function $computeTableMap(grid, cellA, cellB) {
|
896
|
+
const tableMap = [];
|
897
|
+
let cellAValue = null;
|
898
|
+
let cellBValue = null;
|
899
|
+
function write(startRow, startColumn, cell) {
|
900
|
+
const value = {
|
901
|
+
cell,
|
902
|
+
startColumn,
|
903
|
+
startRow
|
904
|
+
};
|
905
|
+
const rowSpan = cell.__rowSpan;
|
906
|
+
const colSpan = cell.__colSpan;
|
907
|
+
for (let i = 0; i < rowSpan; i++) {
|
908
|
+
if (tableMap[startRow + i] === undefined) {
|
909
|
+
tableMap[startRow + i] = [];
|
910
|
+
}
|
911
|
+
for (let j = 0; j < colSpan; j++) {
|
912
|
+
tableMap[startRow + i][startColumn + j] = value;
|
913
|
+
}
|
914
|
+
}
|
915
|
+
if (cellA.is(cell)) {
|
916
|
+
cellAValue = value;
|
917
|
+
}
|
918
|
+
if (cellB.is(cell)) {
|
919
|
+
cellBValue = value;
|
920
|
+
}
|
921
|
+
}
|
922
|
+
function isEmpty(row, column) {
|
923
|
+
return tableMap[row] === undefined || tableMap[row][column] === undefined;
|
924
|
+
}
|
925
|
+
const gridChildren = grid.getChildren();
|
926
|
+
for (let i = 0; i < gridChildren.length; i++) {
|
927
|
+
const row = gridChildren[i];
|
928
|
+
if (!$isTableRowNode(row)) {
|
929
|
+
throw Error(`Expected GridNode children to be TableRowNode`);
|
930
|
+
}
|
931
|
+
const rowChildren = row.getChildren();
|
932
|
+
let j = 0;
|
933
|
+
for (const cell of rowChildren) {
|
934
|
+
if (!$isTableCellNode(cell)) {
|
935
|
+
throw Error(`Expected TableRowNode children to be TableCellNode`);
|
936
|
+
}
|
937
|
+
while (!isEmpty(i, j)) {
|
938
|
+
j++;
|
939
|
+
}
|
940
|
+
write(i, j, cell);
|
941
|
+
j += cell.__colSpan;
|
942
|
+
}
|
943
|
+
}
|
944
|
+
if (!(cellAValue !== null)) {
|
945
|
+
throw Error(`Anchor not found in Grid`);
|
946
|
+
}
|
947
|
+
if (!(cellBValue !== null)) {
|
948
|
+
throw Error(`Focus not found in Grid`);
|
949
|
+
}
|
950
|
+
return [tableMap, cellAValue, cellBValue];
|
951
|
+
}
|
952
|
+
function $getNodeTriplet(source) {
|
953
|
+
let cell;
|
954
|
+
if (source instanceof TableCellNode) {
|
955
|
+
cell = source;
|
956
|
+
} else if ('__type' in source) {
|
957
|
+
const cell_ = $findMatchingParent(source, $isTableCellNode);
|
958
|
+
if (!$isTableCellNode(cell_)) {
|
959
|
+
throw Error(`Expected to find a parent TableCellNode`);
|
960
|
+
}
|
961
|
+
cell = cell_;
|
962
|
+
} else {
|
963
|
+
const cell_ = $findMatchingParent(source.getNode(), $isTableCellNode);
|
964
|
+
if (!$isTableCellNode(cell_)) {
|
965
|
+
throw Error(`Expected to find a parent TableCellNode`);
|
966
|
+
}
|
967
|
+
cell = cell_;
|
968
|
+
}
|
969
|
+
const row = cell.getParent();
|
970
|
+
if (!$isTableRowNode(row)) {
|
971
|
+
throw Error(`Expected TableCellNode to have a parent TableRowNode`);
|
972
|
+
}
|
973
|
+
const grid = row.getParent();
|
974
|
+
if (!$isTableNode(grid)) {
|
975
|
+
throw Error(`Expected TableRowNode to have a parent GridNode`);
|
976
|
+
}
|
977
|
+
return [cell, row, grid];
|
978
|
+
}
|
979
|
+
function $getTableCellNodeRect(tableCellNode) {
|
980
|
+
const [cellNode,, gridNode] = $getNodeTriplet(tableCellNode);
|
981
|
+
const rows = gridNode.getChildren();
|
982
|
+
const rowCount = rows.length;
|
983
|
+
const columnCount = rows[0].getChildren().length;
|
984
|
+
|
985
|
+
// Create a matrix of the same size as the table to track the position of each cell
|
986
|
+
const cellMatrix = new Array(rowCount);
|
987
|
+
for (let i = 0; i < rowCount; i++) {
|
988
|
+
cellMatrix[i] = new Array(columnCount);
|
989
|
+
}
|
990
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
991
|
+
const row = rows[rowIndex];
|
992
|
+
const cells = row.getChildren();
|
993
|
+
let columnIndex = 0;
|
994
|
+
for (let cellIndex = 0; cellIndex < cells.length; cellIndex++) {
|
995
|
+
// Find the next available position in the matrix, skip the position of merged cells
|
996
|
+
while (cellMatrix[rowIndex][columnIndex]) {
|
997
|
+
columnIndex++;
|
998
|
+
}
|
999
|
+
const cell = cells[cellIndex];
|
1000
|
+
const rowSpan = cell.__rowSpan || 1;
|
1001
|
+
const colSpan = cell.__colSpan || 1;
|
1002
|
+
|
1003
|
+
// Put the cell into the corresponding position in the matrix
|
1004
|
+
for (let i = 0; i < rowSpan; i++) {
|
1005
|
+
for (let j = 0; j < colSpan; j++) {
|
1006
|
+
cellMatrix[rowIndex + i][columnIndex + j] = cell;
|
1007
|
+
}
|
1008
|
+
}
|
1009
|
+
|
1010
|
+
// Return to the original index, row span and column span of the cell.
|
1011
|
+
if (cellNode === cell) {
|
1012
|
+
return {
|
1013
|
+
colSpan,
|
1014
|
+
columnIndex,
|
1015
|
+
rowIndex,
|
1016
|
+
rowSpan
|
1017
|
+
};
|
1018
|
+
}
|
1019
|
+
columnIndex += colSpan;
|
1020
|
+
}
|
1021
|
+
}
|
1022
|
+
return null;
|
1023
|
+
}
|
1024
|
+
|
1025
|
+
/**
|
1026
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
1027
|
+
*
|
1028
|
+
* This source code is licensed under the MIT license found in the
|
1029
|
+
* LICENSE file in the root directory of this source tree.
|
1030
|
+
*
|
1031
|
+
*/
|
1032
|
+
class TableSelection {
|
1033
|
+
constructor(tableKey, anchor, focus) {
|
1034
|
+
this.anchor = anchor;
|
1035
|
+
this.focus = focus;
|
1036
|
+
anchor._selection = this;
|
1037
|
+
focus._selection = this;
|
1038
|
+
this._cachedNodes = null;
|
1039
|
+
this.dirty = false;
|
1040
|
+
this.tableKey = tableKey;
|
1041
|
+
}
|
1042
|
+
getStartEndPoints() {
|
1043
|
+
return [this.anchor, this.focus];
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
/**
|
1047
|
+
* Returns whether the Selection is "backwards", meaning the focus
|
1048
|
+
* logically precedes the anchor in the EditorState.
|
1049
|
+
* @returns true if the Selection is backwards, false otherwise.
|
1050
|
+
*/
|
1051
|
+
isBackward() {
|
1052
|
+
return this.focus.isBefore(this.anchor);
|
1053
|
+
}
|
1054
|
+
getCachedNodes() {
|
1055
|
+
return this._cachedNodes;
|
1056
|
+
}
|
1057
|
+
setCachedNodes(nodes) {
|
1058
|
+
this._cachedNodes = nodes;
|
1059
|
+
}
|
1060
|
+
is(selection) {
|
1061
|
+
if (!$isTableSelection(selection)) {
|
1062
|
+
return false;
|
1063
|
+
}
|
1064
|
+
return this.tableKey === selection.tableKey && this.anchor.is(selection.anchor) && this.focus.is(selection.focus);
|
1065
|
+
}
|
1066
|
+
set(tableKey, anchorCellKey, focusCellKey) {
|
1067
|
+
this.dirty = true;
|
1068
|
+
this.tableKey = tableKey;
|
1069
|
+
this.anchor.key = anchorCellKey;
|
1070
|
+
this.focus.key = focusCellKey;
|
1071
|
+
this._cachedNodes = null;
|
1072
|
+
}
|
1073
|
+
clone() {
|
1074
|
+
return new TableSelection(this.tableKey, this.anchor, this.focus);
|
1075
|
+
}
|
1076
|
+
isCollapsed() {
|
1077
|
+
return false;
|
1078
|
+
}
|
1079
|
+
extract() {
|
1080
|
+
return this.getNodes();
|
1081
|
+
}
|
1082
|
+
insertRawText(text) {
|
1083
|
+
// Do nothing?
|
1084
|
+
}
|
1085
|
+
insertText() {
|
1086
|
+
// Do nothing?
|
1087
|
+
}
|
1088
|
+
insertNodes(nodes) {
|
1089
|
+
const focusNode = this.focus.getNode();
|
1090
|
+
if (!$isElementNode(focusNode)) {
|
1091
|
+
throw Error(`Expected TableSelection focus to be an ElementNode`);
|
1092
|
+
}
|
1093
|
+
const selection = $normalizeSelection__EXPERIMENTAL(focusNode.select(0, focusNode.getChildrenSize()));
|
1094
|
+
selection.insertNodes(nodes);
|
1095
|
+
}
|
1096
|
+
|
1097
|
+
// TODO Deprecate this method. It's confusing when used with colspan|rowspan
|
1098
|
+
getShape() {
|
1099
|
+
const anchorCellNode = $getNodeByKey(this.anchor.key);
|
1100
|
+
if (!$isTableCellNode(anchorCellNode)) {
|
1101
|
+
throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
|
1102
|
+
}
|
1103
|
+
const anchorCellNodeRect = $getTableCellNodeRect(anchorCellNode);
|
1104
|
+
if (!(anchorCellNodeRect !== null)) {
|
1105
|
+
throw Error(`getCellRect: expected to find AnchorNode`);
|
1106
|
+
}
|
1107
|
+
const focusCellNode = $getNodeByKey(this.focus.key);
|
1108
|
+
if (!$isTableCellNode(focusCellNode)) {
|
1109
|
+
throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
|
1110
|
+
}
|
1111
|
+
const focusCellNodeRect = $getTableCellNodeRect(focusCellNode);
|
1112
|
+
if (!(focusCellNodeRect !== null)) {
|
1113
|
+
throw Error(`getCellRect: expected to find focusCellNode`);
|
1114
|
+
}
|
1115
|
+
const startX = Math.min(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
|
1116
|
+
const stopX = Math.max(anchorCellNodeRect.columnIndex, focusCellNodeRect.columnIndex);
|
1117
|
+
const startY = Math.min(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
|
1118
|
+
const stopY = Math.max(anchorCellNodeRect.rowIndex, focusCellNodeRect.rowIndex);
|
1119
|
+
return {
|
1120
|
+
fromX: Math.min(startX, stopX),
|
1121
|
+
fromY: Math.min(startY, stopY),
|
1122
|
+
toX: Math.max(startX, stopX),
|
1123
|
+
toY: Math.max(startY, stopY)
|
1124
|
+
};
|
1125
|
+
}
|
1126
|
+
getNodes() {
|
1127
|
+
const cachedNodes = this._cachedNodes;
|
1128
|
+
if (cachedNodes !== null) {
|
1129
|
+
return cachedNodes;
|
1130
|
+
}
|
1131
|
+
const anchorNode = this.anchor.getNode();
|
1132
|
+
const focusNode = this.focus.getNode();
|
1133
|
+
const anchorCell = $findMatchingParent(anchorNode, $isTableCellNode);
|
1134
|
+
// todo replace with triplet
|
1135
|
+
const focusCell = $findMatchingParent(focusNode, $isTableCellNode);
|
1136
|
+
if (!$isTableCellNode(anchorCell)) {
|
1137
|
+
throw Error(`Expected TableSelection anchor to be (or a child of) TableCellNode`);
|
1138
|
+
}
|
1139
|
+
if (!$isTableCellNode(focusCell)) {
|
1140
|
+
throw Error(`Expected TableSelection focus to be (or a child of) TableCellNode`);
|
1141
|
+
}
|
1142
|
+
const anchorRow = anchorCell.getParent();
|
1143
|
+
if (!$isTableRowNode(anchorRow)) {
|
1144
|
+
throw Error(`Expected anchorCell to have a parent TableRowNode`);
|
1145
|
+
}
|
1146
|
+
const tableNode = anchorRow.getParent();
|
1147
|
+
if (!$isTableNode(tableNode)) {
|
1148
|
+
throw Error(`Expected tableNode to have a parent TableNode`);
|
1149
|
+
}
|
1150
|
+
const focusCellGrid = focusCell.getParents()[1];
|
1151
|
+
if (focusCellGrid !== tableNode) {
|
1152
|
+
if (!tableNode.isParentOf(focusCell)) {
|
1153
|
+
// focus is on higher Grid level than anchor
|
1154
|
+
const gridParent = tableNode.getParent();
|
1155
|
+
if (!(gridParent != null)) {
|
1156
|
+
throw Error(`Expected gridParent to have a parent`);
|
1157
|
+
}
|
1158
|
+
this.set(this.tableKey, gridParent.getKey(), focusCell.getKey());
|
1159
|
+
} else {
|
1160
|
+
// anchor is on higher Grid level than focus
|
1161
|
+
const focusCellParent = focusCellGrid.getParent();
|
1162
|
+
if (!(focusCellParent != null)) {
|
1163
|
+
throw Error(`Expected focusCellParent to have a parent`);
|
1164
|
+
}
|
1165
|
+
this.set(this.tableKey, focusCell.getKey(), focusCellParent.getKey());
|
1166
|
+
}
|
1167
|
+
return this.getNodes();
|
1168
|
+
}
|
1169
|
+
|
1170
|
+
// TODO Mapping the whole Grid every time not efficient. We need to compute the entire state only
|
1171
|
+
// once (on load) and iterate on it as updates occur. However, to do this we need to have the
|
1172
|
+
// ability to store a state. Killing TableSelection and moving the logic to the plugin would make
|
1173
|
+
// this possible.
|
1174
|
+
const [map, cellAMap, cellBMap] = $computeTableMap(tableNode, anchorCell, focusCell);
|
1175
|
+
let minColumn = Math.min(cellAMap.startColumn, cellBMap.startColumn);
|
1176
|
+
let minRow = Math.min(cellAMap.startRow, cellBMap.startRow);
|
1177
|
+
let maxColumn = Math.max(cellAMap.startColumn + cellAMap.cell.__colSpan - 1, cellBMap.startColumn + cellBMap.cell.__colSpan - 1);
|
1178
|
+
let maxRow = Math.max(cellAMap.startRow + cellAMap.cell.__rowSpan - 1, cellBMap.startRow + cellBMap.cell.__rowSpan - 1);
|
1179
|
+
let exploredMinColumn = minColumn;
|
1180
|
+
let exploredMinRow = minRow;
|
1181
|
+
let exploredMaxColumn = minColumn;
|
1182
|
+
let exploredMaxRow = minRow;
|
1183
|
+
function expandBoundary(mapValue) {
|
1184
|
+
const {
|
1185
|
+
cell,
|
1186
|
+
startColumn: cellStartColumn,
|
1187
|
+
startRow: cellStartRow
|
1188
|
+
} = mapValue;
|
1189
|
+
minColumn = Math.min(minColumn, cellStartColumn);
|
1190
|
+
minRow = Math.min(minRow, cellStartRow);
|
1191
|
+
maxColumn = Math.max(maxColumn, cellStartColumn + cell.__colSpan - 1);
|
1192
|
+
maxRow = Math.max(maxRow, cellStartRow + cell.__rowSpan - 1);
|
1193
|
+
}
|
1194
|
+
while (minColumn < exploredMinColumn || minRow < exploredMinRow || maxColumn > exploredMaxColumn || maxRow > exploredMaxRow) {
|
1195
|
+
if (minColumn < exploredMinColumn) {
|
1196
|
+
// Expand on the left
|
1197
|
+
const rowDiff = exploredMaxRow - exploredMinRow;
|
1198
|
+
const previousColumn = exploredMinColumn - 1;
|
1199
|
+
for (let i = 0; i <= rowDiff; i++) {
|
1200
|
+
expandBoundary(map[exploredMinRow + i][previousColumn]);
|
1201
|
+
}
|
1202
|
+
exploredMinColumn = previousColumn;
|
1203
|
+
}
|
1204
|
+
if (minRow < exploredMinRow) {
|
1205
|
+
// Expand on top
|
1206
|
+
const columnDiff = exploredMaxColumn - exploredMinColumn;
|
1207
|
+
const previousRow = exploredMinRow - 1;
|
1208
|
+
for (let i = 0; i <= columnDiff; i++) {
|
1209
|
+
expandBoundary(map[previousRow][exploredMinColumn + i]);
|
1210
|
+
}
|
1211
|
+
exploredMinRow = previousRow;
|
1212
|
+
}
|
1213
|
+
if (maxColumn > exploredMaxColumn) {
|
1214
|
+
// Expand on the right
|
1215
|
+
const rowDiff = exploredMaxRow - exploredMinRow;
|
1216
|
+
const nextColumn = exploredMaxColumn + 1;
|
1217
|
+
for (let i = 0; i <= rowDiff; i++) {
|
1218
|
+
expandBoundary(map[exploredMinRow + i][nextColumn]);
|
1219
|
+
}
|
1220
|
+
exploredMaxColumn = nextColumn;
|
1221
|
+
}
|
1222
|
+
if (maxRow > exploredMaxRow) {
|
1223
|
+
// Expand on the bottom
|
1224
|
+
const columnDiff = exploredMaxColumn - exploredMinColumn;
|
1225
|
+
const nextRow = exploredMaxRow + 1;
|
1226
|
+
for (let i = 0; i <= columnDiff; i++) {
|
1227
|
+
expandBoundary(map[nextRow][exploredMinColumn + i]);
|
1228
|
+
}
|
1229
|
+
exploredMaxRow = nextRow;
|
1230
|
+
}
|
1231
|
+
}
|
1232
|
+
const nodes = [tableNode];
|
1233
|
+
let lastRow = null;
|
1234
|
+
for (let i = minRow; i <= maxRow; i++) {
|
1235
|
+
for (let j = minColumn; j <= maxColumn; j++) {
|
1236
|
+
const {
|
1237
|
+
cell
|
1238
|
+
} = map[i][j];
|
1239
|
+
const currentRow = cell.getParent();
|
1240
|
+
if (!$isTableRowNode(currentRow)) {
|
1241
|
+
throw Error(`Expected TableCellNode parent to be a TableRowNode`);
|
1242
|
+
}
|
1243
|
+
if (currentRow !== lastRow) {
|
1244
|
+
nodes.push(currentRow);
|
1245
|
+
}
|
1246
|
+
nodes.push(cell, ...$getChildrenRecursively(cell));
|
1247
|
+
lastRow = currentRow;
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
if (!isCurrentlyReadOnlyMode()) {
|
1251
|
+
this._cachedNodes = nodes;
|
1252
|
+
}
|
1253
|
+
return nodes;
|
1254
|
+
}
|
1255
|
+
getTextContent() {
|
1256
|
+
const nodes = this.getNodes();
|
1257
|
+
let textContent = '';
|
1258
|
+
for (let i = 0; i < nodes.length; i++) {
|
1259
|
+
textContent += nodes[i].getTextContent();
|
1260
|
+
}
|
1261
|
+
return textContent;
|
1262
|
+
}
|
1263
|
+
}
|
1264
|
+
function $isTableSelection(x) {
|
1265
|
+
return x instanceof TableSelection;
|
1266
|
+
}
|
1267
|
+
function $createTableSelection() {
|
1268
|
+
const anchor = $createPoint('root', 0, 'element');
|
1269
|
+
const focus = $createPoint('root', 0, 'element');
|
1270
|
+
return new TableSelection('root', anchor, focus);
|
1271
|
+
}
|
1272
|
+
function $getChildrenRecursively(node) {
|
1273
|
+
const nodes = [];
|
1274
|
+
const stack = [node];
|
1275
|
+
while (stack.length > 0) {
|
1276
|
+
const currentNode = stack.pop();
|
1277
|
+
if (!(currentNode !== undefined)) {
|
1278
|
+
throw Error(`Stack.length > 0; can't be undefined`);
|
1279
|
+
}
|
1280
|
+
if ($isElementNode(currentNode)) {
|
1281
|
+
stack.unshift(...currentNode.getChildren());
|
1282
|
+
}
|
1283
|
+
if (currentNode !== node) {
|
1284
|
+
nodes.push(currentNode);
|
1285
|
+
}
|
1286
|
+
}
|
1287
|
+
return nodes;
|
1288
|
+
}
|
1289
|
+
|
1290
|
+
/**
|
1291
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
1292
|
+
*
|
1293
|
+
* This source code is licensed under the MIT license found in the
|
1294
|
+
* LICENSE file in the root directory of this source tree.
|
1295
|
+
*
|
1296
|
+
*/
|
1297
|
+
const getDOMSelection = targetWindow => CAN_USE_DOM ? (targetWindow || window).getSelection() : null;
|
1298
|
+
class TableObserver {
|
1299
|
+
constructor(editor, tableNodeKey) {
|
1300
|
+
this.isHighlightingCells = false;
|
1301
|
+
this.anchorX = -1;
|
1302
|
+
this.anchorY = -1;
|
1303
|
+
this.focusX = -1;
|
1304
|
+
this.focusY = -1;
|
1305
|
+
this.listenersToRemove = new Set();
|
1306
|
+
this.tableNodeKey = tableNodeKey;
|
1307
|
+
this.editor = editor;
|
1308
|
+
this.table = {
|
1309
|
+
columns: 0,
|
1310
|
+
domRows: [],
|
1311
|
+
rows: 0
|
1312
|
+
};
|
1313
|
+
this.tableSelection = null;
|
1314
|
+
this.anchorCellNodeKey = null;
|
1315
|
+
this.focusCellNodeKey = null;
|
1316
|
+
this.anchorCell = null;
|
1317
|
+
this.focusCell = null;
|
1318
|
+
this.hasHijackedSelectionStyles = false;
|
1319
|
+
this.trackTable();
|
1320
|
+
}
|
1321
|
+
getTable() {
|
1322
|
+
return this.table;
|
1323
|
+
}
|
1324
|
+
removeListeners() {
|
1325
|
+
Array.from(this.listenersToRemove).forEach(removeListener => removeListener());
|
1326
|
+
}
|
1327
|
+
trackTable() {
|
1328
|
+
const observer = new MutationObserver(records => {
|
1329
|
+
this.editor.update(() => {
|
1330
|
+
let gridNeedsRedraw = false;
|
1331
|
+
for (let i = 0; i < records.length; i++) {
|
1332
|
+
const record = records[i];
|
1333
|
+
const target = record.target;
|
1334
|
+
const nodeName = target.nodeName;
|
1335
|
+
if (nodeName === 'TABLE' || nodeName === 'TR') {
|
1336
|
+
gridNeedsRedraw = true;
|
1337
|
+
break;
|
1338
|
+
}
|
1339
|
+
}
|
1340
|
+
if (!gridNeedsRedraw) {
|
1341
|
+
return;
|
1342
|
+
}
|
1343
|
+
const tableElement = this.editor.getElementByKey(this.tableNodeKey);
|
1344
|
+
if (!tableElement) {
|
1345
|
+
throw new Error('Expected to find TableElement in DOM');
|
1346
|
+
}
|
1347
|
+
this.table = getTable(tableElement);
|
1348
|
+
});
|
1349
|
+
});
|
1350
|
+
this.editor.update(() => {
|
1351
|
+
const tableElement = this.editor.getElementByKey(this.tableNodeKey);
|
1352
|
+
if (!tableElement) {
|
1353
|
+
throw new Error('Expected to find TableElement in DOM');
|
1354
|
+
}
|
1355
|
+
this.table = getTable(tableElement);
|
1356
|
+
observer.observe(tableElement, {
|
1357
|
+
childList: true,
|
1358
|
+
subtree: true
|
1359
|
+
});
|
1360
|
+
});
|
1361
|
+
}
|
1362
|
+
clearHighlight() {
|
1363
|
+
const editor = this.editor;
|
1364
|
+
this.isHighlightingCells = false;
|
1365
|
+
this.anchorX = -1;
|
1366
|
+
this.anchorY = -1;
|
1367
|
+
this.focusX = -1;
|
1368
|
+
this.focusY = -1;
|
1369
|
+
this.tableSelection = null;
|
1370
|
+
this.anchorCellNodeKey = null;
|
1371
|
+
this.focusCellNodeKey = null;
|
1372
|
+
this.anchorCell = null;
|
1373
|
+
this.focusCell = null;
|
1374
|
+
this.hasHijackedSelectionStyles = false;
|
1375
|
+
this.enableHighlightStyle();
|
1376
|
+
editor.update(() => {
|
1377
|
+
const tableNode = $getNodeByKey(this.tableNodeKey);
|
1378
|
+
if (!$isTableNode(tableNode)) {
|
1379
|
+
throw new Error('Expected TableNode.');
|
1380
|
+
}
|
1381
|
+
const tableElement = editor.getElementByKey(this.tableNodeKey);
|
1382
|
+
if (!tableElement) {
|
1383
|
+
throw new Error('Expected to find TableElement in DOM');
|
1384
|
+
}
|
1385
|
+
const grid = getTable(tableElement);
|
1386
|
+
$updateDOMForSelection(editor, grid, null);
|
1387
|
+
$setSelection(null);
|
1388
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1389
|
+
});
|
1390
|
+
}
|
1391
|
+
enableHighlightStyle() {
|
1392
|
+
const editor = this.editor;
|
1393
|
+
editor.update(() => {
|
1394
|
+
const tableElement = editor.getElementByKey(this.tableNodeKey);
|
1395
|
+
if (!tableElement) {
|
1396
|
+
throw new Error('Expected to find TableElement in DOM');
|
1397
|
+
}
|
1398
|
+
removeClassNamesFromElement(tableElement, editor._config.theme.tableSelection);
|
1399
|
+
tableElement.classList.remove('disable-selection');
|
1400
|
+
this.hasHijackedSelectionStyles = false;
|
1401
|
+
});
|
1402
|
+
}
|
1403
|
+
disableHighlightStyle() {
|
1404
|
+
const editor = this.editor;
|
1405
|
+
editor.update(() => {
|
1406
|
+
const tableElement = editor.getElementByKey(this.tableNodeKey);
|
1407
|
+
if (!tableElement) {
|
1408
|
+
throw new Error('Expected to find TableElement in DOM');
|
1409
|
+
}
|
1410
|
+
addClassNamesToElement(tableElement, editor._config.theme.tableSelection);
|
1411
|
+
this.hasHijackedSelectionStyles = true;
|
1412
|
+
});
|
1413
|
+
}
|
1414
|
+
updateTableTableSelection(selection) {
|
1415
|
+
if (selection !== null && selection.tableKey === this.tableNodeKey) {
|
1416
|
+
const editor = this.editor;
|
1417
|
+
this.tableSelection = selection;
|
1418
|
+
this.isHighlightingCells = true;
|
1419
|
+
this.disableHighlightStyle();
|
1420
|
+
$updateDOMForSelection(editor, this.table, this.tableSelection);
|
1421
|
+
} else if (selection == null) {
|
1422
|
+
this.clearHighlight();
|
1423
|
+
} else {
|
1424
|
+
this.tableNodeKey = selection.tableKey;
|
1425
|
+
this.updateTableTableSelection(selection);
|
1426
|
+
}
|
1427
|
+
}
|
1428
|
+
setFocusCellForSelection(cell, ignoreStart = false) {
|
1429
|
+
const editor = this.editor;
|
1430
|
+
editor.update(() => {
|
1431
|
+
const tableNode = $getNodeByKey(this.tableNodeKey);
|
1432
|
+
if (!$isTableNode(tableNode)) {
|
1433
|
+
throw new Error('Expected TableNode.');
|
1434
|
+
}
|
1435
|
+
const tableElement = editor.getElementByKey(this.tableNodeKey);
|
1436
|
+
if (!tableElement) {
|
1437
|
+
throw new Error('Expected to find TableElement in DOM');
|
1438
|
+
}
|
1439
|
+
const cellX = cell.x;
|
1440
|
+
const cellY = cell.y;
|
1441
|
+
this.focusCell = cell;
|
1442
|
+
if (this.anchorCell !== null) {
|
1443
|
+
const domSelection = getDOMSelection(editor._window);
|
1444
|
+
// Collapse the selection
|
1445
|
+
if (domSelection) {
|
1446
|
+
domSelection.setBaseAndExtent(this.anchorCell.elem, 0, this.focusCell.elem, 0);
|
1447
|
+
}
|
1448
|
+
}
|
1449
|
+
if (!this.isHighlightingCells && (this.anchorX !== cellX || this.anchorY !== cellY || ignoreStart)) {
|
1450
|
+
this.isHighlightingCells = true;
|
1451
|
+
this.disableHighlightStyle();
|
1452
|
+
} else if (cellX === this.focusX && cellY === this.focusY) {
|
1453
|
+
return;
|
1454
|
+
}
|
1455
|
+
this.focusX = cellX;
|
1456
|
+
this.focusY = cellY;
|
1457
|
+
if (this.isHighlightingCells) {
|
1458
|
+
const focusTableCellNode = $getNearestNodeFromDOMNode(cell.elem);
|
1459
|
+
if (this.tableSelection != null && this.anchorCellNodeKey != null && $isTableCellNode(focusTableCellNode)) {
|
1460
|
+
const focusNodeKey = focusTableCellNode.getKey();
|
1461
|
+
this.tableSelection = this.tableSelection.clone() || $createTableSelection();
|
1462
|
+
this.focusCellNodeKey = focusNodeKey;
|
1463
|
+
this.tableSelection.set(this.tableNodeKey, this.anchorCellNodeKey, this.focusCellNodeKey);
|
1464
|
+
$setSelection(this.tableSelection);
|
1465
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1466
|
+
$updateDOMForSelection(editor, this.table, this.tableSelection);
|
1467
|
+
}
|
1468
|
+
}
|
1469
|
+
});
|
1470
|
+
}
|
1471
|
+
setAnchorCellForSelection(cell) {
|
1472
|
+
this.isHighlightingCells = false;
|
1473
|
+
this.anchorCell = cell;
|
1474
|
+
this.anchorX = cell.x;
|
1475
|
+
this.anchorY = cell.y;
|
1476
|
+
this.editor.update(() => {
|
1477
|
+
const anchorTableCellNode = $getNearestNodeFromDOMNode(cell.elem);
|
1478
|
+
if ($isTableCellNode(anchorTableCellNode)) {
|
1479
|
+
const anchorNodeKey = anchorTableCellNode.getKey();
|
1480
|
+
this.tableSelection = this.tableSelection != null ? this.tableSelection.clone() : $createTableSelection();
|
1481
|
+
this.anchorCellNodeKey = anchorNodeKey;
|
1482
|
+
}
|
1483
|
+
});
|
1484
|
+
}
|
1485
|
+
formatCells(type) {
|
1486
|
+
this.editor.update(() => {
|
1487
|
+
const selection = $getSelection();
|
1488
|
+
if (!$isTableSelection(selection)) {
|
1489
|
+
{
|
1490
|
+
throw Error(`Expected grid selection`);
|
1491
|
+
}
|
1492
|
+
}
|
1493
|
+
const formatSelection = $createRangeSelection();
|
1494
|
+
const anchor = formatSelection.anchor;
|
1495
|
+
const focus = formatSelection.focus;
|
1496
|
+
selection.getNodes().forEach(cellNode => {
|
1497
|
+
if ($isTableCellNode(cellNode) && cellNode.getTextContentSize() !== 0) {
|
1498
|
+
anchor.set(cellNode.getKey(), 0, 'element');
|
1499
|
+
focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
|
1500
|
+
formatSelection.formatText(type);
|
1501
|
+
}
|
1502
|
+
});
|
1503
|
+
$setSelection(selection);
|
1504
|
+
this.editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1505
|
+
});
|
1506
|
+
}
|
1507
|
+
clearText() {
|
1508
|
+
const editor = this.editor;
|
1509
|
+
editor.update(() => {
|
1510
|
+
const tableNode = $getNodeByKey(this.tableNodeKey);
|
1511
|
+
if (!$isTableNode(tableNode)) {
|
1512
|
+
throw new Error('Expected TableNode.');
|
1513
|
+
}
|
1514
|
+
const selection = $getSelection();
|
1515
|
+
if (!$isTableSelection(selection)) {
|
1516
|
+
{
|
1517
|
+
throw Error(`Expected grid selection`);
|
1518
|
+
}
|
1519
|
+
}
|
1520
|
+
const selectedNodes = selection.getNodes().filter($isTableCellNode);
|
1521
|
+
if (selectedNodes.length === this.table.columns * this.table.rows) {
|
1522
|
+
tableNode.selectPrevious();
|
1523
|
+
// Delete entire table
|
1524
|
+
tableNode.remove();
|
1525
|
+
const rootNode = $getRoot();
|
1526
|
+
rootNode.selectStart();
|
1527
|
+
return;
|
1528
|
+
}
|
1529
|
+
selectedNodes.forEach(cellNode => {
|
1530
|
+
if ($isElementNode(cellNode)) {
|
1531
|
+
const paragraphNode = $createParagraphNode();
|
1532
|
+
const textNode = $createTextNode();
|
1533
|
+
paragraphNode.append(textNode);
|
1534
|
+
cellNode.append(paragraphNode);
|
1535
|
+
cellNode.getChildren().forEach(child => {
|
1536
|
+
if (child !== paragraphNode) {
|
1537
|
+
child.remove();
|
1538
|
+
}
|
1539
|
+
});
|
1540
|
+
}
|
1541
|
+
});
|
1542
|
+
$updateDOMForSelection(editor, this.table, null);
|
1543
|
+
$setSelection(null);
|
1544
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
1545
|
+
});
|
1546
|
+
}
|
1547
|
+
}
|
1548
|
+
|
1549
|
+
/**
|
1550
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
1551
|
+
*
|
1552
|
+
* This source code is licensed under the MIT license found in the
|
1553
|
+
* LICENSE file in the root directory of this source tree.
|
1554
|
+
*
|
1555
|
+
*/
|
1556
|
+
const LEXICAL_ELEMENT_KEY = '__lexicalTableSelection';
|
1557
|
+
function applyTableHandlers(tableNode, tableElement, editor, hasTabHandler) {
|
1558
|
+
const rootElement = editor.getRootElement();
|
1559
|
+
if (rootElement === null) {
|
1560
|
+
throw new Error('No root element.');
|
1561
|
+
}
|
1562
|
+
const tableObserver = new TableObserver(editor, tableNode.getKey());
|
1563
|
+
const editorWindow = editor._window || window;
|
1564
|
+
attachTableObserverToTableElement(tableElement, tableObserver);
|
1565
|
+
tableElement.addEventListener('mousedown', event => {
|
1566
|
+
setTimeout(() => {
|
1567
|
+
if (event.button !== 0) {
|
1568
|
+
return;
|
1569
|
+
}
|
1570
|
+
if (!editorWindow) {
|
1571
|
+
return;
|
1572
|
+
}
|
1573
|
+
const anchorCell = getDOMCellFromTarget(event.target);
|
1574
|
+
if (anchorCell !== null) {
|
1575
|
+
stopEvent(event);
|
1576
|
+
tableObserver.setAnchorCellForSelection(anchorCell);
|
1577
|
+
}
|
1578
|
+
const onMouseUp = () => {
|
1579
|
+
editorWindow.removeEventListener('mouseup', onMouseUp);
|
1580
|
+
editorWindow.removeEventListener('mousemove', onMouseMove);
|
1581
|
+
};
|
1582
|
+
const onMouseMove = moveEvent => {
|
1583
|
+
const focusCell = getDOMCellFromTarget(moveEvent.target);
|
1584
|
+
if (focusCell !== null && (tableObserver.anchorX !== focusCell.x || tableObserver.anchorY !== focusCell.y)) {
|
1585
|
+
moveEvent.preventDefault();
|
1586
|
+
tableObserver.setFocusCellForSelection(focusCell);
|
1587
|
+
}
|
1588
|
+
};
|
1589
|
+
editorWindow.addEventListener('mouseup', onMouseUp);
|
1590
|
+
editorWindow.addEventListener('mousemove', onMouseMove);
|
1591
|
+
}, 0);
|
1592
|
+
});
|
1593
|
+
|
1594
|
+
// Clear selection when clicking outside of dom.
|
1595
|
+
const mouseDownCallback = event => {
|
1596
|
+
if (event.button !== 0) {
|
1597
|
+
return;
|
1598
|
+
}
|
1599
|
+
editor.update(() => {
|
1600
|
+
const selection = $getSelection();
|
1601
|
+
const target = event.target;
|
1602
|
+
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey && rootElement.contains(target)) {
|
1603
|
+
tableObserver.clearHighlight();
|
1604
|
+
}
|
1605
|
+
});
|
1606
|
+
};
|
1607
|
+
editorWindow.addEventListener('mousedown', mouseDownCallback);
|
1608
|
+
tableObserver.listenersToRemove.add(() => editorWindow.removeEventListener('mousedown', mouseDownCallback));
|
1609
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ARROW_DOWN_COMMAND, event => $handleArrowKey(editor, event, 'down', tableNode, tableObserver), COMMAND_PRIORITY_HIGH));
|
1610
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ARROW_UP_COMMAND, event => $handleArrowKey(editor, event, 'up', tableNode, tableObserver), COMMAND_PRIORITY_HIGH));
|
1611
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ARROW_LEFT_COMMAND, event => $handleArrowKey(editor, event, 'backward', tableNode, tableObserver), COMMAND_PRIORITY_HIGH));
|
1612
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, event => $handleArrowKey(editor, event, 'forward', tableNode, tableObserver), COMMAND_PRIORITY_HIGH));
|
1613
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_ESCAPE_COMMAND, event => {
|
1614
|
+
const selection = $getSelection();
|
1615
|
+
if ($isTableSelection(selection)) {
|
1616
|
+
const focusCellNode = $findMatchingParent(selection.focus.getNode(), $isTableCellNode);
|
1617
|
+
if ($isTableCellNode(focusCellNode)) {
|
1618
|
+
stopEvent(event);
|
1619
|
+
focusCellNode.selectEnd();
|
1620
|
+
return true;
|
1621
|
+
}
|
1622
|
+
}
|
1623
|
+
return false;
|
1624
|
+
}, COMMAND_PRIORITY_HIGH));
|
1625
|
+
const deleteTextHandler = command => () => {
|
1626
|
+
const selection = $getSelection();
|
1627
|
+
if (!$isSelectionInTable(selection, tableNode)) {
|
1628
|
+
return false;
|
1629
|
+
}
|
1630
|
+
if ($isTableSelection(selection)) {
|
1631
|
+
tableObserver.clearText();
|
1632
|
+
return true;
|
1633
|
+
} else if ($isRangeSelection(selection)) {
|
1634
|
+
const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
1635
|
+
if (!$isTableCellNode(tableCellNode)) {
|
1636
|
+
return false;
|
1637
|
+
}
|
1638
|
+
const anchorNode = selection.anchor.getNode();
|
1639
|
+
const focusNode = selection.focus.getNode();
|
1640
|
+
const isAnchorInside = tableNode.isParentOf(anchorNode);
|
1641
|
+
const isFocusInside = tableNode.isParentOf(focusNode);
|
1642
|
+
const selectionContainsPartialTable = isAnchorInside && !isFocusInside || isFocusInside && !isAnchorInside;
|
1643
|
+
if (selectionContainsPartialTable) {
|
1644
|
+
tableObserver.clearText();
|
1645
|
+
return true;
|
1646
|
+
}
|
1647
|
+
const nearestElementNode = $findMatchingParent(selection.anchor.getNode(), n => $isElementNode(n));
|
1648
|
+
const topLevelCellElementNode = nearestElementNode && $findMatchingParent(nearestElementNode, n => $isElementNode(n) && $isTableCellNode(n.getParent()));
|
1649
|
+
if (!$isElementNode(topLevelCellElementNode) || !$isElementNode(nearestElementNode)) {
|
1650
|
+
return false;
|
1651
|
+
}
|
1652
|
+
if (command === DELETE_LINE_COMMAND && topLevelCellElementNode.getPreviousSibling() === null) {
|
1653
|
+
// TODO: Fix Delete Line in Table Cells.
|
1654
|
+
return true;
|
1655
|
+
}
|
1656
|
+
if (command === DELETE_CHARACTER_COMMAND || command === DELETE_WORD_COMMAND) {
|
1657
|
+
if (selection.isCollapsed() && selection.anchor.offset === 0) {
|
1658
|
+
if (nearestElementNode !== topLevelCellElementNode) {
|
1659
|
+
const children = nearestElementNode.getChildren();
|
1660
|
+
const newParagraphNode = $createParagraphNode();
|
1661
|
+
children.forEach(child => newParagraphNode.append(child));
|
1662
|
+
nearestElementNode.replace(newParagraphNode);
|
1663
|
+
nearestElementNode.getWritable().__parent = tableCellNode.getKey();
|
1664
|
+
return true;
|
1665
|
+
}
|
1666
|
+
}
|
1667
|
+
}
|
1668
|
+
}
|
1669
|
+
return false;
|
1670
|
+
};
|
1671
|
+
[DELETE_WORD_COMMAND, DELETE_LINE_COMMAND, DELETE_CHARACTER_COMMAND].forEach(command => {
|
1672
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(command, deleteTextHandler(command), COMMAND_PRIORITY_CRITICAL));
|
1673
|
+
});
|
1674
|
+
const deleteCellHandler = event => {
|
1675
|
+
const selection = $getSelection();
|
1676
|
+
if (!$isSelectionInTable(selection, tableNode)) {
|
1677
|
+
return false;
|
1678
|
+
}
|
1679
|
+
if ($isTableSelection(selection)) {
|
1680
|
+
event.preventDefault();
|
1681
|
+
event.stopPropagation();
|
1682
|
+
tableObserver.clearText();
|
1683
|
+
return true;
|
1684
|
+
} else if ($isRangeSelection(selection)) {
|
1685
|
+
const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
1686
|
+
if (!$isTableCellNode(tableCellNode)) {
|
1687
|
+
return false;
|
1688
|
+
}
|
1689
|
+
}
|
1690
|
+
return false;
|
1691
|
+
};
|
1692
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_BACKSPACE_COMMAND, deleteCellHandler, COMMAND_PRIORITY_CRITICAL));
|
1693
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_DELETE_COMMAND, deleteCellHandler, COMMAND_PRIORITY_CRITICAL));
|
1694
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(FORMAT_TEXT_COMMAND, payload => {
|
1695
|
+
const selection = $getSelection();
|
1696
|
+
if (!$isSelectionInTable(selection, tableNode)) {
|
1697
|
+
return false;
|
1698
|
+
}
|
1699
|
+
if ($isTableSelection(selection)) {
|
1700
|
+
tableObserver.formatCells(payload);
|
1701
|
+
return true;
|
1702
|
+
} else if ($isRangeSelection(selection)) {
|
1703
|
+
const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
1704
|
+
if (!$isTableCellNode(tableCellNode)) {
|
1705
|
+
return false;
|
1706
|
+
}
|
1707
|
+
}
|
1708
|
+
return false;
|
1709
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1710
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(FORMAT_ELEMENT_COMMAND, formatType => {
|
1711
|
+
const selection = $getSelection();
|
1712
|
+
if (!$isTableSelection(selection) || !$isSelectionInTable(selection, tableNode)) {
|
1713
|
+
return false;
|
1714
|
+
}
|
1715
|
+
const anchorNode = selection.anchor.getNode();
|
1716
|
+
const focusNode = selection.focus.getNode();
|
1717
|
+
if (!$isTableCellNode(anchorNode) || !$isTableCellNode(focusNode)) {
|
1718
|
+
return false;
|
1719
|
+
}
|
1720
|
+
const [tableMap, anchorCell, focusCell] = $computeTableMap(tableNode, anchorNode, focusNode);
|
1721
|
+
const maxRow = Math.max(anchorCell.startRow, focusCell.startRow);
|
1722
|
+
const maxColumn = Math.max(anchorCell.startColumn, focusCell.startColumn);
|
1723
|
+
const minRow = Math.min(anchorCell.startRow, focusCell.startRow);
|
1724
|
+
const minColumn = Math.min(anchorCell.startColumn, focusCell.startColumn);
|
1725
|
+
for (let i = minRow; i <= maxRow; i++) {
|
1726
|
+
for (let j = minColumn; j <= maxColumn; j++) {
|
1727
|
+
const cell = tableMap[i][j].cell;
|
1728
|
+
cell.setFormat(formatType);
|
1729
|
+
const cellChildren = cell.getChildren();
|
1730
|
+
for (let k = 0; k < cellChildren.length; k++) {
|
1731
|
+
const child = cellChildren[k];
|
1732
|
+
if ($isElementNode(child) && !child.isInline()) {
|
1733
|
+
child.setFormat(formatType);
|
1734
|
+
}
|
1735
|
+
}
|
1736
|
+
}
|
1737
|
+
}
|
1738
|
+
return true;
|
1739
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1740
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(CONTROLLED_TEXT_INSERTION_COMMAND, payload => {
|
1741
|
+
const selection = $getSelection();
|
1742
|
+
if (!$isSelectionInTable(selection, tableNode)) {
|
1743
|
+
return false;
|
1744
|
+
}
|
1745
|
+
if ($isTableSelection(selection)) {
|
1746
|
+
tableObserver.clearHighlight();
|
1747
|
+
return false;
|
1748
|
+
} else if ($isRangeSelection(selection)) {
|
1749
|
+
const tableCellNode = $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n));
|
1750
|
+
if (!$isTableCellNode(tableCellNode)) {
|
1751
|
+
return false;
|
1752
|
+
}
|
1753
|
+
}
|
1754
|
+
return false;
|
1755
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1756
|
+
if (hasTabHandler) {
|
1757
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(KEY_TAB_COMMAND, event => {
|
1758
|
+
const selection = $getSelection();
|
1759
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed() || !$isSelectionInTable(selection, tableNode)) {
|
1760
|
+
return false;
|
1761
|
+
}
|
1762
|
+
const tableCellNode = $findCellNode(selection.anchor.getNode());
|
1763
|
+
if (tableCellNode === null) {
|
1764
|
+
return false;
|
1765
|
+
}
|
1766
|
+
stopEvent(event);
|
1767
|
+
const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
|
1768
|
+
selectTableNodeInDirection(tableObserver, tableNode, currentCords.x, currentCords.y, !event.shiftKey ? 'forward' : 'backward');
|
1769
|
+
return true;
|
1770
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1771
|
+
}
|
1772
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(FOCUS_COMMAND, payload => {
|
1773
|
+
return tableNode.isSelected();
|
1774
|
+
}, COMMAND_PRIORITY_HIGH));
|
1775
|
+
function getObserverCellFromCellNode(tableCellNode) {
|
1776
|
+
const currentCords = tableNode.getCordsFromCellNode(tableCellNode, tableObserver.table);
|
1777
|
+
return tableNode.getDOMCellFromCordsOrThrow(currentCords.x, currentCords.y, tableObserver.table);
|
1778
|
+
}
|
1779
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, selectionPayload => {
|
1780
|
+
const {
|
1781
|
+
nodes,
|
1782
|
+
selection
|
1783
|
+
} = selectionPayload;
|
1784
|
+
const anchorAndFocus = selection.getStartEndPoints();
|
1785
|
+
const isTableSelection = $isTableSelection(selection);
|
1786
|
+
const isRangeSelection = $isRangeSelection(selection);
|
1787
|
+
const isSelectionInsideOfGrid = isRangeSelection && $findMatchingParent(selection.anchor.getNode(), n => $isTableCellNode(n)) !== null && $findMatchingParent(selection.focus.getNode(), n => $isTableCellNode(n)) !== null || isTableSelection;
|
1788
|
+
if (nodes.length !== 1 || !$isTableNode(nodes[0]) || !isSelectionInsideOfGrid || anchorAndFocus === null) {
|
1789
|
+
return false;
|
1790
|
+
}
|
1791
|
+
const [anchor] = anchorAndFocus;
|
1792
|
+
const newGrid = nodes[0];
|
1793
|
+
const newGridRows = newGrid.getChildren();
|
1794
|
+
const newColumnCount = newGrid.getFirstChildOrThrow().getChildrenSize();
|
1795
|
+
const newRowCount = newGrid.getChildrenSize();
|
1796
|
+
const gridCellNode = $findMatchingParent(anchor.getNode(), n => $isTableCellNode(n));
|
1797
|
+
const gridRowNode = gridCellNode && $findMatchingParent(gridCellNode, n => $isTableRowNode(n));
|
1798
|
+
const gridNode = gridRowNode && $findMatchingParent(gridRowNode, n => $isTableNode(n));
|
1799
|
+
if (!$isTableCellNode(gridCellNode) || !$isTableRowNode(gridRowNode) || !$isTableNode(gridNode)) {
|
1800
|
+
return false;
|
1801
|
+
}
|
1802
|
+
const startY = gridRowNode.getIndexWithinParent();
|
1803
|
+
const stopY = Math.min(gridNode.getChildrenSize() - 1, startY + newRowCount - 1);
|
1804
|
+
const startX = gridCellNode.getIndexWithinParent();
|
1805
|
+
const stopX = Math.min(gridRowNode.getChildrenSize() - 1, startX + newColumnCount - 1);
|
1806
|
+
const fromX = Math.min(startX, stopX);
|
1807
|
+
const fromY = Math.min(startY, stopY);
|
1808
|
+
const toX = Math.max(startX, stopX);
|
1809
|
+
const toY = Math.max(startY, stopY);
|
1810
|
+
const gridRowNodes = gridNode.getChildren();
|
1811
|
+
let newRowIdx = 0;
|
1812
|
+
let newAnchorCellKey;
|
1813
|
+
let newFocusCellKey;
|
1814
|
+
for (let r = fromY; r <= toY; r++) {
|
1815
|
+
const currentGridRowNode = gridRowNodes[r];
|
1816
|
+
if (!$isTableRowNode(currentGridRowNode)) {
|
1817
|
+
return false;
|
1818
|
+
}
|
1819
|
+
const newGridRowNode = newGridRows[newRowIdx];
|
1820
|
+
if (!$isTableRowNode(newGridRowNode)) {
|
1821
|
+
return false;
|
1822
|
+
}
|
1823
|
+
const gridCellNodes = currentGridRowNode.getChildren();
|
1824
|
+
const newGridCellNodes = newGridRowNode.getChildren();
|
1825
|
+
let newColumnIdx = 0;
|
1826
|
+
for (let c = fromX; c <= toX; c++) {
|
1827
|
+
const currentGridCellNode = gridCellNodes[c];
|
1828
|
+
if (!$isTableCellNode(currentGridCellNode)) {
|
1829
|
+
return false;
|
1830
|
+
}
|
1831
|
+
const newGridCellNode = newGridCellNodes[newColumnIdx];
|
1832
|
+
if (!$isTableCellNode(newGridCellNode)) {
|
1833
|
+
return false;
|
1834
|
+
}
|
1835
|
+
if (r === fromY && c === fromX) {
|
1836
|
+
newAnchorCellKey = currentGridCellNode.getKey();
|
1837
|
+
} else if (r === toY && c === toX) {
|
1838
|
+
newFocusCellKey = currentGridCellNode.getKey();
|
1839
|
+
}
|
1840
|
+
const originalChildren = currentGridCellNode.getChildren();
|
1841
|
+
newGridCellNode.getChildren().forEach(child => {
|
1842
|
+
if ($isTextNode(child)) {
|
1843
|
+
const paragraphNode = $createParagraphNode();
|
1844
|
+
paragraphNode.append(child);
|
1845
|
+
currentGridCellNode.append(child);
|
1846
|
+
} else {
|
1847
|
+
currentGridCellNode.append(child);
|
1848
|
+
}
|
1849
|
+
});
|
1850
|
+
originalChildren.forEach(n => n.remove());
|
1851
|
+
newColumnIdx++;
|
1852
|
+
}
|
1853
|
+
newRowIdx++;
|
1854
|
+
}
|
1855
|
+
if (newAnchorCellKey && newFocusCellKey) {
|
1856
|
+
const newTableSelection = $createTableSelection();
|
1857
|
+
newTableSelection.set(nodes[0].getKey(), newAnchorCellKey, newFocusCellKey);
|
1858
|
+
$setSelection(newTableSelection);
|
1859
|
+
}
|
1860
|
+
return true;
|
1861
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1862
|
+
tableObserver.listenersToRemove.add(editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
1863
|
+
const selection = $getSelection();
|
1864
|
+
const prevSelection = $getPreviousSelection();
|
1865
|
+
if ($isRangeSelection(selection)) {
|
1866
|
+
const {
|
1867
|
+
anchor,
|
1868
|
+
focus
|
1869
|
+
} = selection;
|
1870
|
+
const anchorNode = anchor.getNode();
|
1871
|
+
const focusNode = focus.getNode();
|
1872
|
+
// Using explicit comparison with table node to ensure it's not a nested table
|
1873
|
+
// as in that case we'll leave selection resolving to that table
|
1874
|
+
const anchorCellNode = $findCellNode(anchorNode);
|
1875
|
+
const focusCellNode = $findCellNode(focusNode);
|
1876
|
+
const isAnchorInside = !!(anchorCellNode && tableNode.is($findTableNode(anchorCellNode)));
|
1877
|
+
const isFocusInside = !!(focusCellNode && tableNode.is($findTableNode(focusCellNode)));
|
1878
|
+
const isPartialyWithinTable = isAnchorInside !== isFocusInside;
|
1879
|
+
const isWithinTable = isAnchorInside && isFocusInside;
|
1880
|
+
const isBackward = selection.isBackward();
|
1881
|
+
if (isPartialyWithinTable) {
|
1882
|
+
const newSelection = selection.clone();
|
1883
|
+
newSelection.focus.set(tableNode.getKey(), isBackward ? 0 : tableNode.getChildrenSize(), 'element');
|
1884
|
+
$setSelection(newSelection);
|
1885
|
+
$addHighlightStyleToTable(editor, tableObserver);
|
1886
|
+
} else if (isWithinTable) {
|
1887
|
+
// Handle case when selection spans across multiple cells but still
|
1888
|
+
// has range selection, then we convert it into grid selection
|
1889
|
+
if (!anchorCellNode.is(focusCellNode)) {
|
1890
|
+
tableObserver.setAnchorCellForSelection(getObserverCellFromCellNode(anchorCellNode));
|
1891
|
+
tableObserver.setFocusCellForSelection(getObserverCellFromCellNode(focusCellNode), true);
|
1892
|
+
}
|
1893
|
+
}
|
1894
|
+
}
|
1895
|
+
if (selection && !selection.is(prevSelection) && ($isTableSelection(selection) || $isTableSelection(prevSelection)) && tableObserver.tableSelection && !tableObserver.tableSelection.is(prevSelection)) {
|
1896
|
+
if ($isTableSelection(selection) && selection.tableKey === tableObserver.tableNodeKey) {
|
1897
|
+
tableObserver.updateTableTableSelection(selection);
|
1898
|
+
} else if (!$isTableSelection(selection) && $isTableSelection(prevSelection) && prevSelection.tableKey === tableObserver.tableNodeKey) {
|
1899
|
+
tableObserver.updateTableTableSelection(null);
|
1900
|
+
}
|
1901
|
+
return false;
|
1902
|
+
}
|
1903
|
+
if (tableObserver.hasHijackedSelectionStyles && !tableNode.isSelected()) {
|
1904
|
+
$removeHighlightStyleToTable(editor, tableObserver);
|
1905
|
+
} else if (!tableObserver.hasHijackedSelectionStyles && tableNode.isSelected()) {
|
1906
|
+
$addHighlightStyleToTable(editor, tableObserver);
|
1907
|
+
}
|
1908
|
+
return false;
|
1909
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
1910
|
+
return tableObserver;
|
1911
|
+
}
|
1912
|
+
function attachTableObserverToTableElement(tableElement, tableObserver) {
|
1913
|
+
tableElement[LEXICAL_ELEMENT_KEY] = tableObserver;
|
1914
|
+
}
|
1915
|
+
function getTableObserverFromTableElement(tableElement) {
|
1916
|
+
return tableElement[LEXICAL_ELEMENT_KEY];
|
1917
|
+
}
|
1918
|
+
function getDOMCellFromTarget(node) {
|
1919
|
+
let currentNode = node;
|
1920
|
+
while (currentNode != null) {
|
1921
|
+
const nodeName = currentNode.nodeName;
|
1922
|
+
if (nodeName === 'TD' || nodeName === 'TH') {
|
1923
|
+
// @ts-expect-error: internal field
|
1924
|
+
const cell = currentNode._cell;
|
1925
|
+
if (cell === undefined) {
|
1926
|
+
return null;
|
1927
|
+
}
|
1928
|
+
return cell;
|
1929
|
+
}
|
1930
|
+
currentNode = currentNode.parentNode;
|
1931
|
+
}
|
1932
|
+
return null;
|
1933
|
+
}
|
1934
|
+
function getTable(tableElement) {
|
1935
|
+
const domRows = [];
|
1936
|
+
const grid = {
|
1937
|
+
columns: 0,
|
1938
|
+
domRows,
|
1939
|
+
rows: 0
|
1940
|
+
};
|
1941
|
+
let currentNode = tableElement.firstChild;
|
1942
|
+
let x = 0;
|
1943
|
+
let y = 0;
|
1944
|
+
domRows.length = 0;
|
1945
|
+
while (currentNode != null) {
|
1946
|
+
const nodeMame = currentNode.nodeName;
|
1947
|
+
if (nodeMame === 'TD' || nodeMame === 'TH') {
|
1948
|
+
const elem = currentNode;
|
1949
|
+
const cell = {
|
1950
|
+
elem,
|
1951
|
+
hasBackgroundColor: elem.style.backgroundColor !== '',
|
1952
|
+
highlighted: false,
|
1953
|
+
x,
|
1954
|
+
y
|
1955
|
+
};
|
1956
|
+
|
1957
|
+
// @ts-expect-error: internal field
|
1958
|
+
currentNode._cell = cell;
|
1959
|
+
let row = domRows[y];
|
1960
|
+
if (row === undefined) {
|
1961
|
+
row = domRows[y] = [];
|
1962
|
+
}
|
1963
|
+
row[x] = cell;
|
1964
|
+
} else {
|
1965
|
+
const child = currentNode.firstChild;
|
1966
|
+
if (child != null) {
|
1967
|
+
currentNode = child;
|
1968
|
+
continue;
|
1969
|
+
}
|
1970
|
+
}
|
1971
|
+
const sibling = currentNode.nextSibling;
|
1972
|
+
if (sibling != null) {
|
1973
|
+
x++;
|
1974
|
+
currentNode = sibling;
|
1975
|
+
continue;
|
1976
|
+
}
|
1977
|
+
const parent = currentNode.parentNode;
|
1978
|
+
if (parent != null) {
|
1979
|
+
const parentSibling = parent.nextSibling;
|
1980
|
+
if (parentSibling == null) {
|
1981
|
+
break;
|
1982
|
+
}
|
1983
|
+
y++;
|
1984
|
+
x = 0;
|
1985
|
+
currentNode = parentSibling;
|
1986
|
+
}
|
1987
|
+
}
|
1988
|
+
grid.columns = x + 1;
|
1989
|
+
grid.rows = y + 1;
|
1990
|
+
return grid;
|
1991
|
+
}
|
1992
|
+
function $updateDOMForSelection(editor, table, selection) {
|
1993
|
+
const selectedCellNodes = new Set(selection ? selection.getNodes() : []);
|
1994
|
+
$forEachTableCell(table, (cell, lexicalNode) => {
|
1995
|
+
const elem = cell.elem;
|
1996
|
+
if (selectedCellNodes.has(lexicalNode)) {
|
1997
|
+
cell.highlighted = true;
|
1998
|
+
$addHighlightToDOM(editor, cell);
|
1999
|
+
} else {
|
2000
|
+
cell.highlighted = false;
|
2001
|
+
$removeHighlightFromDOM(editor, cell);
|
2002
|
+
if (!elem.getAttribute('style')) {
|
2003
|
+
elem.removeAttribute('style');
|
2004
|
+
}
|
2005
|
+
}
|
2006
|
+
});
|
2007
|
+
}
|
2008
|
+
function $forEachTableCell(grid, cb) {
|
2009
|
+
const {
|
2010
|
+
domRows
|
2011
|
+
} = grid;
|
2012
|
+
for (let y = 0; y < domRows.length; y++) {
|
2013
|
+
const row = domRows[y];
|
2014
|
+
if (!row) {
|
2015
|
+
continue;
|
2016
|
+
}
|
2017
|
+
for (let x = 0; x < row.length; x++) {
|
2018
|
+
const cell = row[x];
|
2019
|
+
if (!cell) {
|
2020
|
+
continue;
|
2021
|
+
}
|
2022
|
+
const lexicalNode = $getNearestNodeFromDOMNode(cell.elem);
|
2023
|
+
if (lexicalNode !== null) {
|
2024
|
+
cb(cell, lexicalNode, {
|
2025
|
+
x,
|
2026
|
+
y
|
2027
|
+
});
|
2028
|
+
}
|
2029
|
+
}
|
2030
|
+
}
|
2031
|
+
}
|
2032
|
+
function $addHighlightStyleToTable(editor, tableSelection) {
|
2033
|
+
tableSelection.disableHighlightStyle();
|
2034
|
+
$forEachTableCell(tableSelection.table, cell => {
|
2035
|
+
cell.highlighted = true;
|
2036
|
+
$addHighlightToDOM(editor, cell);
|
2037
|
+
});
|
2038
|
+
}
|
2039
|
+
function $removeHighlightStyleToTable(editor, tableObserver) {
|
2040
|
+
tableObserver.enableHighlightStyle();
|
2041
|
+
$forEachTableCell(tableObserver.table, cell => {
|
2042
|
+
const elem = cell.elem;
|
2043
|
+
cell.highlighted = false;
|
2044
|
+
$removeHighlightFromDOM(editor, cell);
|
2045
|
+
if (!elem.getAttribute('style')) {
|
2046
|
+
elem.removeAttribute('style');
|
2047
|
+
}
|
2048
|
+
});
|
2049
|
+
}
|
2050
|
+
const selectTableNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
|
2051
|
+
const isForward = direction === 'forward';
|
2052
|
+
switch (direction) {
|
2053
|
+
case 'backward':
|
2054
|
+
case 'forward':
|
2055
|
+
if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
|
2056
|
+
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table), isForward);
|
2057
|
+
} else {
|
2058
|
+
if (y !== (isForward ? tableObserver.table.rows - 1 : 0)) {
|
2059
|
+
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(isForward ? 0 : tableObserver.table.columns - 1, y + (isForward ? 1 : -1), tableObserver.table), isForward);
|
2060
|
+
} else if (!isForward) {
|
2061
|
+
tableNode.selectPrevious();
|
2062
|
+
} else {
|
2063
|
+
tableNode.selectNext();
|
2064
|
+
}
|
2065
|
+
}
|
2066
|
+
return true;
|
2067
|
+
case 'up':
|
2068
|
+
if (y !== 0) {
|
2069
|
+
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y - 1, tableObserver.table), false);
|
2070
|
+
} else {
|
2071
|
+
tableNode.selectPrevious();
|
2072
|
+
}
|
2073
|
+
return true;
|
2074
|
+
case 'down':
|
2075
|
+
if (y !== tableObserver.table.rows - 1) {
|
2076
|
+
selectTableCellNode(tableNode.getCellNodeFromCordsOrThrow(x, y + 1, tableObserver.table), true);
|
2077
|
+
} else {
|
2078
|
+
tableNode.selectNext();
|
2079
|
+
}
|
2080
|
+
return true;
|
2081
|
+
default:
|
2082
|
+
return false;
|
2083
|
+
}
|
2084
|
+
};
|
2085
|
+
const adjustFocusNodeInDirection = (tableObserver, tableNode, x, y, direction) => {
|
2086
|
+
const isForward = direction === 'forward';
|
2087
|
+
switch (direction) {
|
2088
|
+
case 'backward':
|
2089
|
+
case 'forward':
|
2090
|
+
if (x !== (isForward ? tableObserver.table.columns - 1 : 0)) {
|
2091
|
+
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x + (isForward ? 1 : -1), y, tableObserver.table));
|
2092
|
+
}
|
2093
|
+
return true;
|
2094
|
+
case 'up':
|
2095
|
+
if (y !== 0) {
|
2096
|
+
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y - 1, tableObserver.table));
|
2097
|
+
return true;
|
2098
|
+
} else {
|
2099
|
+
return false;
|
2100
|
+
}
|
2101
|
+
case 'down':
|
2102
|
+
if (y !== tableObserver.table.rows - 1) {
|
2103
|
+
tableObserver.setFocusCellForSelection(tableNode.getDOMCellFromCordsOrThrow(x, y + 1, tableObserver.table));
|
2104
|
+
return true;
|
2105
|
+
} else {
|
2106
|
+
return false;
|
2107
|
+
}
|
2108
|
+
default:
|
2109
|
+
return false;
|
2110
|
+
}
|
2111
|
+
};
|
2112
|
+
function $isSelectionInTable(selection, tableNode) {
|
2113
|
+
if ($isRangeSelection(selection) || $isTableSelection(selection)) {
|
2114
|
+
const isAnchorInside = tableNode.isParentOf(selection.anchor.getNode());
|
2115
|
+
const isFocusInside = tableNode.isParentOf(selection.focus.getNode());
|
2116
|
+
return isAnchorInside && isFocusInside;
|
2117
|
+
}
|
2118
|
+
return false;
|
2119
|
+
}
|
2120
|
+
function selectTableCellNode(tableCell, fromStart) {
|
2121
|
+
if (fromStart) {
|
2122
|
+
tableCell.selectStart();
|
2123
|
+
} else {
|
2124
|
+
tableCell.selectEnd();
|
2125
|
+
}
|
2126
|
+
}
|
2127
|
+
const BROWSER_BLUE_RGB = '172,206,247';
|
2128
|
+
function $addHighlightToDOM(editor, cell) {
|
2129
|
+
const element = cell.elem;
|
2130
|
+
const node = $getNearestNodeFromDOMNode(element);
|
2131
|
+
if (!$isTableCellNode(node)) {
|
2132
|
+
throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
|
2133
|
+
}
|
2134
|
+
const backgroundColor = node.getBackgroundColor();
|
2135
|
+
if (backgroundColor === null) {
|
2136
|
+
element.style.setProperty('background-color', `rgb(${BROWSER_BLUE_RGB})`);
|
2137
|
+
} else {
|
2138
|
+
element.style.setProperty('background-image', `linear-gradient(to right, rgba(${BROWSER_BLUE_RGB},0.85), rgba(${BROWSER_BLUE_RGB},0.85))`);
|
2139
|
+
}
|
2140
|
+
element.style.setProperty('caret-color', 'transparent');
|
2141
|
+
}
|
2142
|
+
function $removeHighlightFromDOM(editor, cell) {
|
2143
|
+
const element = cell.elem;
|
2144
|
+
const node = $getNearestNodeFromDOMNode(element);
|
2145
|
+
if (!$isTableCellNode(node)) {
|
2146
|
+
throw Error(`Expected to find LexicalNode from Table Cell DOMNode`);
|
2147
|
+
}
|
2148
|
+
const backgroundColor = node.getBackgroundColor();
|
2149
|
+
if (backgroundColor === null) {
|
2150
|
+
element.style.removeProperty('background-color');
|
2151
|
+
}
|
2152
|
+
element.style.removeProperty('background-image');
|
2153
|
+
element.style.removeProperty('caret-color');
|
2154
|
+
}
|
2155
|
+
function $findCellNode(node) {
|
2156
|
+
const cellNode = $findMatchingParent(node, $isTableCellNode);
|
2157
|
+
return $isTableCellNode(cellNode) ? cellNode : null;
|
2158
|
+
}
|
2159
|
+
function $findTableNode(node) {
|
2160
|
+
const tableNode = $findMatchingParent(node, $isTableNode);
|
2161
|
+
return $isTableNode(tableNode) ? tableNode : null;
|
2162
|
+
}
|
2163
|
+
function $handleArrowKey(editor, event, direction, tableNode, tableObserver) {
|
2164
|
+
const selection = $getSelection();
|
2165
|
+
if (!$isSelectionInTable(selection, tableNode)) {
|
2166
|
+
return false;
|
2167
|
+
}
|
2168
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
2169
|
+
// Horizontal move between cels seem to work well without interruption
|
2170
|
+
// so just exit early, and handle vertical moves
|
2171
|
+
if (direction === 'backward' || direction === 'forward') {
|
2172
|
+
return false;
|
2173
|
+
}
|
2174
|
+
const {
|
2175
|
+
anchor,
|
2176
|
+
focus
|
2177
|
+
} = selection;
|
2178
|
+
const anchorCellNode = $findMatchingParent(anchor.getNode(), $isTableCellNode);
|
2179
|
+
const focusCellNode = $findMatchingParent(focus.getNode(), $isTableCellNode);
|
2180
|
+
if (!$isTableCellNode(anchorCellNode) || !anchorCellNode.is(focusCellNode)) {
|
2181
|
+
return false;
|
2182
|
+
}
|
2183
|
+
const anchorCellTable = $findTableNode(anchorCellNode);
|
2184
|
+
if (anchorCellTable !== tableNode && anchorCellTable != null) {
|
2185
|
+
const anchorCellTableElement = editor.getElementByKey(anchorCellTable.getKey());
|
2186
|
+
if (anchorCellTableElement != null) {
|
2187
|
+
tableObserver.table = getTable(anchorCellTableElement);
|
2188
|
+
return $handleArrowKey(editor, event, direction, anchorCellTable, tableObserver);
|
2189
|
+
}
|
2190
|
+
}
|
2191
|
+
const anchorCellDom = editor.getElementByKey(anchorCellNode.__key);
|
2192
|
+
const anchorDOM = editor.getElementByKey(anchor.key);
|
2193
|
+
if (anchorDOM == null || anchorCellDom == null) {
|
2194
|
+
return false;
|
2195
|
+
}
|
2196
|
+
let edgeSelectionRect;
|
2197
|
+
if (anchor.type === 'element') {
|
2198
|
+
edgeSelectionRect = anchorDOM.getBoundingClientRect();
|
2199
|
+
} else {
|
2200
|
+
const domSelection = window.getSelection();
|
2201
|
+
if (domSelection === null || domSelection.rangeCount === 0) {
|
2202
|
+
return false;
|
2203
|
+
}
|
2204
|
+
const range = domSelection.getRangeAt(0);
|
2205
|
+
edgeSelectionRect = range.getBoundingClientRect();
|
2206
|
+
}
|
2207
|
+
const edgeChild = direction === 'up' ? anchorCellNode.getFirstChild() : anchorCellNode.getLastChild();
|
2208
|
+
if (edgeChild == null) {
|
2209
|
+
return false;
|
2210
|
+
}
|
2211
|
+
const edgeChildDOM = editor.getElementByKey(edgeChild.__key);
|
2212
|
+
if (edgeChildDOM == null) {
|
2213
|
+
return false;
|
2214
|
+
}
|
2215
|
+
const edgeRect = edgeChildDOM.getBoundingClientRect();
|
2216
|
+
const isExiting = direction === 'up' ? edgeRect.top > edgeSelectionRect.top - edgeSelectionRect.height : edgeSelectionRect.bottom + edgeSelectionRect.height > edgeRect.bottom;
|
2217
|
+
if (isExiting) {
|
2218
|
+
stopEvent(event);
|
2219
|
+
const cords = tableNode.getCordsFromCellNode(anchorCellNode, tableObserver.table);
|
2220
|
+
if (event.shiftKey) {
|
2221
|
+
const cell = tableNode.getDOMCellFromCordsOrThrow(cords.x, cords.y, tableObserver.table);
|
2222
|
+
tableObserver.setAnchorCellForSelection(cell);
|
2223
|
+
tableObserver.setFocusCellForSelection(cell, true);
|
2224
|
+
} else {
|
2225
|
+
return selectTableNodeInDirection(tableObserver, tableNode, cords.x, cords.y, direction);
|
2226
|
+
}
|
2227
|
+
return true;
|
2228
|
+
}
|
2229
|
+
} else if ($isTableSelection(selection)) {
|
2230
|
+
const {
|
2231
|
+
anchor,
|
2232
|
+
focus
|
2233
|
+
} = selection;
|
2234
|
+
const anchorCellNode = $findMatchingParent(anchor.getNode(), $isTableCellNode);
|
2235
|
+
const focusCellNode = $findMatchingParent(focus.getNode(), $isTableCellNode);
|
2236
|
+
const [tableNodeFromSelection] = selection.getNodes();
|
2237
|
+
const tableElement = editor.getElementByKey(tableNodeFromSelection.getKey());
|
2238
|
+
if (!$isTableCellNode(anchorCellNode) || !$isTableCellNode(focusCellNode) || !$isTableNode(tableNodeFromSelection) || tableElement == null) {
|
2239
|
+
return false;
|
2240
|
+
}
|
2241
|
+
tableObserver.updateTableTableSelection(selection);
|
2242
|
+
const grid = getTable(tableElement);
|
2243
|
+
const cordsAnchor = tableNode.getCordsFromCellNode(anchorCellNode, grid);
|
2244
|
+
const anchorCell = tableNode.getDOMCellFromCordsOrThrow(cordsAnchor.x, cordsAnchor.y, grid);
|
2245
|
+
tableObserver.setAnchorCellForSelection(anchorCell);
|
2246
|
+
stopEvent(event);
|
2247
|
+
if (event.shiftKey) {
|
2248
|
+
const cords = tableNode.getCordsFromCellNode(focusCellNode, grid);
|
2249
|
+
return adjustFocusNodeInDirection(tableObserver, tableNodeFromSelection, cords.x, cords.y, direction);
|
2250
|
+
} else {
|
2251
|
+
focusCellNode.selectEnd();
|
2252
|
+
}
|
2253
|
+
return true;
|
2254
|
+
}
|
2255
|
+
return false;
|
2256
|
+
}
|
2257
|
+
function stopEvent(event) {
|
2258
|
+
event.preventDefault();
|
2259
|
+
event.stopImmediatePropagation();
|
2260
|
+
event.stopPropagation();
|
2261
|
+
}
|
2262
|
+
|
2263
|
+
/**
|
2264
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
2265
|
+
*
|
2266
|
+
* This source code is licensed under the MIT license found in the
|
2267
|
+
* LICENSE file in the root directory of this source tree.
|
2268
|
+
*
|
2269
|
+
*/
|
2270
|
+
/** @noInheritDoc */
|
2271
|
+
class TableNode extends ElementNode {
|
2272
|
+
static getType() {
|
2273
|
+
return 'table';
|
2274
|
+
}
|
2275
|
+
static clone(node) {
|
2276
|
+
return new TableNode(node.__key);
|
2277
|
+
}
|
2278
|
+
static importDOM() {
|
2279
|
+
return {
|
2280
|
+
table: _node => ({
|
2281
|
+
conversion: convertTableElement,
|
2282
|
+
priority: 1
|
2283
|
+
})
|
2284
|
+
};
|
2285
|
+
}
|
2286
|
+
static importJSON(_serializedNode) {
|
2287
|
+
return $createTableNode();
|
2288
|
+
}
|
2289
|
+
constructor(key) {
|
2290
|
+
super(key);
|
2291
|
+
}
|
2292
|
+
exportJSON() {
|
2293
|
+
return {
|
2294
|
+
...super.exportJSON(),
|
2295
|
+
type: 'table',
|
2296
|
+
version: 1
|
2297
|
+
};
|
2298
|
+
}
|
2299
|
+
createDOM(config, editor) {
|
2300
|
+
const tableElement = document.createElement('table');
|
2301
|
+
addClassNamesToElement(tableElement, config.theme.table);
|
2302
|
+
return tableElement;
|
2303
|
+
}
|
2304
|
+
updateDOM() {
|
2305
|
+
return false;
|
2306
|
+
}
|
2307
|
+
exportDOM(editor) {
|
2308
|
+
return {
|
2309
|
+
...super.exportDOM(editor),
|
2310
|
+
after: tableElement => {
|
2311
|
+
if (tableElement) {
|
2312
|
+
const newElement = tableElement.cloneNode();
|
2313
|
+
const colGroup = document.createElement('colgroup');
|
2314
|
+
const tBody = document.createElement('tbody');
|
2315
|
+
if (isHTMLElement(tableElement)) {
|
2316
|
+
tBody.append(...tableElement.children);
|
2317
|
+
}
|
2318
|
+
const firstRow = this.getFirstChildOrThrow();
|
2319
|
+
if (!$isTableRowNode(firstRow)) {
|
2320
|
+
throw new Error('Expected to find row node.');
|
2321
|
+
}
|
2322
|
+
const colCount = firstRow.getChildrenSize();
|
2323
|
+
for (let i = 0; i < colCount; i++) {
|
2324
|
+
const col = document.createElement('col');
|
2325
|
+
colGroup.append(col);
|
2326
|
+
}
|
2327
|
+
newElement.replaceChildren(colGroup, tBody);
|
2328
|
+
return newElement;
|
2329
|
+
}
|
2330
|
+
}
|
2331
|
+
};
|
2332
|
+
}
|
2333
|
+
canBeEmpty() {
|
2334
|
+
return false;
|
2335
|
+
}
|
2336
|
+
isShadowRoot() {
|
2337
|
+
return true;
|
2338
|
+
}
|
2339
|
+
getCordsFromCellNode(tableCellNode, table) {
|
2340
|
+
const {
|
2341
|
+
rows,
|
2342
|
+
domRows
|
2343
|
+
} = table;
|
2344
|
+
for (let y = 0; y < rows; y++) {
|
2345
|
+
const row = domRows[y];
|
2346
|
+
if (row == null) {
|
2347
|
+
continue;
|
2348
|
+
}
|
2349
|
+
const x = row.findIndex(cell => {
|
2350
|
+
if (!cell) {
|
2351
|
+
return;
|
2352
|
+
}
|
2353
|
+
const {
|
2354
|
+
elem
|
2355
|
+
} = cell;
|
2356
|
+
const cellNode = $getNearestNodeFromDOMNode(elem);
|
2357
|
+
return cellNode === tableCellNode;
|
2358
|
+
});
|
2359
|
+
if (x !== -1) {
|
2360
|
+
return {
|
2361
|
+
x,
|
2362
|
+
y
|
2363
|
+
};
|
2364
|
+
}
|
2365
|
+
}
|
2366
|
+
throw new Error('Cell not found in table.');
|
2367
|
+
}
|
2368
|
+
getDOMCellFromCords(x, y, table) {
|
2369
|
+
const {
|
2370
|
+
domRows
|
2371
|
+
} = table;
|
2372
|
+
const row = domRows[y];
|
2373
|
+
if (row == null) {
|
2374
|
+
return null;
|
2375
|
+
}
|
2376
|
+
const cell = row[x];
|
2377
|
+
if (cell == null) {
|
2378
|
+
return null;
|
2379
|
+
}
|
2380
|
+
return cell;
|
2381
|
+
}
|
2382
|
+
getDOMCellFromCordsOrThrow(x, y, table) {
|
2383
|
+
const cell = this.getDOMCellFromCords(x, y, table);
|
2384
|
+
if (!cell) {
|
2385
|
+
throw new Error('Cell not found at cords.');
|
2386
|
+
}
|
2387
|
+
return cell;
|
2388
|
+
}
|
2389
|
+
getCellNodeFromCords(x, y, table) {
|
2390
|
+
const cell = this.getDOMCellFromCords(x, y, table);
|
2391
|
+
if (cell == null) {
|
2392
|
+
return null;
|
2393
|
+
}
|
2394
|
+
const node = $getNearestNodeFromDOMNode(cell.elem);
|
2395
|
+
if ($isTableCellNode(node)) {
|
2396
|
+
return node;
|
2397
|
+
}
|
2398
|
+
return null;
|
2399
|
+
}
|
2400
|
+
getCellNodeFromCordsOrThrow(x, y, table) {
|
2401
|
+
const node = this.getCellNodeFromCords(x, y, table);
|
2402
|
+
if (!node) {
|
2403
|
+
throw new Error('Node at cords not TableCellNode.');
|
2404
|
+
}
|
2405
|
+
return node;
|
2406
|
+
}
|
2407
|
+
canSelectBefore() {
|
2408
|
+
return true;
|
2409
|
+
}
|
2410
|
+
canIndent() {
|
2411
|
+
return false;
|
2412
|
+
}
|
2413
|
+
}
|
2414
|
+
function $getElementForTableNode(editor, tableNode) {
|
2415
|
+
const tableElement = editor.getElementByKey(tableNode.getKey());
|
2416
|
+
if (tableElement == null) {
|
2417
|
+
throw new Error('Table Element Not Found');
|
2418
|
+
}
|
2419
|
+
return getTable(tableElement);
|
2420
|
+
}
|
2421
|
+
function convertTableElement(_domNode) {
|
2422
|
+
return {
|
2423
|
+
node: $createTableNode()
|
2424
|
+
};
|
2425
|
+
}
|
2426
|
+
function $createTableNode() {
|
2427
|
+
return $applyNodeReplacement(new TableNode());
|
2428
|
+
}
|
2429
|
+
function $isTableNode(node) {
|
2430
|
+
return node instanceof TableNode;
|
2431
|
+
}
|
2432
|
+
|
2433
|
+
export { $computeTableMap, $createTableCellNode, $createTableNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $getElementForTableNode, $getNodeTriplet, $getTableCellNodeFromLexicalNode, $getTableCellNodeRect, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $getTableRowNodeFromTableCellNodeOrThrow, $insertTableColumn, $insertTableColumn__EXPERIMENTAL, $insertTableRow, $insertTableRow__EXPERIMENTAL, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, $removeTableRowAtIndex, $unmergeCell, INSERT_TABLE_COMMAND, TableCellHeaderStates, TableCellNode, TableNode, TableObserver, TableRowNode, applyTableHandlers, getDOMCellFromTarget, getTableObserverFromTableElement };
|