Data grid / foldkit-data-grid
Sixty seconds
ts
import { ColumnLayout, Columns, GridProjection, RowModel } from 'foldkit-data-grid'
interface Product {
readonly id: string
readonly sku: string
readonly price: number
}
const columns = Columns.define<Product>()({
sku: { header: 'SKU', value: product => product.sku, pinned: 'start' },
price: { header: 'Price', value: product => product.price },
})
const products: ReadonlyArray<Product> = [
{ id: 'p1', sku: 'A-1', price: 9 },
{ id: 'p2', sku: 'B-2', price: 2 },
]
const productKey = (product: Product) => product.id
const grid = GridProjection.make({
rows: RowModel.fromArray(products, productKey),
columns,
layout: ColumnLayout.initial(columns),
})
grid.columns // ['sku', 'price']
grid.moveBy({ row: 'p1', column: 'sku' }, { rows: 1, columns: 1 })
// Option.some({ row: 'p2', column: 'price' })
grid.box({ row: 'p2', column: 'price' }, { row: 'p1', column: 'sku' })
// Option.some({ rows: { start: 0, end: 2 }, columns: ['sku', 'price'] })What each call does:
Columns.define<Product>()({...})declares columns. Each key is a column's stable id, typed as a literal, and eachvaluekeeps its own type. It is called twice so the row type is given and the ids are inferred. It throws for an id that is a number, which JavaScript would enumerate first, and for__proto__.RowModel.fromArray(rows, key)reads an in-memory array. It indexes the keys once per array and key function, so keep both stable across renders, and it throws when two rows share a key. A paged or cursor source implements theRowModelinterface itself.RowModel.map(rows, input, make)transforms each row as it is read, for local state laid over rows a server sent.maketurnsinput(the edits, say) into the per-row function once perrowsandinput, and the result is the same model while both are, so the grid's projection is kept. Keys, count and order arerows' own.ColumnLayout.initial(columns)is the layout the columns declare. A layout is plain data, column ids only, so it can be saved.GridProjection.make(...)combines the three. It performs no I/O and holds no state; rebuild it when any input changes.
Movement and ranges
moveBy(address, { rows, columns }) steps in display order: it crosses from
the pinned start into the center, skips hidden columns, and stops at the
edges. rowStart, rowEnd, first and last jump to the edges.
box(anchor, focus) is the rectangle two cells span, from either corner, as
row indexes [start, end) and the columns it covers.
Every answer is an Option. A cell is absent when its column is hidden, its
row is gone, or its row is counted but not loaded yet.