Skip to content

refactor(createDataTable): drop items option for registry pattern#236

Merged
johnleider merged 6 commits into
masterfrom
refactor/data-table-registry
May 14, 2026
Merged

refactor(createDataTable): drop items option for registry pattern#236
johnleider merged 6 commits into
masterfrom
refactor/data-table-registry

Conversation

@johnleider
Copy link
Copy Markdown
Member

Summary

createDataTable predated the registry-pattern convention used by every other collection composable in v0. The items: MaybeRefOrGetter<T[]> option put row identity outside the composable's registry, forced reactivity plumbing on the caller, and split ownership between the caller's array and the table's internal state. This PR drops items and itemValue and routes row management through the inherited registry surface.

Convention codified

PHILOSOPHY.md §6.10 "Collection composables: no items option" plus a checklist line in .claude/rules/composables.md and .claude/rules/new-feature-checklist.md. The PHILOSOPHY entry lists createDataGrid as also compliant in anticipation of the follow-up grid PR (#174) that adopts the same surface inherited via spread.

API change

// Before
const table = createDataTable({
  items: users,
  columns,
  itemValue: 'id',
})

// After
const table = createDataTable({ columns })
table.onboard(users.map(value => ({ id: value.id, value })))

Internally, createDataTable now owns a createRegistry({ events: true, reactive: true }) and spreads its surface into the returned context, so consumers get register / onboard / unregister / upsert / clear / get / move / etc. for free.

  • DataTableContext<T> now extends RegistryContext<DataTableTicketInput<T>, DataTableTicket<T>>.
  • allItems / filteredItems / sortedItems / items keep their existing Readonly<Ref<readonly T[]>> shape — they project ticket.value through the pipeline.
  • Adapters (Client / Server / Virtual) are unchanged; they read context.items as before.
  • size is re-declared as a getter after the registry spread (otherwise { ...registry } freezes it at construction).

Migration shape

Reactive items sources are no longer auto-synced. Callers either drive register / unregister themselves, or watch a source and do clear() + onboard() — the canonical server pattern is documented in the ServerDataTableAdapter section of the docs page.

Co-author credit

Built by parallel agents per the project's team convention:

  • dev-table — source + tests
  • docs-table — docs page + 4 examples
  • philosophy — PHILOSOPHY §6.10 + rules

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 12, 2026

Open in StackBlitz

commit: a9173fc

@johnleider johnleider force-pushed the refactor/data-table-registry branch 2 times, most recently from b7cd0c0 to f1a66b8 Compare May 12, 2026 18:26
createDataTable predated the registry-pattern convention used by every
other collection composable in v0 (createRegistry, createModel,
createSelection, createSortable, createKanban, …). The items option
put row identity outside the composable's registry, forced
MaybeRefOrGetter plumbing, and split ownership between the caller's
array and the internal state.

Drop the items option. createDataTable now owns an internal
createRegistry({ events: true, reactive: true }) and spreads its
surface into the returned context, so consumers register rows via the
inherited register / onboard / unregister / clear / upsert methods.

Row identity comes from the ticket id at register time. The itemValue
option goes away with it; consumers pass id: value.id when they want a
domain-stable identifier (selection, expansion, grouping all key off
the ticket id, which equals the caller-supplied id by convention).
Adapters are unchanged — they still receive context.items / allItems
refs that project ticket.value through the pipeline.

Migration shape:

  // before
  createDataTable({ items: data, ... })

  // after
  const table = createDataTable({ ... })
  table.onboard(data.map(value => ({ id: value.id, value })))

A reactive items source is no longer auto-synced; callers drive
register / unregister, or watch and do clear() + onboard() (see the
ServerDataTableAdapter prose for the canonical server pattern).

Codified in PHILOSOPHY §6.10 "Collection composables: no items option"
plus a checklist line in composables.md and new-feature-checklist.md.
The PHILOSOPHY entry lists createDataGrid as also compliant in
anticipation of the follow-up grid PR that adopts the same surface
inherited via spread.

Co-authored-by: dev-table <noreply@anthropic.com>
Co-authored-by: docs-table <noreply@anthropic.com>
Co-authored-by: philosophy <noreply@anthropic.com>
@johnleider johnleider force-pushed the refactor/data-table-registry branch from f1a66b8 to 7e92f30 Compare May 12, 2026 18:38
@johnleider johnleider self-assigned this May 12, 2026
@johnleider johnleider added this to the v0.2.x milestone May 12, 2026
…up reopen

- selection/expansion subscribe to unregister:ticket and clear:registry so
  stale ids cannot diverge from the registry
- replace rowId(item) reverse lookup with ticket-walking computeds so two
  tickets sharing a row value reference both participate in selectAll /
  isAllSelected / toggleAll (catalog buckets keyed by object identity used
  to collapse onto ids[0])
- guard selectableIds against tickets registered without a value
- openAll watcher tracks autoOpened keys and only opens new group keys, so
  registering more rows no longer reopens groups the user explicitly closed
- drop stale createDataGrid references from PHILOSOPHY §6.10, composables
  rule, and columns JSDoc
- rename example fetch -> fetchPage to stop shadowing the global
Drop the `columns` factory option. Columns are now registered the same
way rows are — via `table.columns.register({ id, title, sortable })` and
`table.columns.onboard([...])`. Mirrors the createKanban pattern and
brings createDataTable in line with PHILOSOPHY §6.10.

- new DataTableColumnTicketInput<T> / DataTableColumnTicket<T> pair
- table.columns: RegistryContext<...> namespaced under `columns`
- leaves, headers, sortable, filterable, customSorts, customColumnFilters
  are all reactive derivations over columns.values() (were one-shot before)
- sort group reconciles via watch(sortable, ..., { flush: 'sync' }) — new
  sortable columns onboard automatically, removed ones drop from the order
  array so stale SortEntry rows can't leak through
- adapter context's filterableKeys / customSorts / customColumnFilters
  widened to MaybeRefOrGetter<...> so adapter.setup() doesn't snapshot
  empty values when columns onboard post-construction
- column id field uses Extensible<keyof T & string> for autocomplete on
  known row keys while still accepting derived columns
- docs + PHILOSOPHY §6.10 updated; all four examples migrated to the new
  surface and iterate columns via table.columns.values() to demonstrate
  reactivity
@johnleider johnleider merged commit 58df5fb into master May 14, 2026
12 of 13 checks passed
@johnleider johnleider deleted the refactor/data-table-registry branch May 14, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant