0
# Tables
1
2
The tables system provides comprehensive support for table editing with advanced features like cell selection, column resizing, and table manipulation commands. It handles complex table operations while maintaining document consistency.
3
4
## Capabilities
5
6
### Table Structure Classes
7
8
Core classes for table representation and manipulation.
9
10
```typescript { .api }
11
/**
12
* Maps table structure for efficient navigation and manipulation
13
*/
14
class TableMap {
15
/**
16
* Create table map from a table node
17
*/
18
static get(table: Node): TableMap;
19
20
/**
21
* Width of the table (number of columns)
22
*/
23
width: number;
24
25
/**
26
* Height of the table (number of rows)
27
*/
28
height: number;
29
30
/**
31
* Find the cell at given coordinates
32
*/
33
findCell(pos: ResolvedPos): Rect;
34
35
/**
36
* Get position of cell at coordinates
37
*/
38
positionAt(row: number, col: number, table: Node): number;
39
}
40
41
/**
42
* Represents table cell selection
43
*/
44
class CellSelection extends Selection {
45
/**
46
* Create cell selection between two cells
47
*/
48
static create(doc: Node, anchorCell: number, headCell?: number): CellSelection;
49
50
/**
51
* Selected rectangle coordinates
52
*/
53
$anchorCell: ResolvedPos;
54
$headCell: ResolvedPos;
55
56
/**
57
* Check if selection is a single cell
58
*/
59
isColSelection(): boolean;
60
isRowSelection(): boolean;
61
}
62
63
/**
64
* Table resize state management
65
*/
66
class ResizeState {
67
constructor(
68
activeHandle: number,
69
dragging: boolean,
70
startX: number,
71
startWidth: number
72
);
73
74
/**
75
* Apply resize to the table
76
*/
77
apply(tr: Transaction): Transaction;
78
}
79
```
80
81
### Table Node Definitions
82
83
Schema definitions for table structures.
84
85
```typescript { .api }
86
/**
87
* Get table node specifications
88
*/
89
function tableNodes(options?: TableNodesOptions): {
90
table: NodeSpec;
91
table_row: NodeSpec;
92
table_cell: NodeSpec;
93
table_header: NodeSpec;
94
};
95
96
/**
97
* Get node types from schema for table operations
98
*/
99
function tableNodeTypes(schema: Schema): {
100
table: NodeType;
101
table_row: NodeType;
102
table_cell: NodeType;
103
table_header: NodeType;
104
};
105
```
106
107
### Table Plugins
108
109
Plugins for table functionality.
110
111
```typescript { .api }
112
/**
113
* Create table editing plugin
114
*/
115
function tableEditing(): Plugin;
116
117
/**
118
* Create column resizing plugin
119
*/
120
function columnResizing(options?: ColumnResizingOptions): Plugin;
121
```
122
123
### Table Manipulation Commands
124
125
Commands for modifying table structure.
126
127
```typescript { .api }
128
/**
129
* Add a column after the current selection
130
*/
131
function addColumnAfter(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
132
133
/**
134
* Add a column before the current selection
135
*/
136
function addColumnBefore(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
137
138
/**
139
* Add a row after the current selection
140
*/
141
function addRowAfter(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
142
143
/**
144
* Add a row before the current selection
145
*/
146
function addRowBefore(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
147
148
/**
149
* Delete the selected column
150
*/
151
function deleteColumn(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
152
153
/**
154
* Delete the selected row
155
*/
156
function deleteRow(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
157
158
/**
159
* Delete the entire table
160
*/
161
function deleteTable(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
162
163
/**
164
* Merge selected cells
165
*/
166
function mergeCells(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
167
168
/**
169
* Split the current cell
170
*/
171
function splitCell(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
172
173
/**
174
* Split cell with specific cell type
175
*/
176
function splitCellWithType(getCellType: (schema: Schema) => NodeType): Command;
177
```
178
179
### Table Navigation Commands
180
181
Commands for moving within tables.
182
183
```typescript { .api }
184
/**
185
* Move to the next cell
186
*/
187
function goToNextCell(direction: number): Command;
188
189
/**
190
* Move cell selection forward
191
*/
192
function moveCellForward(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
193
194
/**
195
* Select the next cell
196
*/
197
function nextCell(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
198
```
199
200
### Header Toggle Commands
201
202
Commands for toggling header states.
203
204
```typescript { .api }
205
/**
206
* Toggle header state of selection
207
*/
208
function toggleHeader(type: "column" | "row" | "cell"): Command;
209
210
/**
211
* Toggle header state of selected cells
212
*/
213
function toggleHeaderCell(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
214
215
/**
216
* Toggle header state of selected column
217
*/
218
function toggleHeaderColumn(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
219
220
/**
221
* Toggle header state of selected row
222
*/
223
function toggleHeaderRow(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
224
```
225
226
### Table Query Functions
227
228
Utility functions for table inspection.
229
230
```typescript { .api }
231
/**
232
* Find cell around the given position
233
*/
234
function cellAround(pos: ResolvedPos): ResolvedPos | null;
235
236
/**
237
* Find cell near the given position
238
*/
239
function cellNear(pos: ResolvedPos): ResolvedPos | null;
240
241
/**
242
* Find cell at the given position
243
*/
244
function findCell(pos: ResolvedPos): Rect | null;
245
246
/**
247
* Get number of columns in table
248
*/
249
function colCount(pos: ResolvedPos): number;
250
251
/**
252
* Check if row is a header row
253
*/
254
function rowIsHeader(map: TableMap, table: Node, row: number): boolean;
255
256
/**
257
* Check if column is a header column
258
*/
259
function columnIsHeader(map: TableMap, table: Node, col: number): boolean;
260
261
/**
262
* Check if position is inside a table
263
*/
264
function isInTable(state: EditorState): boolean;
265
266
/**
267
* Check if two positions are in the same table
268
*/
269
function inSameTable(a: ResolvedPos, b: ResolvedPos): boolean;
270
271
/**
272
* Get selected rectangle in table
273
*/
274
function selectedRect(state: EditorState): Rect;
275
276
/**
277
* Check if position points directly at a cell
278
*/
279
function pointsAtCell(pos: ResolvedPos): boolean;
280
```
281
282
**Usage Examples:**
283
284
```typescript
285
import {
286
tableNodes,
287
tableEditing,
288
columnResizing,
289
addColumnAfter,
290
addRowAfter,
291
deleteColumn,
292
deleteRow,
293
mergeCells,
294
splitCell,
295
toggleHeaderRow,
296
CellSelection
297
} from "@tiptap/pm/tables";
298
import { keymap } from "@tiptap/pm/keymap";
299
300
// Create schema with table nodes
301
const nodes = {
302
...baseNodes,
303
...tableNodes({
304
cellContent: "block+",
305
cellAttributes: {
306
background: { default: null }
307
}
308
})
309
};
310
311
const schema = new Schema({ nodes, marks });
312
313
// Table plugins
314
const tablePlugins = [
315
tableEditing(),
316
columnResizing({
317
handleWidth: 5,
318
cellMinWidth: 50,
319
lastColumnResizable: true
320
})
321
];
322
323
// Table keymap
324
const tableKeymap = keymap({
325
"Tab": goToNextCell(1),
326
"Shift-Tab": goToNextCell(-1),
327
"Mod-Shift-\\": addColumnAfter,
328
"Mod-Shift-|": addRowAfter,
329
"Mod-Shift-Backspace": deleteColumn,
330
"Mod-Alt-Backspace": deleteRow,
331
"Mod-Shift-m": mergeCells,
332
"Mod-Shift-s": splitCell,
333
"Mod-Shift-h": toggleHeaderRow
334
});
335
336
// Create editor with table support
337
const state = EditorState.create({
338
schema,
339
plugins: [...tablePlugins, tableKeymap]
340
});
341
342
// Create a simple table
343
function insertTable(rows: number, cols: number) {
344
return (state: EditorState, dispatch?: (tr: Transaction) => void) => {
345
const { table, table_row, table_cell } = schema.nodes;
346
347
const cells = [];
348
for (let i = 0; i < cols; i++) {
349
cells.push(table_cell.createAndFill());
350
}
351
352
const tableRows = [];
353
for (let i = 0; i < rows; i++) {
354
tableRows.push(table_row.create(null, cells));
355
}
356
357
const tableNode = table.create(null, tableRows);
358
359
if (dispatch) {
360
dispatch(state.tr.replaceSelectionWith(tableNode));
361
}
362
363
return true;
364
};
365
}
366
367
// Table manipulation helper
368
class TableEditor {
369
constructor(private view: EditorView) {}
370
371
insertTable(rows: number = 3, cols: number = 3) {
372
const command = insertTable(rows, cols);
373
command(this.view.state, this.view.dispatch);
374
}
375
376
addColumn(after: boolean = true) {
377
const command = after ? addColumnAfter : addColumnBefore;
378
command(this.view.state, this.view.dispatch);
379
}
380
381
addRow(after: boolean = true) {
382
const command = after ? addRowAfter : addRowBefore;
383
command(this.view.state, this.view.dispatch);
384
}
385
386
deleteColumn() {
387
deleteColumn(this.view.state, this.view.dispatch);
388
}
389
390
deleteRow() {
391
deleteRow(this.view.state, this.view.dispatch);
392
}
393
394
mergeCells() {
395
mergeCells(this.view.state, this.view.dispatch);
396
}
397
398
splitCell() {
399
splitCell(this.view.state, this.view.dispatch);
400
}
401
402
selectColumn(col: number) {
403
const table = this.findTable();
404
if (table) {
405
const map = TableMap.get(table.node);
406
const anchor = table.start + map.positionAt(0, col, table.node) + 1;
407
const head = table.start + map.positionAt(map.height - 1, col, table.node) + 1;
408
409
const selection = CellSelection.create(this.view.state.doc, anchor, head);
410
this.view.dispatch(this.view.state.tr.setSelection(selection));
411
}
412
}
413
414
private findTable() {
415
const { $from } = this.view.state.selection;
416
for (let d = $from.depth; d > 0; d--) {
417
const node = $from.node(d);
418
if (node.type.name === "table") {
419
return { node, start: $from.start(d) };
420
}
421
}
422
return null;
423
}
424
}
425
```
426
427
## Advanced Table Features
428
429
### Custom Table Rendering
430
431
Create custom table views with enhanced functionality.
432
433
```typescript
434
import { TableView } from "@tiptap/pm/tables";
435
436
class CustomTableView extends TableView {
437
constructor(node: Node, cellMinWidth: number) {
438
super(node, cellMinWidth);
439
this.addCustomFeatures();
440
}
441
442
private addCustomFeatures() {
443
// Add row numbers
444
this.addRowNumbers();
445
446
// Add column letters
447
this.addColumnHeaders();
448
449
// Add context menu
450
this.addContextMenu();
451
}
452
453
private addRowNumbers() {
454
const rows = this.table.querySelectorAll("tr");
455
rows.forEach((row, index) => {
456
const numberCell = document.createElement("td");
457
numberCell.className = "row-number";
458
numberCell.textContent = String(index + 1);
459
row.insertBefore(numberCell, row.firstChild);
460
});
461
}
462
463
private addColumnHeaders() {
464
const firstRow = this.table.querySelector("tr");
465
if (firstRow) {
466
const cellCount = firstRow.children.length;
467
const headerRow = document.createElement("tr");
468
headerRow.className = "column-headers";
469
470
for (let i = 0; i < cellCount; i++) {
471
const headerCell = document.createElement("th");
472
headerCell.textContent = String.fromCharCode(65 + i); // A, B, C...
473
headerRow.appendChild(headerCell);
474
}
475
476
this.table.insertBefore(headerRow, this.table.firstChild);
477
}
478
}
479
480
private addContextMenu() {
481
this.table.addEventListener("contextmenu", (event) => {
482
event.preventDefault();
483
this.showContextMenu(event.clientX, event.clientY);
484
});
485
}
486
487
private showContextMenu(x: number, y: number) {
488
const menu = document.createElement("div");
489
menu.className = "table-context-menu";
490
menu.style.position = "fixed";
491
menu.style.left = x + "px";
492
menu.style.top = y + "px";
493
494
const menuItems = [
495
{ label: "Add Column After", action: () => addColumnAfter },
496
{ label: "Add Row After", action: () => addRowAfter },
497
{ label: "Delete Column", action: () => deleteColumn },
498
{ label: "Delete Row", action: () => deleteRow },
499
{ label: "Merge Cells", action: () => mergeCells }
500
];
501
502
menuItems.forEach(item => {
503
const menuItem = document.createElement("div");
504
menuItem.textContent = item.label;
505
menuItem.onclick = () => {
506
item.action()(this.view.state, this.view.dispatch);
507
menu.remove();
508
};
509
menu.appendChild(menuItem);
510
});
511
512
document.body.appendChild(menu);
513
514
// Remove menu on outside click
515
setTimeout(() => {
516
document.addEventListener("click", () => menu.remove(), { once: true });
517
});
518
}
519
}
520
```
521
522
### Table Import/Export
523
524
Handle table data conversion for different formats.
525
526
```typescript
527
class TableConverter {
528
// Import from CSV
529
static fromCSV(csv: string, schema: Schema): Node {
530
const { table, table_row, table_cell } = schema.nodes;
531
const rows = csv.split("\n").filter(row => row.trim());
532
533
const tableRows = rows.map(rowText => {
534
const cells = rowText.split(",").map(cellText => {
535
const content = cellText.trim();
536
return table_cell.create(null, content ? schema.text(content) : null);
537
});
538
return table_row.create(null, cells);
539
});
540
541
return table.create(null, tableRows);
542
}
543
544
// Export to CSV
545
static toCSV(tableNode: Node): string {
546
const rows: string[] = [];
547
548
tableNode.forEach(row => {
549
const cellTexts: string[] = [];
550
row.forEach(cell => {
551
cellTexts.push(cell.textContent.replace(/,/g, '\\,'));
552
});
553
rows.push(cellTexts.join(","));
554
});
555
556
return rows.join("\n");
557
}
558
559
// Import from HTML table
560
static fromHTML(html: string, schema: Schema): Node {
561
const parser = new DOMParser();
562
const doc = parser.parseFromString(html, "text/html");
563
const htmlTable = doc.querySelector("table");
564
565
if (!htmlTable) throw new Error("No table found in HTML");
566
567
const domParser = DOMParser.fromSchema(schema);
568
return domParser.parse(htmlTable);
569
}
570
571
// Export to HTML
572
static toHTML(tableNode: Node, schema: Schema): string {
573
const serializer = DOMSerializer.fromSchema(schema);
574
const dom = serializer.serializeNode(tableNode);
575
return dom.outerHTML;
576
}
577
}
578
```
579
580
### Table Analytics
581
582
Track and analyze table usage patterns.
583
584
```typescript
585
class TableAnalytics {
586
private metrics = {
587
cellCount: 0,
588
rowCount: 0,
589
columnCount: 0,
590
mergedCells: 0,
591
headerCells: 0
592
};
593
594
analyzeTable(tableNode: Node): TableMetrics {
595
const map = TableMap.get(tableNode);
596
597
this.metrics = {
598
cellCount: 0,
599
rowCount: map.height,
600
columnCount: map.width,
601
mergedCells: 0,
602
headerCells: 0
603
};
604
605
tableNode.descendants((node, pos) => {
606
if (node.type.name === "table_cell" || node.type.name === "table_header") {
607
this.metrics.cellCount++;
608
609
if (node.type.name === "table_header") {
610
this.metrics.headerCells++;
611
}
612
613
const colspan = node.attrs.colspan || 1;
614
const rowspan = node.attrs.rowspan || 1;
615
if (colspan > 1 || rowspan > 1) {
616
this.metrics.mergedCells++;
617
}
618
}
619
});
620
621
return { ...this.metrics };
622
}
623
624
getComplexityScore(tableNode: Node): number {
625
const metrics = this.analyzeTable(tableNode);
626
let score = 0;
627
628
// Base complexity from size
629
score += metrics.rowCount * metrics.columnCount * 0.1;
630
631
// Penalty for merged cells
632
score += metrics.mergedCells * 2;
633
634
// Bonus for proper headers
635
if (metrics.headerCells > 0) {
636
score += 1;
637
}
638
639
return Math.round(score * 10) / 10;
640
}
641
}
642
643
interface TableMetrics {
644
cellCount: number;
645
rowCount: number;
646
columnCount: number;
647
mergedCells: number;
648
headerCells: number;
649
}
650
```
651
652
## Types
653
654
```typescript { .api }
655
/**
656
* Table node options
657
*/
658
interface TableNodesOptions {
659
cellContent?: string;
660
cellAttributes?: { [key: string]: AttributeSpec };
661
}
662
663
/**
664
* Column resizing options
665
*/
666
interface ColumnResizingOptions {
667
handleWidth?: number;
668
cellMinWidth?: number;
669
lastColumnResizable?: boolean;
670
}
671
672
/**
673
* Rectangle representing table selection
674
*/
675
interface Rect {
676
left: number;
677
right: number;
678
top: number;
679
bottom: number;
680
}
681
682
/**
683
* Table cell position information
684
*/
685
interface CellInfo {
686
pos: number;
687
start: number;
688
node: Node;
689
}
690
```