DEVELOPER GUIDE / DEVELOPMENT PREVIEW

From rows to a working grid.

The core owns state. The renderer owns interactions. Your application owns storage.

1. Build from source

Packages are not published to npm. Use Node.js 22 or later and a checkout of the repository.

git clone https://github.com/haodn-dev/acheron-grid.git
cd acheron-grid
npm ci
npm run build
npm run playground

For a separate application, install the built packages through local file paths with your package manager.

npm install /path/to/acheron-grid/packages/core /path/to/acheron-grid/packages/canvas
The headless TypeScript core manages state and commands. Canvas renders cells and handles input. React, Vue or vanilla TypeScript connects the grid to your application.

2. Mount a grid

The container needs a non-zero width and height. Use stable row identities and explicit column keys.

import { LocalDataSource } from '@acheron-grid/core';
import { createGrid } from '@acheron-grid/canvas';

const source = new LocalDataSource([
  { id: 1, title: 'Hello grid' }
], row => row.id);
const grid = createGrid({
  container: document.querySelector('#grid'),
  columns: [{ key: 'title', title: 'Title', editable: true }],
  dataSource: source
});
// On unmount:
grid.destroy();

3. Configure interactions

Define column parsers to validate user inputs. Use capability permissions for edit, selection, clipboard and formatting separately. Custom cell renderers receive values and format metadata; custom option renderers receive the column key.

  • Editing: Enter or F2 opens text and choice editors; Enter toggles checkbox cells.
  • Formatting: Ctrl/Cmd+B and I format the selected cells. During rich text editing, they format the text selection.
  • Links: hover or select a cell to see one shortcut per safe link.
  • Structure: select whole rows or columns to reorder, insert or delete through available context actions.
  • Rich text: HTML is projected into safe text runs; Markdown uses the optional parser adapter.

See the Canvas package documentation for the full option contracts. The repository’s default branch may differ from your local preview.

Editors and validation

Use column parsers to validate text before committing or pasting. Throw a descriptive error to keep the draft available for correction. API updates accept already-validated values; they do not run column parsers.

columnEditors: {
  priority: { type: 'select', values: ['High', 'Medium', 'Low'] },
  tags: { type: 'multiselect', values: ['Idea', 'Design', 'Content'] },
  approved: { type: 'checkbox' }
},
choiceEditor: { placeholder: 'Find an option…', maxHeight: 220 },
multilineEditor: true,
wrapText: true,
editorOptions: { pinned: true, showLabel: 'scroll', guardNavigation: true }

These are construction options alongside your columns and data source. Multiselect labels serialize as comma-separated strings and cannot contain commas. Up/Down navigates choices, Space toggles a multiselect option, Enter/Apply saves and Escape/Cancel discards. Custom option and cell renderers should share their color palette.

Try editors & validation ↗

Searchable context menu

Right-click a cell, row index or header, or press Shift+F10. Type while the menu is open to filter its actions. Use arrow keys and Enter to choose, and Escape to close. Available actions depend on selection, permissions and configuration: clipboard, whole-row/column selection, formatting, locks, freeze, sizing, insert/delete/move, merges and groups. Header menus provide sort and filter; disabled actions explain restrictions.

Try the searchable menu ↗ · Try sort and filter ↗

Selection and clipboard

Click and Shift-click to select a rectangle. Ctrl/Cmd-click adds another range. Row indexes and grouped headers select whole spans. Shift+Space selects rows; Ctrl/Cmd+Space selects columns. Shift+F10 opens a context menu that filters as you type.

grid.selectRow(0);
const tsv = grid.copySelection();
grid.paste(tsv);

Paste checks all destinations, permissions and parsers before one atomic command. It does not create missing rows. Plain TSV exchanges values; native grid clipboard and structured APIs can retain rich styles and range offsets. Do not assume every external application preserves the same formats. Payloads are limited to 100,000 cells and 10 million UTF-16 code units.

Try selection & keyboard ↗

Values, events and history

grid.updateCells([
  { rowIndex: 0, columnKey: 'title', value: 'Lorem ipsum' }
]);
grid.undo();
grid.redo();

Use stable row IDs and column keys. LocalDataSource supports synchronous atomic batches; custom sources need an atomic setValues for multiple-cell writes. Value edits, formatting, explicit resize/freeze and structural commands share bounded history. Locks stay outside history; automatic row measurement is not a user command. Replay rechecks current policy and can reject conflicting external changes.

Use onEvent to observe committed domain events such as cell:change and structure:change. Notifications do not roll back committed changes. Direct source writes bypass history; persistence belongs to your application.

Search, sorting and filtering

grid.openSearch();
grid.setView({
  sort: { columnKey: 'title', direction: 'asc' },
  filters: [{ columnKey: 'title', operator: 'contains', query: 'Lorem' }]
});
grid.setView({});

Search highlights displayed text. Local views sort one column and combine filters with AND; operators include contains, equals, not-empty and empty. If you supply onViewChange, also set viewMode: 'core' for state-preserving internal views. Public row coordinates refer to display order; permission resolvers and non-selection domain events use source coordinates and stable IDs.

Clear views before structural operations; remove merges/groups before applying sort/filter. In-memory filtering and sorting still scale with source size.

Try search, sort & filter ↗

Grouped headers, freeze and sizing

headerGroups: [{ title: 'Availability', children: [
  { title: 'Channels', children: ['online', 'onsite'] },
  { title: 'Delivery', children: ['shipping'] }
] }],
headerHeight: 28,
autoRowHeight: true,
wrapText: true

Keep columns flat; grouped leaves must be unique, contiguous and in column order. Header height describes each header row. Ancestor headers receive tint when their leaves are selected.

grid.setFrozen(1, 1);
grid.setColumnWidth(1, 240);
grid.setRowHeight(0, 64);

Freeze counts exclude the header and index gutter. Resizing previews a guide and applies on release. Auto-fit measures visible content; automatic row height measures all columns of visible rows. Explicit sizes take precedence. Oversized frozen prefixes can consume the viewport.

Try headers, freeze & resize ↗

Structure, merges and row groups

Enable dragging through onReorder and apply the requested move with public APIs. beforeIndex describes an insertion boundary before removal. The host creates row IDs/default values through onRowChange. allowColumnChanges enables the built-in column creation/deletion menu.

onReorder: request => {
  if (request.axis === 'row') grid.moveRows(request.indices, request.beforeIndex);
  else grid.moveColumns(request.indices, request.beforeIndex);
}

The callback runs after mounting, when grid is available. Use canChangeStructure for policy checks that also apply to commands and history replay. Grouped columns must remain contiguous.

grid.mergeCells({ startRow: 2, endRow: 3, startColumn: 1, endColumn: 2 });
const groupId = grid.groupRows(3, 6);
grid.setGroupCollapsed(groupId, true);
grid.setGroupCollapsed(groupId, false);
grid.ungroupRows(groupId);

Range ends are inclusive. Merging retains underlying values for unmerge; row grouping creates manual outlines, not aggregation. Expand groups and unmerge intersecting cells before incompatible structural changes.

Try row/column operations ↗ · Try merges & groups ↗

Rich text, images and safe links

import { markdownToHtml } from '@acheron-grid/markdown';

// createGrid options:
richTextColumns: { notes: 'markdown', description: 'html' },
markdownToHtml,
imageColumns: ['avatar']

The Markdown adapter is optional; core and Canvas do not depend on its parser. Users see formatted content while the host stores source strings. HTML supports underline; Markdown does not add an underline syntax. Ctrl/Cmd+B or I formats selected words during rich-text editing or selected cells outside editing.

Canvas paints restricted text runs without mounting parsed HTML or executing embedded handlers. This is not a general sanitizer for inserting stored HTML into another DOM. Image columns request visible URLs with anonymous CORS; there is no upload/cropping feature. Hover or focus link cells to reveal safe per-link shortcuts.

Try rich text & links ↗

Permissions, locks and customization

resolveCellPermission: cell => cell.rowId === 'protected'
  ? { writable: false }
  : undefined,
permissions: { formatting: false }

Selection, copy, paste, editing, writing and formatting are independent capabilities. Explicit false vetoes remain effective at more specific scopes. Read-only rows may stay selectable/copyable. Dynamic policy changes need grid.render() to refresh the controls; editor commits recheck policy.

grid.setLocked({ scope: 'row', rowIndex: 0 }, true);
grid.format([{ scope: 'column', columnIndex: 1 }], { background: '#fff0dd' });
grid.setTheme({ selectionColor: '#d44b21', iconColor: '#334155' });

Locks target cell/row/column/table scopes. Unlocking a cell does not remove an applicable row or table lock. Freeze keeps content visible and does not deny edits. Theme updates retain the mount; renderCell and createEditor support application presentation. Custom renderers must honor the format fields they support. Client policy does not replace server authorization.

Try formatting & permissions ↗

Troubleshooting

  • Empty grid: check container dimensions, source row count, column keys and browser console.
  • Cannot edit: check editable flags, parsers, setters, resolved permissions and locks.
  • Markdown rejected: supply a synchronous markdownToHtml callback.
  • Image unavailable: check URL, CORS and the server response.
  • Move/view change rejected: check active drafts, applied views, groups, merges and host policy.
  • Missing undo: direct source mutation and automatic measurement bypass user history.

Always destroy the grid when its host unmounts. See the complete practical guide in Markdown and the package contracts for detailed limits.

React and Vue

The @acheron-grid/react and @acheron-grid/vue packages manage Canvas mount/unmount, typed refs and core event forwarding. Keep options stable; update theme, view and frozen panes through runtime props without remounting.

Read the React and Vue examples in Markdown for full components, peer versions and lifecycle contracts. Both adapters render an empty container during SSR; framework rendering stays outside the headless core.

4. Read with an AI assistant

Start with the public reading index, then the AI integration notes. Markdown includes concrete examples and implemented limits. No credentials or internal planning documents are exposed.

The optional MCP adapter now exposes documentation resources and host-approved read/update tools. Read the MCP reference for stdio setup and limits. The browser playground is not automatically connected.

Browse all five package references and AI notes ↗

API reference and deeper examples

This guide covers common workflows, not every option or method. Use the package documentation for full contracts, defaults, callback payloads and edge cases.

The current guide does not include a generated, searchable API reference or runnable examples for every custom renderer and editor. The repository documentation may reflect a different revision from this local preview.

Current limits

This is an experimental development preview. Remote data sources, collaboration, formulas and an automatic MCP browser bridge are not included. Demo changes are local to the current session. Rendering virtualization does not make in-memory filtering independent of dataset size.

Licensed under MIT by Hao Duong. No production performance claims or accessibility certification are made.