or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collaboration.mdcommands-and-editing.mdcursors-and-enhancements.mdhistory.mdindex.mdinput-and-keymaps.mdmarkdown.mdmenus-and-ui.mdmodel-and-schema.mdschema-definitions.mdstate-management.mdtables.mdtransformations.mdview-and-rendering.md

tables.mddocs/

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

```