Skip to content

@jsvision/datagrid / EditableDataGridOptions

Interface: EditableDataGridOptions<T>

Defined in: datagrid/src/grid.ts:70

Construction options for EditableDataGrid.

Type Parameters

T

T

Properties

assignKey?

readonly optional assignKey?: (clone, original) => T

Defined in: datagrid/src/grid.ts:280

Mint the fresh key for EditableDataGrid.duplicateRow — the caller owns key generation. It receives a structured clone of the original row plus the original, and returns the row to insert (typically the clone with a new rowKey). Without it, duplicateRow is a no-op (it never inserts a key-colliding row).

Parameters

clone

T

original

T

Returns

T

Example

ts
import { signal } from '@jsvision/ui';
import { column, fromRows, EditableDataGrid } from '@jsvision/datagrid';

interface Row { id: number; name: string }
const rows = signal<Row[]>([{ id: 1, name: 'Ada' }]);
const columns = [column({ id: 'name', title: 'Name', value: (r: Row) => r.name })];
const source = fromRows(rows, { rowKey: (r) => r.id });

let nextId = 1000;
const grid = new EditableDataGrid<Row>({ columns, source, assignKey: (clone) => ({ ...clone, id: nextId++ }) });

beforeSave?

readonly optional beforeSave?: BeforeSave<T>

Defined in: datagrid/src/grid.ts:166

A per-cell gate that runs above onCommit: after the optimistic in-memory write and before onCommit. Return true to proceed to onCommit, or false/a rejected promise to veto — a veto reverts the cell to its previous value, surfaces a rejection message, and onCommit is never called. Use it for a policy check (permission, a business rule) that should short-circuit persistence. Client-side gating is UX only — the authoritative check still belongs in onCommit/the source.

Example

ts
import { signal } from '@jsvision/ui';
import { column, fromRows, EditableDataGrid } from '@jsvision/datagrid';

interface Row { id: number; name: string; locked?: boolean }
const rows = signal<Row[]>([{ id: 1, name: 'Ada', locked: true }]);
const columns = [column({ id: 'name', title: 'Name', value: (r: Row) => r.name })];
const source = fromRows(rows, { rowKey: (r) => r.id });

// Block edits to a locked row before they ever reach onCommit:
const grid = new EditableDataGrid<Row>({ columns, source, beforeSave: (c) => !c.row.locked });

checkboxColumn?

readonly optional checkboxColumn?: boolean

Defined in: datagrid/src/grid.ts:91

Show a leading selection checkbox column (default false): a per-row [ ]/[x] box plus a tri-state header box (none/some/all of the displayed rows). It is a fixed-width, left-pinned cell — not a sortable/filterable column and never reached by the / cursor. A per-row click toggles the row; the header box selects/clears all displayed rows.


columns

readonly columns: GridColumn<T, unknown>[]

Defined in: datagrid/src/grid.ts:72

The typed columns (authored with column()); adapted to the engine internally.


density?

readonly optional density?: "compact" | "normal"

Defined in: datagrid/src/grid.ts:235

Row density (default 'normal'). 'compact' drops the inter-column divider, reclaiming its cell per column so content packs tighter (the header, body, and quick-filter all reflow together and stay aligned). Horizontal only — rows are 1 cell tall in either mode.


emptyText?

readonly optional emptyText?: string

Defined in: datagrid/src/grid.ts:317

The message shown when the grid is ready with zero displayed rows and no active filter (default 'No rows'). When a filter has reduced a non-empty source to zero, the built-in 'No matching rows' is shown instead. Setting this (or status) opts the grid into the lifecycle empty state; a grid with neither keeps the plain <empty> body at zero rows.


filterPopup?

readonly optional filterPopup?: (ctx) => View

Defined in: datagrid/src/grid.ts:259

Replace the built-in condition-filter popup with a custom view. The factory receives a FilterPopupContext (the column, its filter type, the current filter, the value-list distinct thunk, the apply/clear/close sinks, and a defaultPopup() builder) and returns the view to mount. Call ctx.defaultPopup() to reuse or wrap the built-in popup; return your own view to replace it entirely. The returned view is mounted anchored under the column and clamped into the viewport, at the size it sets on its own layout (or the default popup size when it sets none); if it exposes a focusTarget() method that view is focused. Omit to use the built-in popup.

Parameters

ctx

FilterPopupContext<T>

Returns

View

Example

ts
import { signal } from '@jsvision/ui';
import { column, fromRows, EditableDataGrid } from '@jsvision/datagrid';

interface Row { id: number; name: string }
const rows = signal<Row[]>([{ id: 1, name: 'Ada' }]);
const columns = [column({ id: 'name', title: 'Name', value: (r: Row) => r.name })];
const source = fromRows(rows, { rowKey: (r) => r.id });

// Reuse the built-in popup unchanged (equivalent to omitting the option):
const grid = new EditableDataGrid<Row>({ columns, source, filterPopup: (ctx) => ctx.defaultPopup() });

readonly optional footer?: GridFooter

Defined in: datagrid/src/grid.ts:286

An optional footer band: per-column aggregates (totals aligned under their columns, folded reactively over the displayed rows, honesty-labelled for a not-fully-loaded source) and/or a free-form widget row. See GridFooter. Omit for no footer.


freeze?

readonly optional freeze?: number

Defined in: datagrid/src/grid.ts:221

Shorthand for freezing the first N columns to the left (ignored when freezeLeft is set).


freezeLeft?

readonly optional freezeLeft?: string[]

Defined in: datagrid/src/grid.ts:217

Column ids to pin to the left (frozen) panel.


freezeRight?

readonly optional freezeRight?: string[]

Defined in: datagrid/src/grid.ts:219

Column ids to pin to the right (frozen) panel.


freezeRows?

readonly optional freezeRows?: number

Defined in: datagrid/src/grid.ts:229

Pin the first N data rows as a non-scrolling band directly below the header — the horizontal mirror of frozen columns. The scrolling body's window starts after them, so a pinned row never scrolls off or renders twice. Clamped so at least one scrolling row always remains (a value larger than the row count is reduced, with a dev warning). Composes with frozen columns: the top-left cell is pinned on both axes. Default 0 (no band).


i18n?

readonly optional i18n?: I18n

Defined in: datagrid/src/grid.ts:76

Translation service for package-owned text; defaults to an isolated English service.


keymap?

readonly optional keymap?: GridKeymap

Defined in: datagrid/src/grid.ts:215

A per-grid keyboard remap layered over the default binding table (see DEFAULT_KEYMAP). Each entry maps a chord ('ctrl+alt+shift+key') to a GridAction; a caller entry wins on a chord conflict, and the untouched defaults still fire. An entry naming an unknown action or a malformed chord is ignored (a dev warning, never thrown), so a typo can never break construction. Omit to use the defaults.

Example

ts
import { signal } from '@jsvision/ui';
import { column, fromRows, EditableDataGrid } from '@jsvision/datagrid';

interface Row { id: number; name: string }
const rows = signal<Row[]>([{ id: 1, name: 'Ada' }]);
const columns = [column({ id: 'name', title: 'Name', value: (r: Row) => r.name })];
const source = fromRows(rows, { rowKey: (r) => r.id });

// Ctrl+E also begins editing (F2 still works); an unknown chord is ignored.
const grid = new EditableDataGrid<Row>({ columns, source, keymap: { 'ctrl+e': 'beginEdit' } });

onCommit?

readonly optional onCommit?: OnCommit<T>

Defined in: datagrid/src/grid.ts:103

The per-cell veto sink — accept or reject each edit (see OnCommit).


onRevertRow?

readonly optional onRevertRow?: OnRevertRow<T>

Defined in: datagrid/src/grid.ts:144

Atomic persistence sink for restoring every committed cell in a trapped row session.

The callback receives the original row after all session baselines are applied and one immutable changed-cell list in first-commit order. Return false, throw, or reject to compensate the row to its committed pre-revert values. The session remains available for retry only while the same row and session remain live and compensation completes. Stale settlement still compensates its captured row but cannot reattach retry state.

When this callback is absent, a grid with no beforeSave or onCommit may revert locally. A grid with either per-cell persistence hook refuses rollback without this row-level transaction seam so the UI cannot diverge from host persistence.

The default English message band reports Reverting row… while the callback is pending, Could not revert row changes after a veto or failure, and Row changes cannot be reverted when persisted edits have no row-level transaction seam. A trapped row's validation hint ends with Esc reverts row changes. Supply an i18n service to translate these messages.

Example

ts
import { signal } from '@jsvision/ui';
import { column, EditableDataGrid, fromRows } from '@jsvision/datagrid';

interface Line { id: number; start: number; end: number }
const rows = signal<Line[]>([{ id: 1, start: 1, end: 9 }]);
const columns = [column({
  id: 'start',
  title: 'Start',
  value: (row: Line) => row.start,
  parse: (text) => Number(text),
  set: (row, value) => { row.start = value; },
})];
const grid = new EditableDataGrid<Line>({
  columns,
  source: fromRows(rows, { rowKey: (row) => row.id }),
  validateRow: (row) => row.end > row.start ? { ok: true } : { ok: false, field: 'start' },
  onRevertRow: async () => true,
});

prefetch?

readonly optional prefetch?: number

Defined in: datagrid/src/grid.ts:325

Prefetch buffer size in rows on each side of the visible window, for a windowed source (one exposing ensureRange). As the grid scrolls it requests [top − prefetch, top + visible + prefetch), coalesced to at most one call per frame. Unset ⇒ one viewport (the current visible row count, resolved per draw) — there is no static default because the viewport height is not known at construction. Ignored for an eager in-memory source.


quickFilter?

readonly optional quickFilter?: boolean

Defined in: datagrid/src/grid.ts:101

Show the opt-in quick-filter row — a band of per-column text inputs below the header that drive a live contains filter as you type (default false; the band is never built when off).


rowNumbers?

readonly optional rowNumbers?: boolean

Defined in: datagrid/src/grid.ts:96

Show a leading row-number gutter (default false): 1-based, right-aligned display numbers that renumber whenever the display re-derives (after a sort/filter). Left-pinned and display-only.


selectionMode?

readonly optional selectionMode?: SelectionMode

Defined in: datagrid/src/grid.ts:84

Row selection mode (default 'multi'). 'single' keeps at most one row selected — each pick replaces the prior; 'multi' accumulates (Space/Ctrl+click toggle, Shift extends a range). Selection gestures are always live; the checkbox column and row-number gutter are separately opt-in.


source

readonly source: GridDataSource<T>

Defined in: datagrid/src/grid.ts:74

The data source (carries the required rowKey).


status?

readonly optional status?: () => GridStatus

Defined in: datagrid/src/grid.ts:310

A caller-driven reactive lifecycle status. Return 'loading' to show a spinner (the header stays, the rows hide), 'ready' to show the grid, or { kind: 'error', message, retry? } to show the message and a Retry button (clicking it calls retry). The empty state is auto-derived: when ready with zero displayed rows the grid shows emptyText (or the filter-aware 'No matching rows'). Evaluated in the grid's reactive scope, so flipping the value it reads swaps the view. Omit for an always-ready grid (a zero-row grid then shows the plain <empty> body).

Returns

GridStatus

Example

ts
import { signal } from '@jsvision/ui';
import { column, fromRows, EditableDataGrid } from '@jsvision/datagrid';

interface Row { id: number; name: string }
const rows = signal<Row[]>([{ id: 1, name: 'Ada' }]);
const columns = [column({ id: 'name', title: 'Name', value: (r: Row) => r.name })];
const source = fromRows(rows, { rowKey: (r) => r.id });

const state = signal<'loading' | 'ready'>('loading');
const grid = new EditableDataGrid<Row>({ columns, source, status: () => state() });
// later: state.set('ready');

validateRow?

readonly optional validateRow?: (row) => RowValidation

Defined in: datagrid/src/grid.ts:194

A per-row cross-field gate that runs when the cursor leaves a row that was edited this visit (a cell in it committed). Return { ok: true } to allow the leave, or { ok: false, message?, field? } to block it: the cursor stays on the row, refocuses the field column (the offending field), and message surfaces in the message band. An untouched row — even a pre-existing invalid one — leaves freely; a row that once passes will not re-trap. Use it for cross-field rules a single cell cannot check (e.g. end after start). Client-side gating is UX only — the source stays authoritative.

Parameters

row

T

Returns

RowValidation

Example

ts
import { signal } from '@jsvision/ui';
import { column, fromRows, EditableDataGrid } from '@jsvision/datagrid';

interface Booking { id: number; start: number; end: number }
const rows = signal<Booking[]>([{ id: 1, start: 9, end: 17 }]);
const columns = [
  column({ id: 'start', title: 'Start', value: (r: Booking) => r.start }),
  column({ id: 'end', title: 'End', value: (r: Booking) => r.end }),
];
const source = fromRows(rows, { rowKey: (r) => r.id });

const grid = new EditableDataGrid<Booking>({
  columns, source,
  validateRow: (r) => (r.end > r.start ? { ok: true } : { ok: false, message: 'End must be after start', field: 'end' }),
});

zebra?

readonly optional zebra?: boolean

Defined in: datagrid/src/grid.ts:78

Stripe odd rows for readability (default false).