Data grid / foldkit-mixins-data-grid

Sixty seconds

ts
import { Schema } from 'effect'
import type { HtmlBuilder } from 'foldkit/html'
import { defineMessageUnion } from 'foldkit/message'
import { Bundle } from 'foldkit-bundle'
import { Columns, DataGrid, RowModel } from 'foldkit-data-grid'
import { Style } from 'foldkit-mixins'
import { DataGridView, GridStyle } from 'foldkit-mixins-data-grid'

interface Item {
  readonly id: string
  readonly name: string
}
const items: ReadonlyArray<Item> = [
  { id: 'a', name: 'Anchor' },
  { id: 'b', name: 'Bolt' },
]
const itemKey = (item: Item) => item.id

const columns = Columns.define<Item>()({
  id: { header: 'Id', value: item => item.id, pinned: 'start', width: 96 },
  name: { header: 'Name', value: item => item.name, width: 240 },
})
const Grid = DataGrid.make({ id: 'items', columns })

// The grid's state is one field of the application's Model.
const Placement = Bundle.declare(Grid.bundle, 'grid')
const Model = Schema.Struct({ ...Placement.fields })
type Model = typeof Model.Type
const Message = defineMessageUnion({ ...Placement.cases })
type Message = typeof Message.Type
const application = Bundle.assemble<Model, Message>()([
  // A grid reports edits as an OutMessage; this one edits nothing, so it ignores them.
  Bundle.parent({ Model, Message }).at(Placement, { onOut: Bundle.ignore }),
])

const ItemsGrid = DataGridView<Message>().define(Grid).pipe(Style.attach(GridStyle))
const rows = RowModel.fromArray(items, itemKey)

const view = (model: Model, h: HtmlBuilder<Message>) =>
  ItemsGrid(
    {
      state: model.grid,
      rows,
      wrap: message => Placement.wrapper.make(message),
      label: 'Items',
      rowHeight: 32,
      headerHeight: 36,
    },
    h,
  )

application.initial(...) and application.update() run the grid's state like any other placement; view draws it. Give the grid a height with a Style on root (height: '24rem', say): it is the scroll container.

  • DataGridView<Message>().define(Grid) is a SlotView for that grid. Its row and column types come from the grid, so a cell renderer and the focused cell are checked against them.
  • state is the placed DataGrid Model. The view reads the focused cell, the viewport and the column state from it, and works out the window with VirtualGrid.window.
  • rows is the application's rows, in its order; the view draws them through the column state (Grid.project) and never sorts. Keep the rows model between renders, or the projection is rebuilt every time.
  • wrap lifts the grid's Messages into the application's: keys send Moved (with the scroll that reveals the cell), a pressed cell sends Focused, and the scroll container sends Measured.
  • GridStyle is the default look, in the components layer.