@lexical/table 0.44.1-nightly.20260519.0 → 0.45.1-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{LexicalTable.dev.js → dist/LexicalTable.dev.js} +268 -33
- package/{LexicalTable.dev.mjs → dist/LexicalTable.dev.mjs} +264 -33
- package/{LexicalTable.mjs → dist/LexicalTable.mjs} +4 -0
- package/{LexicalTable.node.mjs → dist/LexicalTable.node.mjs} +4 -0
- package/dist/LexicalTable.prod.js +9 -0
- package/dist/LexicalTable.prod.mjs +9 -0
- package/dist/TableImportExtension.d.ts +41 -0
- package/{index.d.ts → dist/index.d.ts} +1 -0
- package/package.json +34 -18
- package/src/LexicalTableCellNode.ts +479 -0
- package/src/LexicalTableCommands.ts +27 -0
- package/src/LexicalTableExtension.ts +104 -0
- package/src/LexicalTableNode.ts +678 -0
- package/src/LexicalTableObserver.ts +575 -0
- package/src/LexicalTablePluginHelpers.ts +694 -0
- package/src/LexicalTableRowNode.ts +154 -0
- package/src/LexicalTableSelection.ts +460 -0
- package/src/LexicalTableSelectionHelpers.ts +2409 -0
- package/src/LexicalTableUtils.ts +1386 -0
- package/src/TableImportExtension.ts +324 -0
- package/src/constants.ts +13 -0
- package/src/index.ts +97 -0
- package/LexicalTable.prod.js +0 -9
- package/LexicalTable.prod.mjs +0 -9
- /package/{LexicalTable.js → dist/LexicalTable.js} +0 -0
- /package/{LexicalTable.js.flow → dist/LexicalTable.js.flow} +0 -0
- /package/{LexicalTableCellNode.d.ts → dist/LexicalTableCellNode.d.ts} +0 -0
- /package/{LexicalTableCommands.d.ts → dist/LexicalTableCommands.d.ts} +0 -0
- /package/{LexicalTableExtension.d.ts → dist/LexicalTableExtension.d.ts} +0 -0
- /package/{LexicalTableNode.d.ts → dist/LexicalTableNode.d.ts} +0 -0
- /package/{LexicalTableObserver.d.ts → dist/LexicalTableObserver.d.ts} +0 -0
- /package/{LexicalTablePluginHelpers.d.ts → dist/LexicalTablePluginHelpers.d.ts} +0 -0
- /package/{LexicalTableRowNode.d.ts → dist/LexicalTableRowNode.d.ts} +0 -0
- /package/{LexicalTableSelection.d.ts → dist/LexicalTableSelection.d.ts} +0 -0
- /package/{LexicalTableSelectionHelpers.d.ts → dist/LexicalTableSelectionHelpers.d.ts} +0 -0
- /package/{LexicalTableUtils.d.ts → dist/LexicalTableUtils.d.ts} +0 -0
- /package/{constants.d.ts → dist/constants.d.ts} +0 -0
|
@@ -0,0 +1,575 @@
|
|
|
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
|
+
*/
|
|
8
|
+
|
|
9
|
+
import invariant from '@lexical/internal/invariant';
|
|
10
|
+
import {
|
|
11
|
+
addClassNamesToElement,
|
|
12
|
+
removeClassNamesFromElement,
|
|
13
|
+
} from '@lexical/utils';
|
|
14
|
+
import {
|
|
15
|
+
$createParagraphNode,
|
|
16
|
+
$createRangeSelection,
|
|
17
|
+
$createTextNode,
|
|
18
|
+
$getEditor,
|
|
19
|
+
$getNodeByKey,
|
|
20
|
+
$getSelection,
|
|
21
|
+
$isElementNode,
|
|
22
|
+
$isParagraphNode,
|
|
23
|
+
$isRootNode,
|
|
24
|
+
$setSelection,
|
|
25
|
+
getDOMSelection,
|
|
26
|
+
INSERT_PARAGRAPH_COMMAND,
|
|
27
|
+
type LexicalEditor,
|
|
28
|
+
type NodeKey,
|
|
29
|
+
SELECTION_CHANGE_COMMAND,
|
|
30
|
+
type TextFormatType,
|
|
31
|
+
} from 'lexical';
|
|
32
|
+
|
|
33
|
+
import {$isTableCellNode, TableCellNode} from './LexicalTableCellNode';
|
|
34
|
+
import {$isTableNode, TableNode} from './LexicalTableNode';
|
|
35
|
+
import {$isTableRowNode} from './LexicalTableRowNode';
|
|
36
|
+
import {
|
|
37
|
+
$createTableSelectionFrom,
|
|
38
|
+
$isTableSelection,
|
|
39
|
+
type TableSelection,
|
|
40
|
+
} from './LexicalTableSelection';
|
|
41
|
+
import {
|
|
42
|
+
$getNearestTableCellInTableFromDOMNode,
|
|
43
|
+
$updateDOMForSelection,
|
|
44
|
+
getTable,
|
|
45
|
+
getTableElement,
|
|
46
|
+
HTMLTableElementWithWithTableSelectionState,
|
|
47
|
+
} from './LexicalTableSelectionHelpers';
|
|
48
|
+
|
|
49
|
+
export type TableDOMCell = {
|
|
50
|
+
elem: HTMLElement;
|
|
51
|
+
highlighted: boolean;
|
|
52
|
+
hasBackgroundColor: boolean;
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type TableDOMRows = Array<Array<TableDOMCell | undefined> | undefined>;
|
|
58
|
+
|
|
59
|
+
export type TableDOMTable = {
|
|
60
|
+
domRows: TableDOMRows;
|
|
61
|
+
columns: number;
|
|
62
|
+
rows: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function $getTableAndElementByKey(
|
|
66
|
+
tableNodeKey: NodeKey,
|
|
67
|
+
editor: LexicalEditor = $getEditor(),
|
|
68
|
+
): {
|
|
69
|
+
tableNode: TableNode;
|
|
70
|
+
tableElement: HTMLTableElementWithWithTableSelectionState;
|
|
71
|
+
} {
|
|
72
|
+
const tableNode = $getNodeByKey(tableNodeKey);
|
|
73
|
+
invariant(
|
|
74
|
+
$isTableNode(tableNode),
|
|
75
|
+
'TableObserver: Expected tableNodeKey %s to be a TableNode',
|
|
76
|
+
tableNodeKey,
|
|
77
|
+
);
|
|
78
|
+
const tableElement = getTableElement(
|
|
79
|
+
tableNode,
|
|
80
|
+
editor.getElementByKey(tableNodeKey),
|
|
81
|
+
);
|
|
82
|
+
invariant(
|
|
83
|
+
tableElement !== null,
|
|
84
|
+
'TableObserver: Expected to find TableElement in DOM for key %s',
|
|
85
|
+
tableNodeKey,
|
|
86
|
+
);
|
|
87
|
+
return {tableElement, tableNode};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type TableNextFocus = {
|
|
91
|
+
tableKey: NodeKey;
|
|
92
|
+
focusCell: TableDOMCell;
|
|
93
|
+
override: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Tracks table selection state that sits across all tables.
|
|
98
|
+
*/
|
|
99
|
+
export class TableObservers {
|
|
100
|
+
observers: Map<
|
|
101
|
+
NodeKey,
|
|
102
|
+
[TableObserver, HTMLTableElementWithWithTableSelectionState]
|
|
103
|
+
>;
|
|
104
|
+
nextFocus: TableNextFocus | null;
|
|
105
|
+
shouldCheckSelectionForTable: NodeKey | null;
|
|
106
|
+
|
|
107
|
+
constructor() {
|
|
108
|
+
this.observers = new Map<
|
|
109
|
+
NodeKey,
|
|
110
|
+
[TableObserver, HTMLTableElementWithWithTableSelectionState]
|
|
111
|
+
>();
|
|
112
|
+
this.nextFocus = null;
|
|
113
|
+
this.shouldCheckSelectionForTable = null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @internal
|
|
118
|
+
* When handling mousemove events we track what the focus cell should be, but
|
|
119
|
+
* the DOM selection may end up somewhere else entirely. We don't have an elegant
|
|
120
|
+
* way to handle this after the DOM selection has been resolved in a
|
|
121
|
+
* SELECTION_CHANGE_COMMAND callback.
|
|
122
|
+
*/
|
|
123
|
+
setNextFocus(nextFocus: TableNextFocus | null): void {
|
|
124
|
+
this.nextFocus = nextFocus;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @internal */
|
|
128
|
+
getAndClearNextFocus(): TableNextFocus | null {
|
|
129
|
+
const {nextFocus} = this;
|
|
130
|
+
if (nextFocus !== null) {
|
|
131
|
+
this.nextFocus = null;
|
|
132
|
+
}
|
|
133
|
+
return nextFocus;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @internal
|
|
138
|
+
* Firefox has a strange behavior where pressing the down arrow key from
|
|
139
|
+
* above the table will move the caret after the table and then lexical
|
|
140
|
+
* will select the last cell instead of the first.
|
|
141
|
+
* We do still want to let the browser handle caret movement but we will
|
|
142
|
+
* use this property to "tag" the update so that we can recheck the
|
|
143
|
+
* selection after the event is processed.
|
|
144
|
+
*/
|
|
145
|
+
setShouldCheckSelectionForTable(tableKey: NodeKey): void {
|
|
146
|
+
this.shouldCheckSelectionForTable = tableKey;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
getAndClearShouldCheckSelectionForTable(): NodeKey | null {
|
|
152
|
+
const {shouldCheckSelectionForTable} = this;
|
|
153
|
+
if (shouldCheckSelectionForTable) {
|
|
154
|
+
this.shouldCheckSelectionForTable = null;
|
|
155
|
+
return shouldCheckSelectionForTable;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class TableObserver {
|
|
162
|
+
focusX: number;
|
|
163
|
+
focusY: number;
|
|
164
|
+
listenersToRemove: Set<() => void>;
|
|
165
|
+
table: TableDOMTable;
|
|
166
|
+
isHighlightingCells: boolean;
|
|
167
|
+
anchorX: number;
|
|
168
|
+
anchorY: number;
|
|
169
|
+
tableNodeKey: NodeKey;
|
|
170
|
+
anchorCell: TableDOMCell | null;
|
|
171
|
+
focusCell: TableDOMCell | null;
|
|
172
|
+
anchorCellNodeKey: NodeKey | null;
|
|
173
|
+
focusCellNodeKey: NodeKey | null;
|
|
174
|
+
editor: LexicalEditor;
|
|
175
|
+
tableSelection: TableSelection | null;
|
|
176
|
+
hasHijackedSelectionStyles: boolean;
|
|
177
|
+
isSelecting: boolean;
|
|
178
|
+
pointerType: string | null;
|
|
179
|
+
abortController: AbortController;
|
|
180
|
+
listenerOptions: {signal: AbortSignal};
|
|
181
|
+
|
|
182
|
+
constructor(editor: LexicalEditor, tableNodeKey: string) {
|
|
183
|
+
this.isHighlightingCells = false;
|
|
184
|
+
this.anchorX = -1;
|
|
185
|
+
this.anchorY = -1;
|
|
186
|
+
this.focusX = -1;
|
|
187
|
+
this.focusY = -1;
|
|
188
|
+
this.listenersToRemove = new Set();
|
|
189
|
+
this.tableNodeKey = tableNodeKey;
|
|
190
|
+
this.editor = editor;
|
|
191
|
+
this.table = {
|
|
192
|
+
columns: 0,
|
|
193
|
+
domRows: [],
|
|
194
|
+
rows: 0,
|
|
195
|
+
};
|
|
196
|
+
this.tableSelection = null;
|
|
197
|
+
this.anchorCellNodeKey = null;
|
|
198
|
+
this.focusCellNodeKey = null;
|
|
199
|
+
this.anchorCell = null;
|
|
200
|
+
this.focusCell = null;
|
|
201
|
+
this.hasHijackedSelectionStyles = false;
|
|
202
|
+
this.isSelecting = false;
|
|
203
|
+
this.pointerType = null;
|
|
204
|
+
this.abortController = new AbortController();
|
|
205
|
+
this.listenerOptions = {signal: this.abortController.signal};
|
|
206
|
+
this.trackTable();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getTable(): TableDOMTable {
|
|
210
|
+
return this.table;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
removeListeners() {
|
|
214
|
+
this.abortController.abort('removeListeners');
|
|
215
|
+
Array.from(this.listenersToRemove).forEach(removeListener =>
|
|
216
|
+
removeListener(),
|
|
217
|
+
);
|
|
218
|
+
this.listenersToRemove.clear();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
$lookup(): {
|
|
222
|
+
tableNode: TableNode;
|
|
223
|
+
tableElement: HTMLTableElementWithWithTableSelectionState;
|
|
224
|
+
} {
|
|
225
|
+
return $getTableAndElementByKey(this.tableNodeKey, this.editor);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
trackTable() {
|
|
229
|
+
const observer = new MutationObserver(records => {
|
|
230
|
+
this.editor.getEditorState().read(
|
|
231
|
+
() => {
|
|
232
|
+
let gridNeedsRedraw = false;
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < records.length; i++) {
|
|
235
|
+
const record = records[i];
|
|
236
|
+
const target = record.target;
|
|
237
|
+
const nodeName = target.nodeName;
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
nodeName === 'TABLE' ||
|
|
241
|
+
nodeName === 'TBODY' ||
|
|
242
|
+
nodeName === 'THEAD' ||
|
|
243
|
+
nodeName === 'TR'
|
|
244
|
+
) {
|
|
245
|
+
gridNeedsRedraw = true;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!gridNeedsRedraw) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const {tableNode, tableElement} = this.$lookup();
|
|
255
|
+
this.table = getTable(tableNode, tableElement);
|
|
256
|
+
},
|
|
257
|
+
{editor: this.editor},
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
this.editor.getEditorState().read(
|
|
261
|
+
() => {
|
|
262
|
+
const {tableNode, tableElement} = this.$lookup();
|
|
263
|
+
this.table = getTable(tableNode, tableElement);
|
|
264
|
+
observer.observe(tableElement, {
|
|
265
|
+
attributes: true,
|
|
266
|
+
childList: true,
|
|
267
|
+
subtree: true,
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
{editor: this.editor},
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
$clearHighlight(setEmptySelection: boolean = true): void {
|
|
275
|
+
const editor = this.editor;
|
|
276
|
+
this.isHighlightingCells = false;
|
|
277
|
+
this.anchorX = -1;
|
|
278
|
+
this.anchorY = -1;
|
|
279
|
+
this.focusX = -1;
|
|
280
|
+
this.focusY = -1;
|
|
281
|
+
this.tableSelection = null;
|
|
282
|
+
this.anchorCellNodeKey = null;
|
|
283
|
+
this.focusCellNodeKey = null;
|
|
284
|
+
this.anchorCell = null;
|
|
285
|
+
this.focusCell = null;
|
|
286
|
+
this.hasHijackedSelectionStyles = false;
|
|
287
|
+
|
|
288
|
+
this.$enableHighlightStyle();
|
|
289
|
+
|
|
290
|
+
const {tableNode, tableElement} = this.$lookup();
|
|
291
|
+
const grid = getTable(tableNode, tableElement);
|
|
292
|
+
$updateDOMForSelection(editor, grid, null);
|
|
293
|
+
if (setEmptySelection && $getSelection() !== null) {
|
|
294
|
+
$setSelection(null);
|
|
295
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
$enableHighlightStyle() {
|
|
300
|
+
const editor = this.editor;
|
|
301
|
+
const {tableElement} = this.$lookup();
|
|
302
|
+
|
|
303
|
+
removeClassNamesFromElement(
|
|
304
|
+
tableElement,
|
|
305
|
+
editor._config.theme.tableSelection,
|
|
306
|
+
);
|
|
307
|
+
tableElement.classList.remove('disable-selection');
|
|
308
|
+
this.hasHijackedSelectionStyles = false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
$disableHighlightStyle() {
|
|
312
|
+
const {tableElement} = this.$lookup();
|
|
313
|
+
addClassNamesToElement(
|
|
314
|
+
tableElement,
|
|
315
|
+
this.editor._config.theme.tableSelection,
|
|
316
|
+
);
|
|
317
|
+
this.hasHijackedSelectionStyles = true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
$updateTableTableSelection(selection: TableSelection | null): void {
|
|
321
|
+
if (selection !== null) {
|
|
322
|
+
invariant(
|
|
323
|
+
selection.tableKey === this.tableNodeKey,
|
|
324
|
+
"TableObserver.$updateTableTableSelection: selection.tableKey !== this.tableNodeKey ('%s' !== '%s')",
|
|
325
|
+
selection.tableKey,
|
|
326
|
+
this.tableNodeKey,
|
|
327
|
+
);
|
|
328
|
+
const editor = this.editor;
|
|
329
|
+
this.tableSelection = selection;
|
|
330
|
+
this.isHighlightingCells = true;
|
|
331
|
+
this.$disableHighlightStyle();
|
|
332
|
+
this.updateDOMSelection();
|
|
333
|
+
$updateDOMForSelection(editor, this.table, this.tableSelection);
|
|
334
|
+
} else {
|
|
335
|
+
this.$clearHighlight();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** @internal */
|
|
340
|
+
updateDOMSelection() {
|
|
341
|
+
if (this.anchorCell !== null && this.focusCell !== null) {
|
|
342
|
+
const domSelection = getDOMSelection(this.editor._window);
|
|
343
|
+
// We are not using a native selection for tables, and if we
|
|
344
|
+
// set one then the reconciler will undo it.
|
|
345
|
+
// TODO - it would make sense to have one so that native
|
|
346
|
+
// copy/paste worked. Right now we have to emulate with
|
|
347
|
+
// keyboard events but it won't fire if triggered from the menu
|
|
348
|
+
if (domSelection && domSelection.rangeCount > 0) {
|
|
349
|
+
domSelection.removeAllRanges();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
$setFocusCellForSelection(cell: TableDOMCell, ignoreStart = false): boolean {
|
|
355
|
+
const editor = this.editor;
|
|
356
|
+
const {tableNode} = this.$lookup();
|
|
357
|
+
|
|
358
|
+
const cellX = cell.x;
|
|
359
|
+
const cellY = cell.y;
|
|
360
|
+
this.focusCell = cell;
|
|
361
|
+
|
|
362
|
+
// Enable highlighting if: ignoreStart is true, or anchor differs from focus,
|
|
363
|
+
// or we have valid tableSelection with anchor (for first drag after column switch)
|
|
364
|
+
if (!this.isHighlightingCells) {
|
|
365
|
+
const shouldEnable =
|
|
366
|
+
ignoreStart ||
|
|
367
|
+
this.anchorX !== cellX ||
|
|
368
|
+
this.anchorY !== cellY ||
|
|
369
|
+
(this.tableSelection != null && this.anchorCellNodeKey != null);
|
|
370
|
+
if (shouldEnable) {
|
|
371
|
+
this.isHighlightingCells = true;
|
|
372
|
+
this.$disableHighlightStyle();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Skip if we're trying to select the same cell we already have selected
|
|
377
|
+
// But only if focusX/focusY are valid (not -1, which means not reset)
|
|
378
|
+
if (
|
|
379
|
+
this.focusX !== -1 &&
|
|
380
|
+
this.focusY !== -1 &&
|
|
381
|
+
cellX === this.focusX &&
|
|
382
|
+
cellY === this.focusY
|
|
383
|
+
) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
this.focusX = cellX;
|
|
388
|
+
this.focusY = cellY;
|
|
389
|
+
|
|
390
|
+
if (this.isHighlightingCells) {
|
|
391
|
+
const focusTableCellNode = $getNearestTableCellInTableFromDOMNode(
|
|
392
|
+
tableNode,
|
|
393
|
+
cell.elem,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
if (this.tableSelection != null && this.anchorCellNodeKey != null) {
|
|
397
|
+
let targetCellNode = focusTableCellNode;
|
|
398
|
+
|
|
399
|
+
// Fallback: use coordinates if DOM lookup failed (handles timing issues on first drag)
|
|
400
|
+
if (targetCellNode === null && ignoreStart) {
|
|
401
|
+
targetCellNode = tableNode.getCellNodeFromCords(
|
|
402
|
+
cellX,
|
|
403
|
+
cellY,
|
|
404
|
+
this.table,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (targetCellNode !== null) {
|
|
409
|
+
const anchorTableCell = this.$getAnchorTableCellOrThrow();
|
|
410
|
+
this.focusCellNodeKey = targetCellNode.getKey();
|
|
411
|
+
this.tableSelection = $createTableSelectionFrom(
|
|
412
|
+
tableNode,
|
|
413
|
+
anchorTableCell,
|
|
414
|
+
targetCellNode,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
$setSelection(this.tableSelection);
|
|
418
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
|
419
|
+
$updateDOMForSelection(editor, this.table, this.tableSelection);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
$getAnchorTableCell(): TableCellNode | null {
|
|
428
|
+
return this.anchorCellNodeKey
|
|
429
|
+
? $getNodeByKey(this.anchorCellNodeKey)
|
|
430
|
+
: null;
|
|
431
|
+
}
|
|
432
|
+
$getAnchorTableCellOrThrow(): TableCellNode {
|
|
433
|
+
const anchorTableCell = this.$getAnchorTableCell();
|
|
434
|
+
invariant(
|
|
435
|
+
anchorTableCell !== null,
|
|
436
|
+
'TableObserver anchorTableCell is null',
|
|
437
|
+
);
|
|
438
|
+
return anchorTableCell;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
$getFocusTableCell(): TableCellNode | null {
|
|
442
|
+
return this.focusCellNodeKey ? $getNodeByKey(this.focusCellNodeKey) : null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
$getFocusTableCellOrThrow(): TableCellNode {
|
|
446
|
+
const focusTableCell = this.$getFocusTableCell();
|
|
447
|
+
invariant(focusTableCell !== null, 'TableObserver focusTableCell is null');
|
|
448
|
+
return focusTableCell;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
$setAnchorCellForSelection(cell: TableDOMCell) {
|
|
452
|
+
this.isHighlightingCells = false;
|
|
453
|
+
this.anchorCell = cell;
|
|
454
|
+
this.anchorX = cell.x;
|
|
455
|
+
this.anchorY = cell.y;
|
|
456
|
+
// Reset focus state to prevent stale values from previous selections
|
|
457
|
+
this.focusX = -1;
|
|
458
|
+
this.focusY = -1;
|
|
459
|
+
this.focusCell = null;
|
|
460
|
+
this.focusCellNodeKey = null;
|
|
461
|
+
|
|
462
|
+
const {tableNode} = this.$lookup();
|
|
463
|
+
const anchorTableCellNode = $getNearestTableCellInTableFromDOMNode(
|
|
464
|
+
tableNode,
|
|
465
|
+
cell.elem,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
if (anchorTableCellNode !== null) {
|
|
469
|
+
const anchorNodeKey = anchorTableCellNode.getKey();
|
|
470
|
+
if (this.tableSelection != null) {
|
|
471
|
+
this.tableSelection = this.tableSelection.clone();
|
|
472
|
+
this.tableSelection.set(
|
|
473
|
+
tableNode.getKey(),
|
|
474
|
+
anchorNodeKey,
|
|
475
|
+
anchorNodeKey,
|
|
476
|
+
);
|
|
477
|
+
} else {
|
|
478
|
+
this.tableSelection = $createTableSelectionFrom(
|
|
479
|
+
tableNode,
|
|
480
|
+
anchorTableCellNode,
|
|
481
|
+
anchorTableCellNode,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
this.anchorCellNodeKey = anchorNodeKey;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
$formatCells(type: TextFormatType) {
|
|
489
|
+
const selection = $getSelection();
|
|
490
|
+
|
|
491
|
+
invariant($isTableSelection(selection), 'Expected Table selection');
|
|
492
|
+
|
|
493
|
+
const formatSelection = $createRangeSelection();
|
|
494
|
+
|
|
495
|
+
const anchor = formatSelection.anchor;
|
|
496
|
+
const focus = formatSelection.focus;
|
|
497
|
+
|
|
498
|
+
const cellNodes = selection.getNodes().filter($isTableCellNode);
|
|
499
|
+
invariant(cellNodes.length > 0, 'No table cells present');
|
|
500
|
+
const paragraph = cellNodes[0].getFirstChild();
|
|
501
|
+
const alignFormatWith = $isParagraphNode(paragraph)
|
|
502
|
+
? paragraph.getFormatFlags(type, null)
|
|
503
|
+
: null;
|
|
504
|
+
|
|
505
|
+
cellNodes.forEach((cellNode: TableCellNode) => {
|
|
506
|
+
anchor.set(cellNode.getKey(), 0, 'element');
|
|
507
|
+
focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
|
|
508
|
+
formatSelection.formatText(type, alignFormatWith);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
$setSelection(selection);
|
|
512
|
+
|
|
513
|
+
this.editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
$clearText() {
|
|
517
|
+
const {editor} = this;
|
|
518
|
+
const tableNode = $getNodeByKey(this.tableNodeKey);
|
|
519
|
+
|
|
520
|
+
if (!$isTableNode(tableNode)) {
|
|
521
|
+
throw new Error('Expected TableNode.');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const selection = $getSelection();
|
|
525
|
+
|
|
526
|
+
invariant($isTableSelection(selection), 'Expected TableSelection');
|
|
527
|
+
|
|
528
|
+
const selectedNodes = selection.getNodes().filter($isTableCellNode);
|
|
529
|
+
|
|
530
|
+
// Check if the entire table is selected by verifying first and last cells
|
|
531
|
+
const firstRow = tableNode.getFirstChild();
|
|
532
|
+
const lastRow = tableNode.getLastChild();
|
|
533
|
+
|
|
534
|
+
const isEntireTableSelected =
|
|
535
|
+
selectedNodes.length > 0 &&
|
|
536
|
+
firstRow !== null &&
|
|
537
|
+
lastRow !== null &&
|
|
538
|
+
$isTableRowNode(firstRow) &&
|
|
539
|
+
$isTableRowNode(lastRow) &&
|
|
540
|
+
selectedNodes[0] === firstRow.getFirstChild() &&
|
|
541
|
+
selectedNodes[selectedNodes.length - 1] === lastRow.getLastChild();
|
|
542
|
+
|
|
543
|
+
if (isEntireTableSelected) {
|
|
544
|
+
tableNode.selectPrevious();
|
|
545
|
+
const parent = tableNode.getParent();
|
|
546
|
+
// Delete entire table
|
|
547
|
+
tableNode.remove();
|
|
548
|
+
// Handle case when table was the only node
|
|
549
|
+
if ($isRootNode(parent) && parent.isEmpty()) {
|
|
550
|
+
editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
selectedNodes.forEach(cellNode => {
|
|
556
|
+
if ($isElementNode(cellNode)) {
|
|
557
|
+
const paragraphNode = $createParagraphNode();
|
|
558
|
+
const textNode = $createTextNode();
|
|
559
|
+
paragraphNode.append(textNode);
|
|
560
|
+
cellNode.append(paragraphNode);
|
|
561
|
+
cellNode.getChildren().forEach(child => {
|
|
562
|
+
if (child !== paragraphNode) {
|
|
563
|
+
child.remove();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
$updateDOMForSelection(editor, this.table, null);
|
|
570
|
+
|
|
571
|
+
$setSelection(null);
|
|
572
|
+
|
|
573
|
+
editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
|
|
574
|
+
}
|
|
575
|
+
}
|