@jsvision/datagrid / EditableDataGridOptions
Interface: EditableDataGridOptions<T>
Defined in: datagrid/src/grid.ts:70
Construction options for EditableDataGrid.
Type Parameters
T
T
Properties
assignKey?
readonlyoptionalassignKey?: (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
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?
readonlyoptionalbeforeSave?: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
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?
readonlyoptionalcheckboxColumn?: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
readonlycolumns:GridColumn<T,unknown>[]
Defined in: datagrid/src/grid.ts:72
The typed columns (authored with column()); adapted to the engine internally.
density?
readonlyoptionaldensity?:"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?
readonlyoptionalemptyText?: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?
readonlyoptionalfilterPopup?: (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
Returns
View
Example
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() });footer?
readonlyoptionalfooter?: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?
readonlyoptionalfreeze?:number
Defined in: datagrid/src/grid.ts:221
Shorthand for freezing the first N columns to the left (ignored when freezeLeft is set).
freezeLeft?
readonlyoptionalfreezeLeft?:string[]
Defined in: datagrid/src/grid.ts:217
Column ids to pin to the left (frozen) panel.
freezeRight?
readonlyoptionalfreezeRight?:string[]
Defined in: datagrid/src/grid.ts:219
Column ids to pin to the right (frozen) panel.
freezeRows?
readonlyoptionalfreezeRows?: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?
readonlyoptionali18n?:I18n
Defined in: datagrid/src/grid.ts:76
Translation service for package-owned text; defaults to an isolated English service.
keymap?
readonlyoptionalkeymap?: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
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?
readonlyoptionalonCommit?:OnCommit<T>
Defined in: datagrid/src/grid.ts:103
The per-cell veto sink — accept or reject each edit (see OnCommit).
onRevertRow?
readonlyoptionalonRevertRow?: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
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?
readonlyoptionalprefetch?: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?
readonlyoptionalquickFilter?: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?
readonlyoptionalrowNumbers?: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?
readonlyoptionalselectionMode?: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
readonlysource:GridDataSource<T>
Defined in: datagrid/src/grid.ts:74
The data source (carries the required rowKey).
status?
readonlyoptionalstatus?: () =>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
Example
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?
readonlyoptionalvalidateRow?: (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
Example
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?
readonlyoptionalzebra?:boolean
Defined in: datagrid/src/grid.ts:78
Stripe odd rows for readability (default false).