@domphy/table
v0.19.2
Published
Domphy Table - headless table logic, a 1-1 port of @tanstack/table-core
Maintainers
Readme
@domphy/table
domphy.com · Docs · npm
Headless table logic for Domphy apps: sorting, filtering, pagination, row selection, grouping, expanding, column pinning, sizing, visibility, and faceting. Domphy owns the rendering — this package owns the table state.
Install
npm install @domphy/table@domphy/core is a peer dependency.
Quick start — createDomphyTable
Import from the /domphy subpath to get a reactive handle that Domphy elements subscribe to:
import { createDomphyTable } from "@domphy/table/domphy"
import { createColumnHelper, getCoreRowModel, getSortedRowModel } from "@domphy/table"
const columnHelper = createColumnHelper<Person>()
const columns = [
columnHelper.accessor("name", { header: "Name" }),
columnHelper.accessor("age", { header: "Age" }),
]
const dTable = createDomphyTable({
data: people,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
// Default `row.id` is the row *index* (`"0"`, `"1"`, …). Pass `getRowId`
// whenever you use `row.id` as a reconcile `_key`, or for selection /
// pinning that must survive sort, filter, or data reorder.
getRowId: (person) => String(person.id),
})Render with version(l) for coarse re-renders (whole table) or pass l directly to fine-grained reads:
// Coarse — re-render tbody when any table state changes
const App = {
tbody: (l) => {
dTable.version(l)
return dTable.table.getRowModel().rows.map((row) => ({
tr: row.getVisibleCells().map((cell) => ({
td: String(cell.getValue() ?? ""),
_key: cell.id,
})),
_key: row.id,
}))
},
}
// Fine-grained — only this button re-renders when page changes
const PrevButton = {
button: "Previous",
disabled: (l) => !dTable.getCanPreviousPage(l),
onClick: () => dTable.table.previousPage(),
}DomphyTable handle
createDomphyTable(options) returns a DomphyTable<TData> handle:
| Method | Description |
|---|---|
| table | Raw table-core Table instance — all feature methods (setSorting, setColumnFilters, nextPage, …) |
| version(l?) | Reactive change counter — bumps on any state change |
| state(l?) | Full TableState, reactive with listener |
| setState(updater) | Direct state update |
| setOptions(updater) | Feed new options (data, columns, …) — bumps version so reactive reads re-render |
| destroy() | Releases reactive state |
| getRowModel(l?) | Reactive row model |
| getHeaderGroups(l?) | Reactive header groups |
| getAllLeafColumns(l?) | All leaf columns, reactive |
| getVisibleLeafColumns(l?) | Visible leaf columns, reactive |
| getIsAllColumnsVisible(l?) | Reactive |
| getIsSomeColumnsVisible(l?) | Reactive |
| getSelectedRowModel(l?) | Reactive selected rows |
| getIsAllRowsSelected(l?) | Reactive |
| getIsSomeRowsSelected(l?) | Reactive |
| getIsAllRowsExpanded(l?) | Reactive |
| getCanNextPage(l?) | Reactive |
| getCanPreviousPage(l?) | Reactive |
| getPageCount(l?) | Reactive |
Updating data
createDomphyTable takes options once — feed a new dataset (or new columns) through setOptions, which re-derives the row models and bumps version so subscribed subtrees re-render:
dTable.setOptions((prev) => ({ ...prev, data: freshPeople }))
// Raw object is merged onto the previous options (adapter `onStateChange` is kept):
dTable.setOptions({ data: freshPeople })Row models re-derive on data identity change, and the page index clamps automatically when the dataset shrinks (autoResetPageIndex, default on unless manualPagination). Use setState for table state (sorting, filters, …) — not setOptions. The adapter owns onStateChange; a raw object or updater cannot replace that wrapper.
Cell editing (opt-in)
CellEditing is a Domphy-original feature (no TanStack counterpart) and is not part of the built-in feature list — pass it via _features to keep the core a byte-level port:
import { createDomphyTable } from "@domphy/table/domphy"
import { CellEditing, createColumnHelper, getCoreRowModel } from "@domphy/table"
const dTable = createDomphyTable({
data: people,
columns,
getCoreRowModel: getCoreRowModel(),
_features: [CellEditing],
// Commit hook — write the value back into your own data here.
onCellEdit: ({ rowId, columnId, value }) => updatePerson(rowId, columnId, value),
// Gate editing per cell: boolean or (cell) => boolean (default true)
// enableCellEditing: (cell) => cell.column.id !== "id",
})State is cellEditing: { rowId, columnId } | null — one cell edits at a time. Cell methods: beginEdit(), commitEdit(value) (fires onCellEdit, then always exits — even if onCellEdit throws or initialState.cellEditing is set), cancelEdit() (exits silently the same way), getIsEditing(), getCanEdit(). Table methods: setEditingCell, getEditingCell, resetEditingCell(defaultState?) (true forces null; without true restores initialState.cellEditing). Types expose these methods as optional because the feature is not in builtInFeatures.
An editable <td> render — show an input while the cell is editing, commit on Enter/blur, cancel on Escape:
{
td: (l) => {
dTable.version(l)
const cell = cellAt(row, columnId)
return cell.getIsEditing()
? [{
input: null,
value: String(cell.getValue() ?? ""),
onKeyDown: (e) => {
if (e.key === "Enter") cell.commitEdit(e.target.value)
if (e.key === "Escape") cell.cancelEdit()
},
onBlur: (e) => cell.commitEdit(e.target.value),
}]
: [String(cell.getValue() ?? "")]
},
onDblClick: () => cellAt(row, columnId).beginEdit(),
}Raw table-core (advanced)
Import directly from @domphy/table for the raw TanStack table-core API:
import { createTable, getCoreRowModel, createColumnHelper } from "@domphy/table"All TanStack Table v8 APIs are available unchanged.
What's included
createTable/createColumnHelper— table instance and typed column defs- Row models (opt-in, tree-shakeable): core, sorted, filtered, grouped, expanded, paginated, faceted
- Built-in
sortingFns,filterFns,aggregationFns - Per-feature APIs: column filtering, global filtering, sorting, pagination, row selection, expanding, grouping, column ordering/pinning/sizing/visibility, row pinning, faceting
- Row pinning (built-in):
row.pin,getTopRows/getCenterRows/getBottomRows. Missing pinned ids (row left the data) are skipped, not thrown getRowId: defaultrow.idis the row index. Required for stable_key: row.id, selection, and pinning across sort/filter/reorder — passgetRowId: (row) => String(row.id)when the row has anid- Cell editing (Domphy-original, opt-in — not in
builtInFeatures; pass_features: [CellEditing]). Types onTable/Cell/TableStateare optional until the feature is installed.commitEdit/cancelEditalways exit edit mode (resetEditingCell(true))
Documentation
License
MIT — see LICENSE.
