@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.
Files changed (37) hide show
  1. package/{LexicalTable.dev.js → dist/LexicalTable.dev.js} +268 -33
  2. package/{LexicalTable.dev.mjs → dist/LexicalTable.dev.mjs} +264 -33
  3. package/{LexicalTable.mjs → dist/LexicalTable.mjs} +4 -0
  4. package/{LexicalTable.node.mjs → dist/LexicalTable.node.mjs} +4 -0
  5. package/dist/LexicalTable.prod.js +9 -0
  6. package/dist/LexicalTable.prod.mjs +9 -0
  7. package/dist/TableImportExtension.d.ts +41 -0
  8. package/{index.d.ts → dist/index.d.ts} +1 -0
  9. package/package.json +34 -18
  10. package/src/LexicalTableCellNode.ts +479 -0
  11. package/src/LexicalTableCommands.ts +27 -0
  12. package/src/LexicalTableExtension.ts +104 -0
  13. package/src/LexicalTableNode.ts +678 -0
  14. package/src/LexicalTableObserver.ts +575 -0
  15. package/src/LexicalTablePluginHelpers.ts +694 -0
  16. package/src/LexicalTableRowNode.ts +154 -0
  17. package/src/LexicalTableSelection.ts +460 -0
  18. package/src/LexicalTableSelectionHelpers.ts +2409 -0
  19. package/src/LexicalTableUtils.ts +1386 -0
  20. package/src/TableImportExtension.ts +324 -0
  21. package/src/constants.ts +13 -0
  22. package/src/index.ts +97 -0
  23. package/LexicalTable.prod.js +0 -9
  24. package/LexicalTable.prod.mjs +0 -9
  25. /package/{LexicalTable.js → dist/LexicalTable.js} +0 -0
  26. /package/{LexicalTable.js.flow → dist/LexicalTable.js.flow} +0 -0
  27. /package/{LexicalTableCellNode.d.ts → dist/LexicalTableCellNode.d.ts} +0 -0
  28. /package/{LexicalTableCommands.d.ts → dist/LexicalTableCommands.d.ts} +0 -0
  29. /package/{LexicalTableExtension.d.ts → dist/LexicalTableExtension.d.ts} +0 -0
  30. /package/{LexicalTableNode.d.ts → dist/LexicalTableNode.d.ts} +0 -0
  31. /package/{LexicalTableObserver.d.ts → dist/LexicalTableObserver.d.ts} +0 -0
  32. /package/{LexicalTablePluginHelpers.d.ts → dist/LexicalTablePluginHelpers.d.ts} +0 -0
  33. /package/{LexicalTableRowNode.d.ts → dist/LexicalTableRowNode.d.ts} +0 -0
  34. /package/{LexicalTableSelection.d.ts → dist/LexicalTableSelection.d.ts} +0 -0
  35. /package/{LexicalTableSelectionHelpers.d.ts → dist/LexicalTableSelectionHelpers.d.ts} +0 -0
  36. /package/{LexicalTableUtils.d.ts → dist/LexicalTableUtils.d.ts} +0 -0
  37. /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
+ }