diff --git a/.claude/rules/composables.md b/.claude/rules/composables.md index 52c37e375..1ba58fd81 100644 --- a/.claude/rules/composables.md +++ b/.claude/rules/composables.md @@ -26,6 +26,7 @@ Scope-specific mechanics for `packages/0/src/composables/**`. Covers naming, fac - §6.6 `useProxyModel` - §6.7 `useProxyRegistry` - §6.8 Register / unregister lifecycle contract +- §6.10 Collection composables: no `items` option - §7 Events & lifecycle - §9 Errors & invariants @@ -343,6 +344,10 @@ const table = createDataTable({ - The composable has exactly one correct implementation, and consumers have no reason to swap it. Example: `useHotkey` — the listener semantics are fixed. - You want to switch behavior based on a boolean flag. Use an option (`mode: 'client' | 'server'`) rather than dressing it up as an adapter. Adapters are for swapping *implementations*, not for flipping a known toggle. +### Collection composables: no `items` option + +A composable that owns a collection of values exposes `register` / `onboard` / `unregister`. It never accepts an `items` option in its factory — row identity, order, and per-row state live in the registry. Followed by `createRegistry`, `createModel`, `createSelection`, `createSingle`, `createGroup`, `createStep`, `createNested`, `createSortable`, `createKanban`, `createQueue`, `createTimeline`, `createTokens`, and (after the recent refactor) `createDataTable`. Full rule and migration shape: PHILOSOPHY §6.10. + ### `useProxyModel` and `useProxyRegistry` — cross-link Both composables are covered in PHILOSOPHY §6.6 and §6.7. Repeating the when-to-use summary here for composable authors: @@ -494,3 +499,4 @@ Pure transformers (`toRef`, `toElement`, `toValue`) are fine to call inline — - [ ] No DOM event binding inside the composable - [ ] ID generation through `useId()` - [ ] Trinity return only from `createTrinity` / `createContext` / `createPlugin` +- [ ] Composable that owns a collection of values uses `register` / `onboard`, never an `items` option (PHILOSOPHY §6.10) diff --git a/.claude/rules/new-feature-checklist.md b/.claude/rules/new-feature-checklist.md index 5e822739a..343ed3ec4 100644 --- a/.claude/rules/new-feature-checklist.md +++ b/.claude/rules/new-feature-checklist.md @@ -189,3 +189,4 @@ Prefer extending an existing pattern over creating a new one. - [ ] Feature appears in `apps/docs/build/generated/api-whitelist.ts` after build - [ ] `` renders on the new docs page - [ ] Maturity level matches the promotion criteria table (don't self-promote to `stable` or `mature` — those require a maintainer) +- [ ] Collection composable surface uses `register` / `onboard` (no `items` option) — see PHILOSOPHY §6.10 diff --git a/apps/docs/src/examples/composables/create-data-table/basic/BasicTable.vue b/apps/docs/src/examples/composables/create-data-table/basic/BasicTable.vue index cb0a6563c..213c6f7a8 100644 --- a/apps/docs/src/examples/composables/create-data-table/basic/BasicTable.vue +++ b/apps/docs/src/examples/composables/create-data-table/basic/BasicTable.vue @@ -4,13 +4,14 @@ import { users } from './data' const table = createDataTable({ - items: users, - columns, pagination: { itemsPerPage: 5 }, }) - function sortIcon (key: string) { - const dir = table.sort.direction(key) + table.columns.onboard(columns) + table.onboard(users.map(value => ({ id: value.id, value }))) + + function arrow (id: string) { + const dir = table.sort.direction(id) if (dir === 'asc') return '↑' if (dir === 'desc') return '↓' return '' @@ -32,13 +33,13 @@ {{ col.title }} - {{ sortIcon(col.key) }} + {{ arrow(col.id) }} diff --git a/apps/docs/src/examples/composables/create-data-table/basic/columns.ts b/apps/docs/src/examples/composables/create-data-table/basic/columns.ts index d29f29a5d..8a2430fd9 100644 --- a/apps/docs/src/examples/composables/create-data-table/basic/columns.ts +++ b/apps/docs/src/examples/composables/create-data-table/basic/columns.ts @@ -1,8 +1,8 @@ -import type { DataTableColumn } from '@vuetify/v0' +import type { DataTableColumnTicketInput } from '@vuetify/v0' import type { User } from './data' -export const columns: DataTableColumn[] = [ - { key: 'name', title: 'Name', sortable: true, filterable: true }, - { key: 'email', title: 'Email', sortable: true, filterable: true }, - { key: 'role', title: 'Role', sortable: true }, +export const columns: DataTableColumnTicketInput[] = [ + { id: 'name', title: 'Name', sortable: true, filterable: true }, + { id: 'email', title: 'Email', sortable: true, filterable: true }, + { id: 'role', title: 'Role', sortable: true }, ] diff --git a/apps/docs/src/examples/composables/create-data-table/features/FeaturesTable.vue b/apps/docs/src/examples/composables/create-data-table/features/FeaturesTable.vue index 8274a804d..82fcb1a3d 100644 --- a/apps/docs/src/examples/composables/create-data-table/features/FeaturesTable.vue +++ b/apps/docs/src/examples/composables/create-data-table/features/FeaturesTable.vue @@ -4,8 +4,6 @@ import { employees } from './data' const table = createDataTable({ - items: employees, - columns, groupBy: 'department', openAll: true, mandate: true, @@ -15,14 +13,17 @@ pagination: { itemsPerPage: 20 }, }) - function sortIcon (key: string) { - const dir = table.sort.direction(key) + table.columns.onboard(columns) + table.onboard(employees.map(value => ({ id: value.id, value }))) + + function arrow (id: string) { + const dir = table.sort.direction(id) if (dir === 'asc') return '↑' if (dir === 'desc') return '↓' return '' } - function formatSalary (value: number) { + function format (value: number) { return `$${value.toLocaleString()}` } @@ -68,13 +69,13 @@ {{ col.title }} - {{ sortIcon(col.key) }} + {{ arrow(col.id) }} @@ -85,7 +86,7 @@ class="bg-surface-tint cursor-pointer hover:bg-surface-variant transition-colors" @click="table.grouping.toggle(group.key)" > - + {{ table.grouping.isOpen(group.key) ? '−' : '+' }} {{ group.key }} ({{ group.items.length }}) @@ -111,7 +112,7 @@ {{ item.name }} {{ item.department }} - {{ formatSalary(item.salary) }} + {{ format(item.salary) }} [] = [ - { key: 'name', title: 'Name', sortable: true, filterable: true }, - { key: 'department', title: 'Department', sortable: true }, +export const columns: DataTableColumnTicketInput[] = [ + { id: 'name', title: 'Name', sortable: true, filterable: true }, + { id: 'department', title: 'Department', sortable: true }, { - key: 'salary', + id: 'salary', title: 'Salary', sortable: true, filterable: true, @@ -17,5 +17,5 @@ export const columns: DataTableColumn[] = [ return String(value).includes(query) }, }, - { key: 'active', title: 'Status', sortable: true }, + { id: 'active', title: 'Status', sortable: true }, ] diff --git a/apps/docs/src/examples/composables/create-data-table/server/ServerTable.vue b/apps/docs/src/examples/composables/create-data-table/server/ServerTable.vue index aa0e4b446..ca42033f0 100644 --- a/apps/docs/src/examples/composables/create-data-table/server/ServerTable.vue +++ b/apps/docs/src/examples/composables/create-data-table/server/ServerTable.vue @@ -1,48 +1,43 @@