Table internals
This page covers the structural sub-components of Table — the header, body, footer, row, and column primitives. You'll rarely instantiate them directly; they are built and managed by Table. Reach for them when you need to subclass for a custom column type or hook into the virtual-scrolling pipeline.
All symbols on this page are exported from @jimka/typescript-ui/component/table. Since Header, Body, Row, and Column collide with other groups (e.g. display, layout), import with an as rename in code that mixes them:
import {
Header as TableHeader,
Body as TableBody,
Row as TableRow,
FooterRow as TableFooter,
Column as TableColumn,
} from '@jimka/typescript-ui/component/table';Header
Header renders the column-header strip as a <thead> element. It builds one HeaderCell per visible field from the model, wired with sort-click, resize-drag, and context-menu callbacks.
Body
Body is the virtual-scrolling row container.
- Maintains a fixed pool of reusable row components.
- Only rows visible in the viewport plus a small buffer are in the DOM at any time.
- Scrolling is delegated to a
VirtualScroller— a rows-container<div>whosetranslate3dtransform exposes the requested viewport, two customScrollbaroverlays, and wheel/touch handlers with fling momentum.overflow:hiddenon the body suppresses the native scroll path, removing the compositor-vs-JS race that caused a one-frame flicker during fast scrolling. - Rebinds existing pool slots to new data via
setData()only when their data index changes.
The full implementation lives in src/typescript/lib/component/table/Body.ts and is documented at the API page.
FooterRow
FooterRow is an optional <tfoot> strip with a single Row. Use it for summary aggregations (totals, averages) computed externally and pushed in via the row.
Row
Row is a single data row rendered as a <tr>. It creates one typed cell per model field — picked from StringCell, NumberCell, BooleanCell, DateCell, TimeCell, DateTimeCell, or DefaultCell — and binds each cell's commit callback to the corresponding field on the bound ModelRecord.
Column
Column is a resolved presentation descriptor for a single column. Created internally — not constructed directly. It wraps a Field and carries optional width constraints and an initial visibility flag derived from a ColumnSpec.
Cell types
The framework ships with seven typed cells, selected by Row based on each field's declared type:
StringCell— read / write strings viaStringRenderer+StringEditor.NumberCell— right-aligned numbers viaNumberRenderer+NumberEditor.BooleanCell— checkbox; theBooleanEditordoubles as the renderer.DateCell— calendar date viaDateRenderer+DateEditor.TimeCell— time-of-day viaTimeRenderer+TimeEditor; honors the column'sshowSecondsflag.DateTimeCell— combined date + time viaDateTimeRenderer+DateTimeEditor; honors the column'sshowSecondsflag.DefaultCell— fallback for fields whose type is not explicitly mapped.
For DateCell, TimeCell, and DateTimeCell, committing an empty editor writes null; committing an unparseable value reverts to the previous value rather than writing null.
HeaderCell extends DefaultCell with sort indicators, click-to-sort, and a resize drag handle.
Shared editor pool
Only one cell in the body is ever being edited at a time, so the built-in typed cells share editor instances through a CellEditorPool owned by Body. On startEdit, the cell asks the pool for the editor matching its getEditorKey() (e.g. "string", "time:seconds"), re-parents that single editor into the active cell, and on commit/cancel detaches it again. The renderer side remains one component per cell — every visible row paints its own value.
BooleanCell opts out: its BooleanEditor doubles as the renderer, so each row needs its own checkbox. Its getEditorKey() returns null and the cell allocates the editor up front exactly as before. GlyphCell and DefaultCell are read-only and have no editor.
Custom cell authors can opt in by overriding getEditorKey() and registering a factory on body.getEditorPool() — see Custom cell type.
Building a custom cell type
Subclass Cell<T> with your own CellRenderer and optional CellEditor. The renderer displays the value; the editor takes over on double-click, commits on blur or Enter, and reverts on Escape.
import { Cell, CellRenderer, CellEditor } from '@jimka/typescript-ui/component/table';
class CurrencyRenderer extends CellRenderer<number> { /* … */ }
class CurrencyEditor extends CellEditor<number> { /* … */ }
class CurrencyCell extends Cell<number> {
constructor() {
super('td', new CurrencyRenderer(), new CurrencyEditor());
}
}See Custom cell type for the full pattern.
See also
- Table — the parent component
- Tree — uses a similar virtual-scrolling pool
- API: Cell, CellEditor, CellRenderer