npm package: @markwhen/codemirror-tables
Description: Rich WYSIWYG table editing extension for CodeMirror 6
Author: Rob Koch
Latest version: 0.1.1

# @markwhen/codemirror-tables

A standalone CodeMirror 6 extension for rendering and editing GitHub Flavored Markdown (GFM) tables with an interactive widget interface.

Originally extracted from [Joplin's](https://joplinapp.org/) rich markdown editor.

## Features

- **Visual Table Rendering**: Markdown tables are rendered as interactive HTML tables
- **Inline Cell Editing**: Click any cell to edit its contents with a nested CodeMirror editor
- **Floating Toolbar**: Context-aware toolbar with table manipulation commands
- **Keyboard Navigation**: Tab, Shift+Tab, Enter, and arrow keys for cell navigation
- **Table Operations**: Insert/delete rows and columns, change alignment, apply formatting
- **Markdown Support**: Full markdown rendering in cells (bold, italic, links, code, etc.)

## Installation

```bash
npm install @markwhen/codemirror-tables
```

## Basic Usage

```typescript
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { markdown } from "@codemirror/lang-markdown";
import { tableExtension } from "@markwhen/codemirror-tables";

const state = EditorState.create({
  doc: `# My Document

| Name  | Age | City |
|-------|-----|------|
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |

Some text after the table.`,
  extensions: [
    markdown(),
    tableExtension(),
  ],
});

const view = new EditorView({
  state,
  parent: document.getElementById("editor"),
});
```

## Configuration

The `createTableExtension` function accepts an optional configuration object:

```typescript
import { createTableExtension } from "@markwhen/codemirror-tables";
import { keymap } from "@codemirror/view";
import { myCustomTheme } from "./myTheme";

const extensions = [
  markdown(),
  createTableExtension({
    // Custom link click handler (default opens in new tab)
    onLinkClick: (href: string) => {
      window.open(href, "_blank");
    },
    
    // Additional extensions for nested cell editors
    cellEditorExtensions: [
      myCustomTheme,
      keymap.of([/* custom keybindings */]),
    ],
    
    // Enable debug logging
    debug: false,
  }),
];
```

### Configuration Options

| Option | Type | Description |
|--------|------|-------------|
| `onLinkClick` | `(href: string) => void` | Custom handler for link clicks within table cells |
| `cellEditorExtensions` | `Extension \| Extension[]` | Additional CodeMirror extensions for nested cell editors (themes, keymaps, etc.) |
| `debug` | `boolean` | Enable debug logging (default: `false`) |

## Keyboard Shortcuts

When editing a cell:

| Key | Action |
|-----|--------|
| `Tab` | Move to next cell (creates new row at end of table) |
| `Shift+Tab` | Move to previous cell |
| `Enter` | Move to cell below (creates new row at end of table) |
| `Shift+Enter` | Insert line break in cell |
| `Escape` | Close cell editor |
| `Arrow Keys` | Navigate between cells (when at cell boundary) |

## Toolbar Actions

The floating toolbar appears above a table when a cell is being edited:

- **Row Operations**: Insert row above/below, delete row
- **Column Operations**: Insert column left/right, delete column
- **Alignment**: Align column left, center, or right
- **Formatting**: Bold, italic, code, strikethrough, link (applied to selected text in cell)

## API Reference

### Main Extension

```typescript
import { tableExtension } from "@markwhen/codemirror-tables";

// Returns an Extension array to add to your EditorState
const extensions = tableExtension(config?);
```

### Commands

Execute table operations programmatically:

```typescript
import {
  execInsertRowAbove,
  execInsertRowBelow,
  execDeleteRow,
  execInsertColumnLeft,
  execInsertColumnRight,
  execDeleteColumn,
  execSetColumnAlignment,
  execToggleBoldAtCursor,
  execToggleItalicAtCursor,
  execToggleCodeAtCursor,
  execToggleStrikethroughAtCursor,
  execToggleLinkAtCursor,
} from "@markwhen/codemirror-tables";

// Commands take the parent EditorView
execInsertRowBelow(view);
execSetColumnAlignment(view, "center"); // "left" | "center" | "right"
```

### State Management

```typescript
import {
  activeCellState,
  setActiveCell,
  clearActiveCell,
  getActiveCell,
} from "@markwhen/codemirror-tables";

// Get current active cell
const activeCell = getActiveCell(view.state);
if (activeCell) {
  console.log(`Editing row ${activeCell.row}, col ${activeCell.col}`);
}

// Dispatch to set/clear active cell
view.dispatch({ effects: setActiveCell.of({ tableId, row, col }) });
view.dispatch({ effects: clearActiveCell.of(null) });
```

### Table Parsing & Manipulation

```typescript
import {
  parseMarkdownTable,
  serializeTable,
  computeMarkdownTableCellRanges,
  insertRow,
  deleteRow,
  insertColumn,
  deleteColumn,
  updateColumnAlignment,
} from "@markwhen/codemirror-tables";

// Parse markdown table text
const tableText = "| A | B |\n|---|---|\n| 1 | 2 |";
const tableData = parseMarkdownTable(tableText);
// { headers: ['A', 'B'], alignments: [null, null], rows: [['1', '2']] }

// Manipulate the table
let table = tableData;
table = insertRow(table, 0, "after");
table = insertColumn(table, 1, "before");
table = updateColumnAlignment(table, 0, "center");

// Serialize back to markdown
const newMarkdown = serializeTable(table);
```

### Types

```typescript
import type {
  TableConfig,
  ActiveCell,
  TableCellRanges,
  CellRange,
  ColumnAlignment,
  ParsedTable,
} from "@markwhen/codemirror-tables";

interface TableConfig {
  onLinkClick?: (href: string) => void;
}

interface ActiveCell {
  tableId: string;
  row: number;  // -1 for header row
  col: number;
}

type ColumnAlignment = "left" | "center" | "right" | null;
```

## Styling

The package includes default styles. You can customize the appearance by overriding these CSS classes:

```css
/* Table widget container */
.cm-table-widget { }

/* The table element */
.cm-table-widget table { }

/* Header cells */
.cm-table-widget th { }

/* Data cells */
.cm-table-widget td { }

/* Cell being edited */
.cm-table-widget .cell-editing { }

/* Floating toolbar */
.cm-table-toolbar { }

/* Toolbar buttons */
.cm-table-toolbar button { }
.cm-table-toolbar button:hover { }

/* Toolbar dividers */
.cm-table-toolbar .toolbar-divider { }
```

## Integration with Collaboration

The extension uses `syncAnnotation` to mark internal transactions that synchronize nested editor state with the parent document. When integrating with collaborative editing (e.g., `@codemirror/collab`), you may want to filter these:

```typescript
import { syncAnnotation } from "@markwhen/codemirror-tables";

// In your collab integration
for (const tr of update.transactions) {
  if (tr.annotation(syncAnnotation)) {
    // Skip internal sync transactions
    continue;
  }
  // Process for collaboration...
}
```

## Peer Dependencies

This package requires the following CodeMirror packages:

- `@codemirror/state` ^6.0.0
- `@codemirror/view` ^6.0.0
- `@codemirror/language` ^6.0.0
- `@codemirror/commands` ^6.0.0
- `@codemirror/lang-markdown` ^6.0.0
- `@lezer/markdown` ^1.0.0

And uses:
- `@floating-ui/dom` - For toolbar positioning
- `style-mod` - For CSS-in-JS styling

## License

MIT