@tanstack/vue-table 9.0.0-alpha.9 → 9.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +127 -0
  2. package/dist/FlexRender.cjs +80 -0
  3. package/dist/FlexRender.cjs.map +1 -0
  4. package/dist/FlexRender.d.cts +63 -0
  5. package/dist/FlexRender.d.ts +63 -0
  6. package/dist/FlexRender.js +79 -0
  7. package/dist/FlexRender.js.map +1 -0
  8. package/dist/createTableHook.cjs +193 -0
  9. package/dist/createTableHook.cjs.map +1 -0
  10. package/dist/createTableHook.d.cts +134 -0
  11. package/dist/createTableHook.d.ts +134 -0
  12. package/dist/createTableHook.js +192 -0
  13. package/dist/createTableHook.js.map +1 -0
  14. package/dist/flex-render.cjs +5 -0
  15. package/dist/flex-render.d.cts +2 -0
  16. package/dist/flex-render.d.ts +2 -0
  17. package/dist/flex-render.js +3 -0
  18. package/dist/index.cjs +17 -0
  19. package/dist/index.d.cts +5 -0
  20. package/dist/index.d.ts +5 -0
  21. package/dist/index.js +7 -0
  22. package/dist/merge-proxy.cjs +77 -0
  23. package/dist/merge-proxy.cjs.map +1 -0
  24. package/dist/merge-proxy.js +75 -0
  25. package/dist/merge-proxy.js.map +1 -0
  26. package/dist/reactivity.cjs +64 -0
  27. package/dist/reactivity.cjs.map +1 -0
  28. package/dist/reactivity.js +64 -0
  29. package/dist/reactivity.js.map +1 -0
  30. package/dist/static-functions.cjs +9 -0
  31. package/dist/static-functions.d.cts +1 -0
  32. package/dist/static-functions.d.ts +1 -0
  33. package/dist/static-functions.js +3 -0
  34. package/dist/useTable.cjs +75 -0
  35. package/dist/useTable.cjs.map +1 -0
  36. package/dist/useTable.d.cts +42 -0
  37. package/dist/useTable.d.ts +42 -0
  38. package/dist/useTable.js +75 -0
  39. package/dist/useTable.js.map +1 -0
  40. package/package.json +31 -19
  41. package/skills/vue/client-to-server/SKILL.md +360 -0
  42. package/skills/vue/compose-with-tanstack-form/SKILL.md +369 -0
  43. package/skills/vue/compose-with-tanstack-pacer/SKILL.md +321 -0
  44. package/skills/vue/compose-with-tanstack-query/SKILL.md +383 -0
  45. package/skills/vue/compose-with-tanstack-store/SKILL.md +302 -0
  46. package/skills/vue/compose-with-tanstack-virtual/SKILL.md +344 -0
  47. package/skills/vue/getting-started/SKILL.md +415 -0
  48. package/skills/vue/migrate-v8-to-v9/SKILL.md +393 -0
  49. package/skills/vue/production-readiness/SKILL.md +278 -0
  50. package/skills/vue/table-state/SKILL.md +399 -0
  51. package/src/FlexRender.ts +138 -0
  52. package/src/createTableHook.ts +533 -0
  53. package/src/flex-render.ts +1 -0
  54. package/src/index.ts +3 -74
  55. package/src/merge-proxy.ts +66 -15
  56. package/src/reactivity.ts +89 -0
  57. package/src/static-functions.ts +1 -0
  58. package/src/useTable.ts +180 -0
  59. package/dist/cjs/index.cjs +0 -68
  60. package/dist/cjs/index.cjs.map +0 -1
  61. package/dist/cjs/index.d.cts +0 -14
  62. package/dist/cjs/merge-proxy.cjs +0 -61
  63. package/dist/cjs/merge-proxy.cjs.map +0 -1
  64. package/dist/cjs/merge-proxy.d.cts +0 -11
  65. package/dist/esm/index.d.ts +0 -14
  66. package/dist/esm/index.js +0 -63
  67. package/dist/esm/index.js.map +0 -1
  68. package/dist/esm/merge-proxy.d.ts +0 -11
  69. package/dist/esm/merge-proxy.js +0 -61
  70. package/dist/esm/merge-proxy.js.map +0 -1
@@ -0,0 +1,399 @@
1
+ ---
2
+ name: vue/table-state
3
+ description: >
4
+ Vue reactivity for `@tanstack/vue-table` v9. Covers `useTable(options, selector?)`, reactive
5
+ `data`/`columns` via `ref`/`computed` getters, the `vueReactivity()` binding (readonly atoms →
6
+ `computed`, writable → `shallowRef`, subscriptions via `watch({flush:'sync'})`), the three state
7
+ surfaces `table.atoms.<slice>` / `table.store` / `table.state`, selected state via the second
8
+ `useTable` argument, the `<FlexRender>` component, `table.Subscribe` for atom/source subscriptions,
9
+ and composition with `createAtom` / `useSelector` from `@tanstack/vue-store`.
10
+ type: framework
11
+ library: tanstack-table
12
+ framework: vue
13
+ library_version: '9.0.0-alpha.48'
14
+ requires:
15
+ - state-management
16
+ - setup
17
+ sources:
18
+ - docs/framework/vue/guide/table-state.md
19
+ - docs/framework/vue/vue-table.md
20
+ - packages/vue-table/src/useTable.ts
21
+ - packages/vue-table/src/reactivity.ts
22
+ - packages/vue-table/src/FlexRender.ts
23
+ - examples/vue/basic-use-table/
24
+ - examples/vue/basic-external-atoms/
25
+ - examples/vue/basic-external-state/
26
+ ---
27
+
28
+ # Vue Table State, Subscribe & createTableHook
29
+
30
+ ## Dependencies
31
+
32
+ ```bash
33
+ pnpm add @tanstack/vue-table @tanstack/vue-store
34
+ ```
35
+
36
+ `@tanstack/vue-store` is a peer for `createAtom` / `useSelector`. It is only required when you
37
+ opt into external atoms — basic tables that use `initialState` or built-in state work without it.
38
+
39
+ This skill is v9-specific (`9.0.0-alpha.48`). The hook is `useTable` for every framework now; the
40
+ v8 name `useVueTable` no longer exists.
41
+
42
+ ## Setup
43
+
44
+ Every Vue table call requires `features` (built from `tableFeatures({...})`). Row model
45
+ factories now live on the features object alongside the feature itself. Core row model is
46
+ automatic; only register `paginatedRowModel`, `sortedRowModel`, etc. when you use the matching
47
+ feature.
48
+
49
+ ```vue
50
+ <script setup lang="ts">
51
+ import { ref } from 'vue'
52
+ import {
53
+ FlexRender,
54
+ createColumnHelper,
55
+ createSortedRowModel,
56
+ rowSortingFeature,
57
+ sortFns,
58
+ tableFeatures,
59
+ useTable,
60
+ } from '@tanstack/vue-table'
61
+
62
+ type Person = { firstName: string; lastName: string; age: number }
63
+
64
+ // Stable identity — declare outside the component or at module scope.
65
+ const features = tableFeatures({
66
+ rowSortingFeature,
67
+ sortedRowModel: createSortedRowModel(),
68
+ sortFns,
69
+ })
70
+ const columnHelper = createColumnHelper<typeof features, Person>()
71
+ const columns = columnHelper.columns([
72
+ columnHelper.accessor('firstName', { header: 'First' }),
73
+ columnHelper.accessor('lastName', { header: 'Last' }),
74
+ columnHelper.accessor('age', { header: 'Age' }),
75
+ ])
76
+
77
+ const data = ref<Person[]>([])
78
+
79
+ const table = useTable({
80
+ features,
81
+ columns,
82
+ // Reactive data: pass the ref directly OR a getter — the adapter unwraps.
83
+ data,
84
+ })
85
+ </script>
86
+
87
+ <template>
88
+ <table>
89
+ <thead>
90
+ <tr v-for="hg in table.getHeaderGroups()" :key="hg.id">
91
+ <th v-for="h in hg.headers" :key="h.id">
92
+ <FlexRender v-if="!h.isPlaceholder" :header="h" />
93
+ </th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ <tr v-for="row in table.getRowModel().rows" :key="row.id">
98
+ <td v-for="cell in row.getAllCells()" :key="cell.id">
99
+ <FlexRender :cell="cell" />
100
+ </td>
101
+ </tr>
102
+ </tbody>
103
+ </table>
104
+ </template>
105
+ ```
106
+
107
+ ### Why this works
108
+
109
+ The Vue adapter calls `vueReactivity()` and installs it as `coreReactivityFeature` automatically
110
+ (see `packages/vue-table/src/useTable.ts`):
111
+
112
+ - Readonly atoms back onto `computed()` refs.
113
+ - Writable atoms back onto `shallowRef()`.
114
+ - Subscriptions use `watch(source, cb, { flush: 'sync' })`, so table updates are visible to Vue
115
+ render and computed work immediately.
116
+
117
+ `useTable` also runs a `watch(() => getReactiveOptionDeps(...))` on every option, so passing a
118
+ `ref` or `computed` for `data`, `columns`, `rowCount`, etc. is supported — the table calls
119
+ `setOptions` whenever any reactive option changes.
120
+
121
+ > **Vue note.** `table.Subscribe` exists for parity with React, but you usually do not need it.
122
+ > Vue's reactivity re-evaluates template reads automatically — wrap reads in `computed(...)` if
123
+ > you need them outside a template. Do not import React-Compiler workarounds.
124
+
125
+ ## Core Patterns
126
+
127
+ ### 1. The three read surfaces
128
+
129
+ ```ts
130
+ // (a) Per-slice atom — narrowest, no full state snapshot built
131
+ const sorting = table.atoms.sorting.get()
132
+
133
+ // (b) Flat readonly store — every registered slice as one object
134
+ const snapshot = table.state
135
+
136
+ // (c) Vue selected state — the value returned from useTable's 2nd arg
137
+ const table = useTable(
138
+ {
139
+ features,
140
+ columns,
141
+ data,
142
+ },
143
+ (state) => ({ sorting: state.sorting }),
144
+ )
145
+ table.state.sorting // typed, reactive
146
+ ```
147
+
148
+ `table.atoms.<slice>` only contains slices for features registered in `features`. If
149
+ `rowSortingFeature` is not registered, `table.atoms.sorting` is `undefined` (and TypeScript
150
+ flags it). This is the v9-specific "missing API" gotcha — register the feature first.
151
+
152
+ Source: `docs/framework/vue/guide/table-state.md` (Feature-based State, Accessing Table State).
153
+
154
+ ### 2. Reactive data with a getter or computed
155
+
156
+ The adapter accepts a `ref`/`computed` for any option. The idiomatic shapes are:
157
+
158
+ ```ts
159
+ // (a) Pass the ref directly — adapter unwraps via `unref()`
160
+ const data = ref(makeData(100))
161
+ const table = useTable({ features, columns, data })
162
+
163
+ // (b) Use a getter when `data` is owned by a parent object
164
+ const table = useTable({
165
+ features,
166
+ columns,
167
+ get data() {
168
+ return data.value
169
+ },
170
+ })
171
+
172
+ // (c) Computed when filtering/derivation lives on the client
173
+ const filtered = computed(() => data.value.filter(/* … */))
174
+ const table = useTable({
175
+ features,
176
+ columns,
177
+ get data() {
178
+ return filtered.value
179
+ },
180
+ })
181
+ ```
182
+
183
+ When `data.value` changes, `useTable` calls `setOptions` synchronously and the table re-derives.
184
+ Source: `examples/vue/basic-use-table/src/App.tsx`, `examples/vue/virtualized-rows/src/App.vue`.
185
+
186
+ ### 3. External atoms (recommended for shared state)
187
+
188
+ Use `createAtom` from `@tanstack/vue-store`; pass through `options.atoms`. Atoms take
189
+ precedence over `options.state` — pick one mechanism per slice.
190
+
191
+ ```vue
192
+ <script setup lang="ts">
193
+ import { ref } from 'vue'
194
+ import { createAtom, useSelector } from '@tanstack/vue-store'
195
+ import {
196
+ createPaginatedRowModel,
197
+ rowPaginationFeature,
198
+ tableFeatures,
199
+ useTable,
200
+ type PaginationState,
201
+ } from '@tanstack/vue-table'
202
+
203
+ const features = tableFeatures({
204
+ rowPaginationFeature,
205
+ paginatedRowModel: createPaginatedRowModel(),
206
+ })
207
+
208
+ const paginationAtom = createAtom<PaginationState>({
209
+ pageIndex: 0,
210
+ pageSize: 10,
211
+ })
212
+ const pagination = useSelector(paginationAtom) // reactive ref-like
213
+
214
+ const data = ref([] as Person[])
215
+ const table = useTable({
216
+ features,
217
+ columns,
218
+ data,
219
+ atoms: { pagination: paginationAtom },
220
+ // NOTE: no `onPaginationChange` — `table.setPageIndex()` writes through to the atom.
221
+ })
222
+ </script>
223
+
224
+ <template>
225
+ <button @click="table.nextPage()" :disabled="!table.getCanNextPage()">
226
+ Next
227
+ </button>
228
+ <span>Page {{ pagination.value.pageIndex + 1 }}</span>
229
+ </template>
230
+ ```
231
+
232
+ Source: `examples/vue/basic-external-atoms/src/App.tsx`.
233
+
234
+ ### 4. External `state` + `on[State]Change` with getters
235
+
236
+ Still supported and convenient for migration paths. The critical rule: pass each slice as a
237
+ **getter** so Vue can track `.value` changes. A raw ref captured in the state object is read
238
+ once and never re-tracked.
239
+
240
+ ```ts
241
+ const sorting = ref<SortingState>([])
242
+ const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 10 })
243
+
244
+ const table = useTable({
245
+ features,
246
+ columns,
247
+ data,
248
+ state: {
249
+ get sorting() {
250
+ return sorting.value
251
+ }, // <- getter
252
+ get pagination() {
253
+ return pagination.value
254
+ }, // <- getter
255
+ },
256
+ onSortingChange: (u) => {
257
+ sorting.value = typeof u === 'function' ? u(sorting.value) : u
258
+ },
259
+ onPaginationChange: (u) => {
260
+ pagination.value = typeof u === 'function' ? u(pagination.value) : u
261
+ },
262
+ })
263
+ ```
264
+
265
+ Source: `examples/vue/basic-external-state/src/App.tsx`,
266
+ `docs/framework/vue/guide/table-state.md` (External State).
267
+
268
+ ### 5. `<FlexRender>` and `table.Subscribe`
269
+
270
+ `<FlexRender>` accepts a `cell`, `header`, or `footer` prop (preferred). The legacy
271
+ `:render` / `:props` pattern still works.
272
+
273
+ ```vue
274
+ <FlexRender :cell="cell" />
275
+ <FlexRender :header="header" />
276
+ <FlexRender :footer="header" />
277
+
278
+ <!-- Legacy form -->
279
+ <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
280
+ ```
281
+
282
+ `table.Subscribe` mostly mirrors React. It is rarely the right tool in Vue — prefer
283
+ `computed(() => table.atoms.<slice>.get())` or the `useTable` selector. If you do need it
284
+ (for cross-store source subscription), it accepts a `source` prop:
285
+
286
+ ```ts
287
+ // In a render function or JSX file
288
+ return () => (
289
+ <table.Subscribe source={someAtom} selector={(v) => v.someField}>
290
+ {(value) => <span>{value}</span>}
291
+ </table.Subscribe>
292
+ )
293
+ ```
294
+
295
+ ## Common Mistakes
296
+
297
+ ### Passing a `ref` to `state.pagination` without a getter (CRITICAL)
298
+
299
+ ```ts
300
+ // ❌ Captures the ref object; later `pagination.value = …` writes are invisible.
301
+ const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 10 })
302
+ const table = useTable({
303
+ features,
304
+ columns,
305
+ data,
306
+ state: { pagination },
307
+ })
308
+
309
+ // ✅ Use a getter so Vue tracks `.value`.
310
+ const table = useTable({
311
+ features,
312
+ columns,
313
+ data,
314
+ state: {
315
+ get pagination() {
316
+ return pagination.value
317
+ },
318
+ },
319
+ onPaginationChange: (u) => {
320
+ pagination.value = typeof u === 'function' ? u(pagination.value) : u
321
+ },
322
+ })
323
+ ```
324
+
325
+ Source: `docs/framework/vue/guide/table-state.md` (External State).
326
+
327
+ ### Reading `table.atoms.<slice>.get()` outside a reactive context (HIGH)
328
+
329
+ ```ts
330
+ // ❌ One-shot read at component setup — never updates.
331
+ const pagination = table.atoms.pagination.get()
332
+
333
+ // ✅ Wrap in `computed` so Vue tracks the atom.
334
+ const pagination = computed(() => table.atoms.pagination.get())
335
+ // or
336
+ const table = useTable(opts, (s) => ({ pagination: s.pagination }))
337
+ // then read `table.state.pagination`
338
+ ```
339
+
340
+ ### Using the v8 `useVueTable` name (HIGH)
341
+
342
+ ```ts
343
+ // ❌ Removed in v9.
344
+ import { useVueTable } from '@tanstack/vue-table'
345
+
346
+ // ✅
347
+ import { useTable } from '@tanstack/vue-table'
348
+ ```
349
+
350
+ ### "API missing" because the feature is not in `features` (CRITICAL, v9-specific)
351
+
352
+ ```ts
353
+ // ❌ `rowSortingFeature` not registered → `table.setSorting` and `table.atoms.sorting` do not exist.
354
+ const features = tableFeatures({})
355
+ const table = useTable({ features, columns, data })
356
+ table.setSorting([{ id: 'age', desc: true }]) // TS error / runtime no-op
357
+
358
+ // ✅ Register the feature and its matching row model factory inside tableFeatures.
359
+ const features = tableFeatures({
360
+ rowSortingFeature,
361
+ sortedRowModel: createSortedRowModel(),
362
+ sortFns,
363
+ })
364
+ const table = useTable({ features, columns, data })
365
+ ```
366
+
367
+ ### Reimplementing built-in state transitions (CRITICAL — #1 AI tell)
368
+
369
+ ```ts
370
+ // ❌ Hand-rolled sort — bypasses the table's invariants and reset APIs.
371
+ const sorting = ref<SortingState>([])
372
+ const sorted = computed(() => [...data.value].sort(/* … */))
373
+
374
+ // ✅ Let the table own it. Use `table.setSorting`, `column.toggleSorting`,
375
+ // `header.column.getToggleSortingHandler()`.
376
+ ```
377
+
378
+ The library exposes `setSorting`, `setColumnFilters`, `toggleSelected`, `nextPage`, etc. for
379
+ nearly every state transition.
380
+
381
+ ### Hallucinating pre-v9 API names
382
+
383
+ `useVueTable`, `getCoreRowModel()` as an option, `createColumnHelper<TData>()` (single generic),
384
+ `sortingFn` instead of `sortFn` — all v8 shapes that will not compile. See
385
+ `migrate-v8-to-v9` for the full rename list.
386
+
387
+ ### Unstable `features` / `columns` / `data` identity
388
+
389
+ Declare `features`, `columnHelper`, and `columns` **outside** `<script setup>` (at module
390
+ scope) or use `computed`. Recreating them every render churns the table's option diff watcher
391
+ and triggers a `setOptions` on every render — slow, and external atom slices can flicker.
392
+
393
+ ## See Also
394
+
395
+ - `tanstack-table/vue/getting-started` — the end-to-end first-table walkthrough
396
+ - `tanstack-table/vue/production-readiness` — selectors, tree-shaking, identity
397
+ - `tanstack-table/vue/compose-with-tanstack-store` — external atoms in depth
398
+ - `tanstack-table/table-core/state-management` (core) — the atom model that drives this skill
399
+ - `tanstack-table/vue/migrate-v8-to-v9` — `useVueTable` → `useTable`
@@ -0,0 +1,138 @@
1
+ import { defineComponent, h, isVNode } from 'vue'
2
+ import type { PropType } from 'vue'
3
+
4
+ export interface FlexRenderCell {
5
+ column: {
6
+ columnDef: {
7
+ aggregatedCell?: any
8
+ cell?: any
9
+ }
10
+ }
11
+ getContext: () => any
12
+ getIsAggregated?: () => boolean
13
+ getIsPlaceholder?: () => boolean
14
+ }
15
+
16
+ export interface FlexRenderHeader {
17
+ column: {
18
+ columnDef: {
19
+ footer?: any
20
+ header?: any
21
+ }
22
+ }
23
+ getContext: () => any
24
+ }
25
+
26
+ /**
27
+ * If rendering headers, cells, or footers with custom markup, use flexRender instead of `cell.getValue()` or `cell.renderValue()`.
28
+ * @example flexRender(cell.column.columnDef.cell, cell.getContext())
29
+ */
30
+ export function flexRender(render: any, props: any): any {
31
+ if (typeof render === 'function') {
32
+ const rendered = render(props)
33
+
34
+ if (isVNode(rendered)) {
35
+ return rendered
36
+ }
37
+
38
+ if (typeof rendered === 'function' || typeof rendered === 'object') {
39
+ return h(rendered, props)
40
+ }
41
+
42
+ return rendered
43
+ }
44
+
45
+ if (typeof render === 'object') {
46
+ return h(render, props)
47
+ }
48
+
49
+ return render
50
+ }
51
+
52
+ /**
53
+ * Simplified component for rendering headers, cells, or footers.
54
+ *
55
+ * Supports both the new shorthand pattern and the legacy `:render`/`:props` pattern:
56
+ * @example
57
+ * ```vue
58
+ * <!-- New shorthand pattern -->
59
+ * <FlexRender :cell="cell" />
60
+ * <FlexRender :header="header" />
61
+ * <FlexRender :footer="header" />
62
+ *
63
+ * <!-- Legacy pattern (still supported) -->
64
+ * <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
65
+ * ```
66
+ */
67
+ export const FlexRender = defineComponent({
68
+ props: {
69
+ render: {
70
+ type: [Function, Object, String] as PropType<any>,
71
+ default: undefined,
72
+ },
73
+ props: {
74
+ type: Object as PropType<any>,
75
+ default: undefined,
76
+ },
77
+ cell: {
78
+ type: Object as PropType<FlexRenderCell>,
79
+ default: undefined,
80
+ },
81
+ header: {
82
+ type: Object as PropType<FlexRenderHeader>,
83
+ default: undefined,
84
+ },
85
+ footer: {
86
+ type: Object as PropType<FlexRenderHeader>,
87
+ default: undefined,
88
+ },
89
+ },
90
+ setup: (props: {
91
+ render?: any
92
+ props?: any
93
+ cell?: FlexRenderCell
94
+ header?: FlexRenderHeader
95
+ footer?: FlexRenderHeader
96
+ }) => {
97
+ return () => {
98
+ // New shorthand pattern: extract render and props from cell/header/footer
99
+ if (props.cell) {
100
+ const cell = props.cell
101
+ const def = cell.column.columnDef
102
+ // When the column-grouping feature is registered, a cell can be in
103
+ // one of three special modes that should not render `columnDef.cell`
104
+ // directly:
105
+ // - aggregated: render `columnDef.aggregatedCell` (falling back to
106
+ // `columnDef.cell` if the column did not define one)
107
+ // - placeholder: a duplicate value within a group — render nothing
108
+ // - grouped: fall through to `columnDef.cell`; consumers that want
109
+ // a custom group header typically branch on `cell.getIsGrouped()`
110
+ // themselves first
111
+ if (cell.getIsAggregated?.()) {
112
+ return flexRender(def.aggregatedCell ?? def.cell, cell.getContext())
113
+ }
114
+ if (cell.getIsPlaceholder?.()) {
115
+ return null
116
+ }
117
+ return flexRender(def.cell, cell.getContext())
118
+ }
119
+
120
+ if (props.header) {
121
+ return flexRender(
122
+ props.header.column.columnDef.header,
123
+ props.header.getContext(),
124
+ )
125
+ }
126
+
127
+ if (props.footer) {
128
+ return flexRender(
129
+ props.footer.column.columnDef.footer,
130
+ props.footer.getContext(),
131
+ )
132
+ }
133
+
134
+ // Legacy pattern: use render and props directly
135
+ return flexRender(props.render, props.props)
136
+ }
137
+ },
138
+ })