@lexical/table 0.44.1-nightly.20260519.0 → 0.45.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} +243 -33
- package/{LexicalTable.dev.mjs → dist/LexicalTable.dev.mjs} +239 -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 +38 -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 +302 -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,678 @@
|
|
|
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
|
+
$descendantsMatching,
|
|
12
|
+
addClassNamesToElement,
|
|
13
|
+
isHTMLElement,
|
|
14
|
+
removeClassNamesFromElement,
|
|
15
|
+
} from '@lexical/utils';
|
|
16
|
+
import {
|
|
17
|
+
$applyNodeReplacement,
|
|
18
|
+
$getEditor,
|
|
19
|
+
$getNearestNodeFromDOMNode,
|
|
20
|
+
BaseSelection,
|
|
21
|
+
DOMConversionMap,
|
|
22
|
+
DOMConversionOutput,
|
|
23
|
+
DOMExportOutput,
|
|
24
|
+
EditorConfig,
|
|
25
|
+
ElementDOMSlot,
|
|
26
|
+
type ElementFormatType,
|
|
27
|
+
ElementNode,
|
|
28
|
+
LexicalEditor,
|
|
29
|
+
LexicalNode,
|
|
30
|
+
LexicalUpdateJSON,
|
|
31
|
+
NodeKey,
|
|
32
|
+
SerializedElementNode,
|
|
33
|
+
setDOMStyleFromCSS,
|
|
34
|
+
setDOMUnmanaged,
|
|
35
|
+
Spread,
|
|
36
|
+
} from 'lexical';
|
|
37
|
+
|
|
38
|
+
import {PIXEL_VALUE_REG_EXP} from './constants';
|
|
39
|
+
import {$isTableCellNode, type TableCellNode} from './LexicalTableCellNode';
|
|
40
|
+
import {TableDOMCell, TableDOMTable} from './LexicalTableObserver';
|
|
41
|
+
import {$isTableRowNode, type TableRowNode} from './LexicalTableRowNode';
|
|
42
|
+
import {
|
|
43
|
+
$getNearestTableCellInTableFromDOMNode,
|
|
44
|
+
getTable,
|
|
45
|
+
getTableElement,
|
|
46
|
+
isHTMLTableElement,
|
|
47
|
+
} from './LexicalTableSelectionHelpers';
|
|
48
|
+
import {$computeTableMapSkipCellCheck} from './LexicalTableUtils';
|
|
49
|
+
|
|
50
|
+
const __DEV__ = process.env.NODE_ENV !== 'production';
|
|
51
|
+
|
|
52
|
+
function isHTMLDivElement(element: unknown): element is HTMLDivElement {
|
|
53
|
+
return isHTMLElement(element) && element.nodeName === 'DIV';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type SerializedTableNode = Spread<
|
|
57
|
+
{
|
|
58
|
+
colWidths?: readonly number[];
|
|
59
|
+
rowStriping?: boolean;
|
|
60
|
+
frozenColumnCount?: number;
|
|
61
|
+
frozenRowCount?: number;
|
|
62
|
+
},
|
|
63
|
+
SerializedElementNode
|
|
64
|
+
>;
|
|
65
|
+
|
|
66
|
+
function updateColgroup(
|
|
67
|
+
dom: HTMLTableElement,
|
|
68
|
+
colCount: number,
|
|
69
|
+
colWidths?: number[] | readonly number[],
|
|
70
|
+
) {
|
|
71
|
+
const colGroup = dom.querySelector('colgroup');
|
|
72
|
+
if (!colGroup) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const cols = [];
|
|
76
|
+
for (let i = 0; i < colCount; i++) {
|
|
77
|
+
const col = document.createElement('col');
|
|
78
|
+
const width = colWidths && colWidths[i];
|
|
79
|
+
if (width) {
|
|
80
|
+
col.style.width = `${width}px`;
|
|
81
|
+
}
|
|
82
|
+
cols.push(col);
|
|
83
|
+
}
|
|
84
|
+
colGroup.replaceChildren(...cols);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setRowStriping(
|
|
88
|
+
dom: HTMLTableElement,
|
|
89
|
+
config: EditorConfig,
|
|
90
|
+
rowStriping: boolean,
|
|
91
|
+
): void {
|
|
92
|
+
if (rowStriping) {
|
|
93
|
+
addClassNamesToElement(dom, config.theme.tableRowStriping);
|
|
94
|
+
dom.setAttribute('data-lexical-row-striping', 'true');
|
|
95
|
+
} else {
|
|
96
|
+
removeClassNamesFromElement(dom, config.theme.tableRowStriping);
|
|
97
|
+
dom.removeAttribute('data-lexical-row-striping');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function setFrozenColumns(
|
|
102
|
+
dom: HTMLDivElement,
|
|
103
|
+
tableElement: HTMLTableElement,
|
|
104
|
+
config: EditorConfig,
|
|
105
|
+
frozenColumnCount: number,
|
|
106
|
+
): void {
|
|
107
|
+
if (frozenColumnCount > 0) {
|
|
108
|
+
addClassNamesToElement(dom, config.theme.tableFrozenColumn);
|
|
109
|
+
tableElement.setAttribute('data-lexical-frozen-column', 'true');
|
|
110
|
+
} else {
|
|
111
|
+
removeClassNamesFromElement(dom, config.theme.tableFrozenColumn);
|
|
112
|
+
tableElement.removeAttribute('data-lexical-frozen-column');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function setFrozenRows(
|
|
117
|
+
dom: HTMLDivElement,
|
|
118
|
+
tableElement: HTMLTableElement,
|
|
119
|
+
config: EditorConfig,
|
|
120
|
+
frozenRowCount: number,
|
|
121
|
+
): void {
|
|
122
|
+
if (frozenRowCount > 0) {
|
|
123
|
+
addClassNamesToElement(dom, config.theme.tableFrozenRow);
|
|
124
|
+
tableElement.setAttribute('data-lexical-frozen-row', 'true');
|
|
125
|
+
} else {
|
|
126
|
+
removeClassNamesFromElement(dom, config.theme.tableFrozenRow);
|
|
127
|
+
tableElement.removeAttribute('data-lexical-frozen-row');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function alignTableElement(
|
|
132
|
+
dom: HTMLTableElement,
|
|
133
|
+
config: EditorConfig,
|
|
134
|
+
formatType: ElementFormatType,
|
|
135
|
+
): void {
|
|
136
|
+
if (!config.theme.tableAlignment) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const removeClasses: string[] = [];
|
|
140
|
+
const addClasses: string[] = [];
|
|
141
|
+
for (const format of ['center', 'right'] as const) {
|
|
142
|
+
const classes = config.theme.tableAlignment[format];
|
|
143
|
+
if (!classes) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
(format === formatType ? addClasses : removeClasses).push(classes);
|
|
147
|
+
}
|
|
148
|
+
removeClassNamesFromElement(dom, ...removeClasses);
|
|
149
|
+
addClassNamesToElement(dom, ...addClasses);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const scrollableEditors = new WeakSet<LexicalEditor>();
|
|
153
|
+
|
|
154
|
+
export function $isScrollableTablesActive(
|
|
155
|
+
editor: LexicalEditor = $getEditor(),
|
|
156
|
+
): boolean {
|
|
157
|
+
return scrollableEditors.has(editor);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function setScrollableTablesActive(
|
|
161
|
+
editor: LexicalEditor,
|
|
162
|
+
active: boolean,
|
|
163
|
+
): void {
|
|
164
|
+
if (active) {
|
|
165
|
+
if (__DEV__ && !editor._config.theme.tableScrollableWrapper) {
|
|
166
|
+
console.warn(
|
|
167
|
+
'TableNode: hasHorizontalScroll is active but theme.tableScrollableWrapper is not defined.',
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
scrollableEditors.add(editor);
|
|
171
|
+
} else {
|
|
172
|
+
scrollableEditors.delete(editor);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @noInheritDoc */
|
|
177
|
+
export class TableNode extends ElementNode {
|
|
178
|
+
/** @internal */
|
|
179
|
+
__rowStriping: boolean;
|
|
180
|
+
__frozenColumnCount: number;
|
|
181
|
+
__frozenRowCount: number;
|
|
182
|
+
__colWidths?: readonly number[];
|
|
183
|
+
|
|
184
|
+
static getType(): string {
|
|
185
|
+
return 'table';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getColWidths(): readonly number[] | undefined {
|
|
189
|
+
const self = this.getLatest();
|
|
190
|
+
return self.__colWidths;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
setColWidths(colWidths: readonly number[] | undefined): this {
|
|
194
|
+
const self = this.getWritable();
|
|
195
|
+
// NOTE: Node properties should be immutable. Freeze to prevent accidental mutation.
|
|
196
|
+
self.__colWidths =
|
|
197
|
+
colWidths !== undefined && __DEV__ ? Object.freeze(colWidths) : colWidths;
|
|
198
|
+
return self;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static clone(node: TableNode): TableNode {
|
|
202
|
+
return new TableNode(node.__key);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
afterCloneFrom(prevNode: this) {
|
|
206
|
+
super.afterCloneFrom(prevNode);
|
|
207
|
+
this.__colWidths = prevNode.__colWidths;
|
|
208
|
+
this.__rowStriping = prevNode.__rowStriping;
|
|
209
|
+
this.__frozenColumnCount = prevNode.__frozenColumnCount;
|
|
210
|
+
this.__frozenRowCount = prevNode.__frozenRowCount;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static importDOM(): DOMConversionMap | null {
|
|
214
|
+
return {
|
|
215
|
+
table: (_node: Node) => ({
|
|
216
|
+
conversion: $convertTableElement,
|
|
217
|
+
priority: 1,
|
|
218
|
+
}),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static importJSON(serializedNode: SerializedTableNode): TableNode {
|
|
223
|
+
return $createTableNode().updateFromJSON(serializedNode);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedTableNode>): this {
|
|
227
|
+
return super
|
|
228
|
+
.updateFromJSON(serializedNode)
|
|
229
|
+
.setRowStriping(serializedNode.rowStriping || false)
|
|
230
|
+
.setFrozenColumns(serializedNode.frozenColumnCount || 0)
|
|
231
|
+
.setFrozenRows(serializedNode.frozenRowCount || 0)
|
|
232
|
+
.setColWidths(serializedNode.colWidths);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
constructor(key?: NodeKey) {
|
|
236
|
+
super(key);
|
|
237
|
+
this.__rowStriping = false;
|
|
238
|
+
this.__frozenColumnCount = 0;
|
|
239
|
+
this.__frozenRowCount = 0;
|
|
240
|
+
this.__colWidths = undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
exportJSON(): SerializedTableNode {
|
|
244
|
+
return {
|
|
245
|
+
...super.exportJSON(),
|
|
246
|
+
colWidths: this.getColWidths(),
|
|
247
|
+
frozenColumnCount: this.__frozenColumnCount
|
|
248
|
+
? this.__frozenColumnCount
|
|
249
|
+
: undefined,
|
|
250
|
+
frozenRowCount: this.__frozenRowCount ? this.__frozenRowCount : undefined,
|
|
251
|
+
rowStriping: this.__rowStriping ? this.__rowStriping : undefined,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
extractWithChild(
|
|
256
|
+
child: LexicalNode,
|
|
257
|
+
selection: BaseSelection | null,
|
|
258
|
+
destination: 'clone' | 'html',
|
|
259
|
+
): boolean {
|
|
260
|
+
return destination === 'html';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getDOMSlot(element: HTMLElement): ElementDOMSlot<HTMLTableElement> {
|
|
264
|
+
const tableElement = !isHTMLTableElement(element)
|
|
265
|
+
? element.querySelector('table')
|
|
266
|
+
: element;
|
|
267
|
+
invariant(
|
|
268
|
+
isHTMLTableElement(tableElement),
|
|
269
|
+
'TableNode.getDOMSlot: createDOM() did not return a table',
|
|
270
|
+
);
|
|
271
|
+
return super
|
|
272
|
+
.getDOMSlot(element)
|
|
273
|
+
.withElement(tableElement)
|
|
274
|
+
.withAfter(tableElement.querySelector('colgroup'));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
createDOM(config: EditorConfig, editor?: LexicalEditor): HTMLElement {
|
|
278
|
+
const tableElement = document.createElement('table');
|
|
279
|
+
if (this.__style) {
|
|
280
|
+
setDOMStyleFromCSS(tableElement.style, this.__style);
|
|
281
|
+
}
|
|
282
|
+
const colGroup = document.createElement('colgroup');
|
|
283
|
+
tableElement.appendChild(colGroup);
|
|
284
|
+
setDOMUnmanaged(colGroup);
|
|
285
|
+
addClassNamesToElement(tableElement, config.theme.table);
|
|
286
|
+
this.updateTableElement(null, tableElement, config);
|
|
287
|
+
if ($isScrollableTablesActive(editor)) {
|
|
288
|
+
const wrapperElement = document.createElement('div');
|
|
289
|
+
const classes = config.theme.tableScrollableWrapper;
|
|
290
|
+
if (classes) {
|
|
291
|
+
addClassNamesToElement(wrapperElement, classes);
|
|
292
|
+
} else {
|
|
293
|
+
wrapperElement.style.overflowX = 'auto';
|
|
294
|
+
}
|
|
295
|
+
wrapperElement.appendChild(tableElement);
|
|
296
|
+
this.updateTableWrapper(null, wrapperElement, tableElement, config);
|
|
297
|
+
return wrapperElement;
|
|
298
|
+
}
|
|
299
|
+
return tableElement;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
updateTableWrapper(
|
|
303
|
+
prevNode: this | null,
|
|
304
|
+
tableWrapper: HTMLDivElement,
|
|
305
|
+
tableElement: HTMLTableElement,
|
|
306
|
+
config: EditorConfig,
|
|
307
|
+
): void {
|
|
308
|
+
if (
|
|
309
|
+
this.__frozenColumnCount !== (prevNode ? prevNode.__frozenColumnCount : 0)
|
|
310
|
+
) {
|
|
311
|
+
setFrozenColumns(
|
|
312
|
+
tableWrapper,
|
|
313
|
+
tableElement,
|
|
314
|
+
config,
|
|
315
|
+
this.__frozenColumnCount,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (this.__frozenRowCount !== (prevNode ? prevNode.__frozenRowCount : 0)) {
|
|
319
|
+
setFrozenRows(tableWrapper, tableElement, config, this.__frozenRowCount);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
updateTableElement(
|
|
324
|
+
prevNode: this | null,
|
|
325
|
+
tableElement: HTMLTableElement,
|
|
326
|
+
config: EditorConfig,
|
|
327
|
+
): void {
|
|
328
|
+
if (this.__style !== (prevNode ? prevNode.__style : '')) {
|
|
329
|
+
setDOMStyleFromCSS(
|
|
330
|
+
tableElement.style,
|
|
331
|
+
this.__style,
|
|
332
|
+
prevNode ? prevNode.__style : '',
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (this.__rowStriping !== (prevNode ? prevNode.__rowStriping : false)) {
|
|
336
|
+
setRowStriping(tableElement, config, this.__rowStriping);
|
|
337
|
+
}
|
|
338
|
+
const prevColCount = prevNode ? prevNode.getColumnCount() : 0;
|
|
339
|
+
const prevColWidths = prevNode ? prevNode.__colWidths : undefined;
|
|
340
|
+
if (
|
|
341
|
+
this.getColumnCount() !== prevColCount ||
|
|
342
|
+
this.getColWidths() !== prevColWidths
|
|
343
|
+
) {
|
|
344
|
+
updateColgroup(tableElement, this.getColumnCount(), this.getColWidths());
|
|
345
|
+
}
|
|
346
|
+
alignTableElement(tableElement, config, this.getFormatType());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {
|
|
350
|
+
const tableElement = getTableElement(this, dom);
|
|
351
|
+
if ((dom === tableElement) === $isScrollableTablesActive()) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if (isHTMLDivElement(dom)) {
|
|
355
|
+
this.updateTableWrapper(prevNode, dom, tableElement, config);
|
|
356
|
+
}
|
|
357
|
+
this.updateTableElement(prevNode, tableElement, config);
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
scaleDOMColWidths(dom: HTMLElement, scale: number): void {
|
|
362
|
+
const colWidths = this.getColWidths();
|
|
363
|
+
if (!colWidths) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const tableElement = getTableElement(this, dom);
|
|
367
|
+
updateColgroup(
|
|
368
|
+
tableElement,
|
|
369
|
+
this.getColumnCount(),
|
|
370
|
+
colWidths.map(width => width * scale),
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
exportDOM(editor: LexicalEditor): DOMExportOutput {
|
|
375
|
+
const superExport = super.exportDOM(editor);
|
|
376
|
+
const {element} = superExport;
|
|
377
|
+
return {
|
|
378
|
+
after: tableElement => {
|
|
379
|
+
if (superExport.after) {
|
|
380
|
+
tableElement = superExport.after(tableElement);
|
|
381
|
+
}
|
|
382
|
+
if (!isHTMLTableElement(tableElement) && isHTMLElement(tableElement)) {
|
|
383
|
+
tableElement = tableElement.querySelector('table');
|
|
384
|
+
}
|
|
385
|
+
if (!isHTMLTableElement(tableElement)) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
alignTableElement(tableElement, editor._config, this.getFormatType());
|
|
389
|
+
|
|
390
|
+
// Scan the table map to build a map of table cell key to the columns it needs
|
|
391
|
+
const [tableMap] = $computeTableMapSkipCellCheck(this, null, null);
|
|
392
|
+
const cellValues = new Map<
|
|
393
|
+
NodeKey,
|
|
394
|
+
{startColumn: number; colSpan: number}
|
|
395
|
+
>();
|
|
396
|
+
for (const mapRow of tableMap) {
|
|
397
|
+
for (const mapValue of mapRow) {
|
|
398
|
+
const key = mapValue.cell.getKey();
|
|
399
|
+
if (!cellValues.has(key)) {
|
|
400
|
+
cellValues.set(key, {
|
|
401
|
+
colSpan: mapValue.cell.getColSpan(),
|
|
402
|
+
startColumn: mapValue.startColumn,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// scan the DOM to find the table cell keys that were used and mark those columns
|
|
409
|
+
const knownColumns = new Set<number>();
|
|
410
|
+
for (const cellDOM of tableElement.querySelectorAll(
|
|
411
|
+
':scope > tr > [data-temporary-table-cell-lexical-key]',
|
|
412
|
+
)) {
|
|
413
|
+
const key = cellDOM.getAttribute(
|
|
414
|
+
'data-temporary-table-cell-lexical-key',
|
|
415
|
+
);
|
|
416
|
+
if (key) {
|
|
417
|
+
const cellSpan = cellValues.get(key);
|
|
418
|
+
cellDOM.removeAttribute('data-temporary-table-cell-lexical-key');
|
|
419
|
+
if (cellSpan) {
|
|
420
|
+
cellValues.delete(key);
|
|
421
|
+
for (let i = 0; i < cellSpan.colSpan; i++) {
|
|
422
|
+
knownColumns.add(i + cellSpan.startColumn);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Compute the colgroup and columns in the export
|
|
429
|
+
const colGroup = tableElement.querySelector(':scope > colgroup');
|
|
430
|
+
if (colGroup) {
|
|
431
|
+
// Only include the <col /> for rows that are in the output
|
|
432
|
+
const cols = Array.from(
|
|
433
|
+
tableElement.querySelectorAll(':scope > colgroup > col'),
|
|
434
|
+
).filter((dom, i) => knownColumns.has(i));
|
|
435
|
+
colGroup.replaceChildren(...cols);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Wrap direct descendant rows in a tbody for export
|
|
439
|
+
const rows = tableElement.querySelectorAll(':scope > tr');
|
|
440
|
+
if (rows.length > 0) {
|
|
441
|
+
const tBody = document.createElement('tbody');
|
|
442
|
+
for (const row of rows) {
|
|
443
|
+
tBody.appendChild(row);
|
|
444
|
+
}
|
|
445
|
+
tableElement.append(tBody);
|
|
446
|
+
}
|
|
447
|
+
return tableElement;
|
|
448
|
+
},
|
|
449
|
+
element:
|
|
450
|
+
!isHTMLTableElement(element) && isHTMLElement(element)
|
|
451
|
+
? element.querySelector('table')
|
|
452
|
+
: element,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
canBeEmpty(): false {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
isShadowRoot(): boolean {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
getCordsFromCellNode(
|
|
465
|
+
tableCellNode: TableCellNode,
|
|
466
|
+
table: TableDOMTable,
|
|
467
|
+
): {x: number; y: number} {
|
|
468
|
+
const {rows, domRows} = table;
|
|
469
|
+
|
|
470
|
+
for (let y = 0; y < rows; y++) {
|
|
471
|
+
const row = domRows[y];
|
|
472
|
+
|
|
473
|
+
if (row == null) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
for (let x = 0; x < row.length; x++) {
|
|
478
|
+
const cell = row[x];
|
|
479
|
+
if (cell == null) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const {elem} = cell;
|
|
483
|
+
const cellNode = $getNearestTableCellInTableFromDOMNode(this, elem);
|
|
484
|
+
if (cellNode !== null && tableCellNode.is(cellNode)) {
|
|
485
|
+
return {x, y};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
throw new Error('Cell not found in table.');
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
getDOMCellFromCords(
|
|
494
|
+
x: number,
|
|
495
|
+
y: number,
|
|
496
|
+
table: TableDOMTable,
|
|
497
|
+
): null | TableDOMCell {
|
|
498
|
+
const {domRows} = table;
|
|
499
|
+
|
|
500
|
+
const row = domRows[y];
|
|
501
|
+
|
|
502
|
+
if (row == null) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const index = x < row.length ? x : row.length - 1;
|
|
507
|
+
|
|
508
|
+
const cell = row[index];
|
|
509
|
+
|
|
510
|
+
if (cell == null) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return cell;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
getDOMCellFromCordsOrThrow(
|
|
518
|
+
x: number,
|
|
519
|
+
y: number,
|
|
520
|
+
table: TableDOMTable,
|
|
521
|
+
): TableDOMCell {
|
|
522
|
+
const cell = this.getDOMCellFromCords(x, y, table);
|
|
523
|
+
|
|
524
|
+
if (!cell) {
|
|
525
|
+
throw new Error('Cell not found at cords.');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return cell;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
getCellNodeFromCords(
|
|
532
|
+
x: number,
|
|
533
|
+
y: number,
|
|
534
|
+
table: TableDOMTable,
|
|
535
|
+
): null | TableCellNode {
|
|
536
|
+
const cell = this.getDOMCellFromCords(x, y, table);
|
|
537
|
+
|
|
538
|
+
if (cell == null) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const node = $getNearestNodeFromDOMNode(cell.elem);
|
|
543
|
+
|
|
544
|
+
if ($isTableCellNode(node)) {
|
|
545
|
+
return node;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
getCellNodeFromCordsOrThrow(
|
|
552
|
+
x: number,
|
|
553
|
+
y: number,
|
|
554
|
+
table: TableDOMTable,
|
|
555
|
+
): TableCellNode {
|
|
556
|
+
const node = this.getCellNodeFromCords(x, y, table);
|
|
557
|
+
|
|
558
|
+
if (!node) {
|
|
559
|
+
throw new Error('Node at cords not TableCellNode.');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return node;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
getRowStriping(): boolean {
|
|
566
|
+
return Boolean(this.getLatest().__rowStriping);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
setRowStriping(newRowStriping: boolean): this {
|
|
570
|
+
const self = this.getWritable();
|
|
571
|
+
self.__rowStriping = newRowStriping;
|
|
572
|
+
return self;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
setFrozenColumns(columnCount: number): this {
|
|
576
|
+
const self = this.getWritable();
|
|
577
|
+
self.__frozenColumnCount = columnCount;
|
|
578
|
+
return self;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
getFrozenColumns(): number {
|
|
582
|
+
return this.getLatest().__frozenColumnCount;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
setFrozenRows(rowCount: number): this {
|
|
586
|
+
const self = this.getWritable();
|
|
587
|
+
self.__frozenRowCount = rowCount;
|
|
588
|
+
return self;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
getFrozenRows(): number {
|
|
592
|
+
return this.getLatest().__frozenRowCount;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
canSelectBefore(): true {
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
canIndent(): false {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
getColumnCount(): number {
|
|
604
|
+
const firstRow = this.getFirstChild<TableRowNode>();
|
|
605
|
+
if (!firstRow) {
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let columnCount = 0;
|
|
610
|
+
firstRow.getChildren().forEach(cell => {
|
|
611
|
+
if ($isTableCellNode(cell)) {
|
|
612
|
+
columnCount += cell.getColSpan();
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
return columnCount;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export function $getElementForTableNode(
|
|
621
|
+
editor: LexicalEditor,
|
|
622
|
+
tableNode: TableNode,
|
|
623
|
+
): TableDOMTable {
|
|
624
|
+
const tableElement = editor.getElementByKey(tableNode.getKey());
|
|
625
|
+
invariant(
|
|
626
|
+
tableElement !== null,
|
|
627
|
+
'$getElementForTableNode: Table Element Not Found',
|
|
628
|
+
);
|
|
629
|
+
return getTable(tableNode, tableElement);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function $convertTableElement(
|
|
633
|
+
domNode: HTMLElement,
|
|
634
|
+
): DOMConversionOutput {
|
|
635
|
+
const tableNode = $createTableNode();
|
|
636
|
+
if (domNode.hasAttribute('data-lexical-row-striping')) {
|
|
637
|
+
tableNode.setRowStriping(true);
|
|
638
|
+
}
|
|
639
|
+
if (domNode.hasAttribute('data-lexical-frozen-column')) {
|
|
640
|
+
tableNode.setFrozenColumns(1);
|
|
641
|
+
}
|
|
642
|
+
if (domNode.hasAttribute('data-lexical-frozen-row')) {
|
|
643
|
+
tableNode.setFrozenRows(1);
|
|
644
|
+
}
|
|
645
|
+
const colGroup = domNode.querySelector(':scope > colgroup');
|
|
646
|
+
if (colGroup) {
|
|
647
|
+
let columns: number[] | undefined = [];
|
|
648
|
+
for (const col of colGroup.querySelectorAll(':scope > col')) {
|
|
649
|
+
let width = (col as HTMLElement).style.width || '';
|
|
650
|
+
if (!PIXEL_VALUE_REG_EXP.test(width)) {
|
|
651
|
+
// Also support deprecated width attribute for google docs
|
|
652
|
+
width = col.getAttribute('width') || '';
|
|
653
|
+
if (!/^\d+$/.test(width)) {
|
|
654
|
+
columns = undefined;
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
columns.push(parseFloat(width));
|
|
659
|
+
}
|
|
660
|
+
if (columns) {
|
|
661
|
+
tableNode.setColWidths(columns);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
after: children => $descendantsMatching(children, $isTableRowNode),
|
|
666
|
+
node: tableNode,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export function $createTableNode(): TableNode {
|
|
671
|
+
return $applyNodeReplacement(new TableNode());
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function $isTableNode(
|
|
675
|
+
node: LexicalNode | null | undefined,
|
|
676
|
+
): node is TableNode {
|
|
677
|
+
return node instanceof TableNode;
|
|
678
|
+
}
|