@tanstack/svelte-table 9.0.0-beta.7 → 9.0.0-beta.71

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 (48) hide show
  1. package/README.md +5 -1
  2. package/dist/FlexRender.svelte +15 -1
  3. package/dist/createTable.svelte.d.ts +26 -36
  4. package/dist/createTable.svelte.js +27 -32
  5. package/dist/createTableHook.svelte.d.ts +51 -18
  6. package/dist/createTableHook.svelte.js +18 -10
  7. package/dist/experimental-worker-plugin.d.ts +1 -0
  8. package/dist/experimental-worker-plugin.js +1 -0
  9. package/dist/index.d.ts +1 -2
  10. package/dist/index.js +0 -1
  11. package/dist/reactivity.svelte.d.ts +3 -1
  12. package/dist/reactivity.svelte.js +6 -1
  13. package/dist/render-component.js +4 -0
  14. package/package.json +16 -9
  15. package/skills/create-table-hook/SKILL.md +168 -0
  16. package/skills/getting-started/SKILL.md +172 -0
  17. package/skills/migrate-v8-to-v9/SKILL.md +201 -0
  18. package/skills/table-state/SKILL.md +262 -0
  19. package/skills/with-tanstack-query/SKILL.md +149 -0
  20. package/skills/with-tanstack-virtual/SKILL.md +150 -0
  21. package/dist/subscribe.d.ts +0 -24
  22. package/dist/subscribe.js +0 -4
  23. package/skills/svelte/client-to-server/SKILL.md +0 -238
  24. package/skills/svelte/compose-with-tanstack-form/SKILL.md +0 -295
  25. package/skills/svelte/compose-with-tanstack-pacer/SKILL.md +0 -176
  26. package/skills/svelte/compose-with-tanstack-query/SKILL.md +0 -299
  27. package/skills/svelte/compose-with-tanstack-store/SKILL.md +0 -277
  28. package/skills/svelte/compose-with-tanstack-virtual/SKILL.md +0 -286
  29. package/skills/svelte/getting-started/SKILL.md +0 -340
  30. package/skills/svelte/migrate-v8-to-v9/SKILL.md +0 -256
  31. package/skills/svelte/production-readiness/SKILL.md +0 -256
  32. package/skills/svelte/table-state/SKILL.md +0 -441
  33. package/src/AppCell.svelte +0 -13
  34. package/src/AppHeader.svelte +0 -13
  35. package/src/AppTable.svelte +0 -11
  36. package/src/FlexRender.svelte +0 -103
  37. package/src/context-keys.ts +0 -3
  38. package/src/createTable.svelte.ts +0 -137
  39. package/src/createTableHook.svelte.ts +0 -639
  40. package/src/createTableState.svelte.ts +0 -30
  41. package/src/flex-render.ts +0 -3
  42. package/src/global.d.ts +0 -1
  43. package/src/index.ts +0 -21
  44. package/src/merge-objects.ts +0 -79
  45. package/src/reactivity.svelte.ts +0 -118
  46. package/src/render-component.ts +0 -107
  47. package/src/static-functions.ts +0 -1
  48. package/src/subscribe.ts +0 -46
@@ -1,299 +0,0 @@
1
- ---
2
- name: svelte/compose-with-tanstack-query
3
- description: >
4
- Server-side / async data flow with `@tanstack/svelte-query` and `@tanstack/svelte-table`.
5
- Key the `createQuery` on the table state that drives the request (pagination + sort +
6
- filters), pass `placeholderData: keepPreviousData` to avoid a "0 rows flash" between pages,
7
- set `manualPagination` (and optionally `manualSorting` / `manualFiltering`), supply
8
- `rowCount`, and feed the query result through reactive getters (`get data()`,
9
- `get rowCount()`). Own driver state with `$state` or `@tanstack/svelte-store` atoms.
10
- Svelte 5+ only.
11
- type: composition
12
- library: tanstack-table
13
- framework: svelte
14
- library_version: '9.0.0-alpha.48'
15
- requires:
16
- - svelte/client-to-server
17
- - pagination
18
- - state-management
19
- sources:
20
- - TanStack/table:examples/svelte/with-tanstack-query/
21
- - TanStack/table:docs/framework/svelte/guide/table-state.md
22
- ---
23
-
24
- # Compose with TanStack Query (Svelte)
25
-
26
- `@tanstack/svelte-query` and `@tanstack/svelte-table` complement each other naturally:
27
-
28
- - **Query** owns server data — fetching, caching, retries, placeholder data.
29
- - **Table** owns view state — pagination, sort, filters, selection.
30
-
31
- The integration is short and predictable: drive the query key from the view state, manual-mode
32
- the affected pipeline stages, pipe the result back through reactive getters.
33
-
34
- ## The pattern in 30 seconds
35
-
36
- ```svelte
37
- <script lang="ts">
38
- import { createQuery, keepPreviousData } from '@tanstack/svelte-query'
39
- import {
40
- createTable,
41
- rowPaginationFeature,
42
- tableFeatures,
43
- type PaginationState,
44
- } from '@tanstack/svelte-table'
45
-
46
- const features = tableFeatures({ rowPaginationFeature })
47
-
48
- let pagination: PaginationState = $state({ pageIndex: 0, pageSize: 10 })
49
-
50
- const dataQuery = createQuery<{
51
- rows: Array<Person>
52
- rowCount: number
53
- }>(() => ({
54
- queryKey: ['people', pagination],
55
- queryFn: () => fetchPeople(pagination),
56
- placeholderData: keepPreviousData,
57
- }))
58
-
59
- const table = createTable({
60
- features,
61
- rowModels: {},
62
- columns,
63
- get data() {
64
- return dataQuery.data?.rows ?? []
65
- },
66
- get rowCount() {
67
- return dataQuery.data?.rowCount
68
- },
69
- state: {
70
- get pagination() {
71
- return pagination
72
- },
73
- },
74
- onPaginationChange: (updater) => {
75
- pagination = typeof updater === 'function' ? updater(pagination) : updater
76
- },
77
- manualPagination: true,
78
- })
79
- </script>
80
- ```
81
-
82
- Three things to notice:
83
-
84
- 1. `queryKey` includes the driver state (`pagination`). Query re-fetches when the page or page
85
- size changes.
86
- 2. `placeholderData: keepPreviousData` keeps the previous page visible while the next page
87
- loads. Without it, `dataQuery.data?.rows` is `undefined` for one tick on every page change
88
- and the table flashes empty.
89
- 3. `manualPagination: true` tells the table the data is already paged. Without `rowCount` the
90
- pager has no idea how many pages exist.
91
-
92
- ## Driver-state ownership choices
93
-
94
- You can drive the query from either:
95
-
96
- - **Component `$state` + `state` + `on[State]Change`** (shown above) — simplest, mirrors
97
- what most v8 codebases look like after migration.
98
- - **External `@tanstack/svelte-store` atoms + `atoms`** — preferable when the same state
99
- drives multiple components (a toolbar, a sidebar, a URL syncer).
100
-
101
- ```ts
102
- import { createAtom, useSelector } from '@tanstack/svelte-store'
103
-
104
- const paginationAtom = createAtom<PaginationState>({
105
- pageIndex: 0,
106
- pageSize: 10,
107
- })
108
- const pagination = useSelector(paginationAtom)
109
-
110
- const dataQuery = createQuery(() => ({
111
- queryKey: ['people', pagination.current],
112
- queryFn: () => fetchPeople(pagination.current),
113
- placeholderData: keepPreviousData,
114
- }))
115
-
116
- const table = createTable({
117
- features,
118
- rowModels: {},
119
- columns,
120
- get data() {
121
- return dataQuery.data?.rows ?? []
122
- },
123
- get rowCount() {
124
- return dataQuery.data?.rowCount
125
- },
126
- atoms: { pagination: paginationAtom },
127
- manualPagination: true,
128
- })
129
- ```
130
-
131
- `table.setPageIndex(2)` writes through `paginationAtom`, which invalidates `queryKey`, which
132
- fetches page 3.
133
-
134
- ## Adding sort and filters
135
-
136
- ```ts
137
- import type { ColumnFiltersState, SortingState } from '@tanstack/svelte-table'
138
-
139
- let sorting: SortingState = $state([])
140
- let filters: ColumnFiltersState = $state([])
141
- let pagination: PaginationState = $state({ pageIndex: 0, pageSize: 10 })
142
-
143
- const dataQuery = createQuery(() => ({
144
- queryKey: ['people', pagination, sorting, filters],
145
- queryFn: () => fetchPeople({ pagination, sorting, filters }),
146
- placeholderData: keepPreviousData,
147
- }))
148
-
149
- const table = createTable({
150
- features: tableFeatures({
151
- rowPaginationFeature,
152
- rowSortingFeature,
153
- columnFilteringFeature,
154
- }),
155
- rowModels: {},
156
- columns,
157
- get data() {
158
- return dataQuery.data?.rows ?? []
159
- },
160
- get rowCount() {
161
- return dataQuery.data?.rowCount
162
- },
163
- state: {
164
- get pagination() {
165
- return pagination
166
- },
167
- get sorting() {
168
- return sorting
169
- },
170
- get columnFilters() {
171
- return filters
172
- },
173
- },
174
- onPaginationChange: (u) =>
175
- (pagination = typeof u === 'function' ? u(pagination) : u),
176
- onSortingChange: (u) => (sorting = typeof u === 'function' ? u(sorting) : u),
177
- onColumnFiltersChange: (u) =>
178
- (filters = typeof u === 'function' ? u(filters) : u),
179
- manualPagination: true,
180
- manualSorting: true,
181
- manualFiltering: true,
182
- })
183
- ```
184
-
185
- ## Reset page on filter / sort change
186
-
187
- Otherwise a user filters from "all 5000 people" to "5 named Alice" and stays on page 12.
188
-
189
- ```ts
190
- $effect(() => {
191
- // re-run on identity change
192
- filters
193
- sorting
194
- table.setPageIndex(0)
195
- })
196
- ```
197
-
198
- ## Debounce keystroke-driven filters
199
-
200
- Without debouncing, a search input fires one request per character.
201
-
202
- ```ts
203
- import { createDebouncer } from '@tanstack/svelte-pacer/debouncer'
204
-
205
- const debouncedSetGlobalFilter = createDebouncer(
206
- (value: string) => table.setGlobalFilter(value),
207
- { wait: 250 },
208
- )
209
- ```
210
-
211
- ```svelte
212
- <input
213
- oninput={(e) => debouncedSetGlobalFilter.maybeExecute(e.currentTarget.value)}
214
- />
215
- ```
216
-
217
- See the `compose-with-tanstack-pacer` skill for the full pacer pattern.
218
-
219
- ## Loading and empty states
220
-
221
- `createQuery` exposes `isFetching`, `isPending`, `isError`, `data`. Use them around the
222
- table, not inside the row loop.
223
-
224
- ```svelte
225
- {#if dataQuery.isPending}
226
- <div>Loading…</div>
227
- {:else if dataQuery.isError}
228
- <div>Failed: {dataQuery.error.message}</div>
229
- {:else}
230
- <table>...</table>
231
- {/if}
232
-
233
- {#if dataQuery.isFetching}
234
- <small>Refreshing…</small>
235
- {/if}
236
- ```
237
-
238
- `isFetching` is helpful for the "loading next page" indicator while
239
- `placeholderData: keepPreviousData` still shows the old rows.
240
-
241
- ## Optimistic updates (when you also mutate)
242
-
243
- ```ts
244
- import { createMutation, useQueryClient } from '@tanstack/svelte-query'
245
-
246
- const queryClient = useQueryClient()
247
-
248
- const updatePerson = createMutation(() => ({
249
- mutationFn: (input: Partial<Person>) =>
250
- fetch(`/api/people/${input.id}`, {
251
- method: 'PATCH',
252
- body: JSON.stringify(input),
253
- }),
254
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['people'] }),
255
- }))
256
- ```
257
-
258
- After the mutation succeeds, `invalidateQueries` re-fetches; `placeholderData` keeps the old
259
- rows visible during the refresh.
260
-
261
- ## SvelteKit `load` integration (a sketch)
262
-
263
- If your table is on a SvelteKit page, `+page.ts` can hydrate the query cache with the first
264
- page so SSR renders rows immediately. Subsequent pages still go through `createQuery`.
265
-
266
- ```ts
267
- // +page.ts
268
- export const load = async ({ fetch }) => {
269
- const initial = await fetchPeople({ pageIndex: 0, pageSize: 10 }, fetch)
270
- return { initial }
271
- }
272
- ```
273
-
274
- ```svelte
275
- <script lang="ts">
276
- let { data } = $props()
277
- // pass data.initial into placeholderData on first render
278
- </script>
279
- ```
280
-
281
- ## Common failure modes
282
-
283
- - **Forgot `rowCount`.** Pager shows zero pages.
284
- - **No `placeholderData: keepPreviousData`.** Empty-table flash on every page change.
285
- - **Forgot `manualPagination: true`.** Table tries to paginate the already-paged window.
286
- `getPageCount()` returns 1.
287
- - **Driver state in `queryKey` is stale.** Always pass the current value, not a captured one.
288
- - **No reset on filter change.** Stays on dead pages.
289
- - **Plain `data: dataQuery.data?.rows`.** No reactivity — must be a getter.
290
- - **Re-creating `createQuery` inside `$effect`.** It's a one-time call; create it at component init.
291
- - **Reimplementing pagination math against `query.data` instead of calling
292
- `table.nextPage()`.** Don't.
293
-
294
- ## Related skills
295
-
296
- - `tanstack-table/svelte/client-to-server` — base server-side pattern (without Query).
297
- - `tanstack-table/svelte/compose-with-tanstack-store` — atom-based driver state.
298
- - `tanstack-table/svelte/compose-with-tanstack-pacer` — debounce / throttle high-frequency inputs.
299
- - `tanstack-table/core/pagination` — manual mode semantics.
@@ -1,277 +0,0 @@
1
- ---
2
- name: svelte/compose-with-tanstack-store
3
- description: >
4
- TanStack Table v9 is built on TanStack Store. Each state slice (sorting, pagination,
5
- rowSelection, columnFilters, ...) is a separate atom. In Svelte, `@tanstack/svelte-store`
6
- exposes `createAtom`, `useSelector`, `shallow`. Read `table.atoms.<slice>` per slice,
7
- `table.store` flat, or `table.state` for the selector projection. Subscribe with
8
- `subscribeTable(atom, selector?)` (returns `.current`). Own a slice externally with
9
- `createAtom` + `atoms: { sorting: sortingAtom }`. Svelte 5+ only — `$state` / `$derived.by` /
10
- `$effect.pre` reactivity.
11
- type: composition
12
- library: tanstack-table
13
- framework: svelte
14
- library_version: '9.0.0-alpha.48'
15
- requires:
16
- - state-management
17
- sources:
18
- - TanStack/table:docs/framework/svelte/guide/table-state.md
19
- - TanStack/table:packages/svelte-table/src/reactivity.svelte.ts
20
- - TanStack/table:packages/svelte-table/src/subscribe.ts
21
- - TanStack/table:examples/svelte/basic-external-atoms/
22
- ---
23
-
24
- # Compose with TanStack Store (Svelte)
25
-
26
- `@tanstack/svelte-store` is the reactive primitive under `@tanstack/svelte-table` v9. The
27
- table doesn't merely _use_ Store — its entire reactivity model is built from Store atoms with
28
- rune backings.
29
-
30
- ## Mental model — three read surfaces
31
-
32
- A registered v9 table exposes:
33
-
34
- | Surface | Shape | When to use |
35
- | --------------------- | --------------------------- | ------------------------------------------ |
36
- | `table.atoms.<slice>` | `ReadonlyAtom<TSlice>` | Per-slice subscription / `.get()` snapshot |
37
- | `table.store` | `ReadonlyStore<FlatState>` | Flat snapshot across registered slices |
38
- | `table.state` | `TSelected` (from selector) | The selector projection (Svelte-only) |
39
-
40
- Plus the writable internals:
41
-
42
- - `table.baseAtoms.<slice>` — writable atom for state the table owns.
43
-
44
- If a slice is supplied externally via `atoms`, `table.atoms.<slice>` reads from your atom and
45
- `table.baseAtoms.<slice>` is unused for that slice.
46
-
47
- ## The Svelte bindings (what `svelteReactivity()` actually does)
48
-
49
- The Svelte adapter ships `svelteReactivity()` and installs it as `coreReativityFeature`. It
50
- maps Store primitives to runes:
51
-
52
- - Readonly atoms → `$derived.by(fn)`
53
- - Writable atoms → `$state(initialValue)`
54
- - Subscriptions → `$effect.root` + `$effect`
55
- - Batch → `flushSync`
56
-
57
- This is why simple atom reads inside `.svelte` components (templates, `$derived`, `$effect`)
58
- participate in reactivity automatically. There is no React-style `useStore` requirement.
59
-
60
- ## Pattern 1 — Read a slice without subscribing
61
-
62
- For event handlers, async work, exports, anything outside of reactive markup. Cheap, no
63
- subscription setup.
64
-
65
- ```ts
66
- import type { SortingState } from '@tanstack/svelte-table'
67
-
68
- function logSort() {
69
- const sorting: SortingState = table.atoms.sorting.get()
70
- console.log(sorting)
71
- }
72
- ```
73
-
74
- `table.state` is the full snapshot equivalent.
75
-
76
- ## Pattern 2 — Reactive selector via `createTable`
77
-
78
- The second argument to `createTable` is a TanStack Store selector. The result is exposed on
79
- `table.state`. The default selector is `(state) => state`.
80
-
81
- ```svelte
82
- <script lang="ts">
83
- const table = createTable(
84
- {
85
- features,
86
- rowModels: { paginatedRowModel: createPaginatedRowModel() },
87
- columns,
88
- get data() {
89
- return data
90
- },
91
- },
92
- (state) => ({
93
- pageIndex: state.pagination.pageIndex,
94
- pageSize: state.pagination.pageSize,
95
- }),
96
- )
97
- </script>
98
-
99
- <strong>Page {table.state.pageIndex + 1}</strong>
100
- ```
101
-
102
- The narrower the selector, the less your markup re-renders.
103
-
104
- ## Pattern 3 — Per-block subscription with `subscribeTable`
105
-
106
- `subscribeTable(source, selector?)` is the dedicated per-component subscription. It uses
107
- `shallow` compare and exposes a `.current` accessor.
108
-
109
- ```svelte
110
- <script lang="ts">
111
- import { subscribeTable } from '@tanstack/svelte-table'
112
-
113
- // whole slice
114
- const pagination = subscribeTable(table.atoms.pagination)
115
-
116
- // narrowed
117
- const pageSize = subscribeTable(table.atoms.pagination, (p) => p.pageSize)
118
-
119
- // works on table.store too
120
- const fullSnapshot = subscribeTable(table.store)
121
- </script>
122
-
123
- <span
124
- >Page {pagination.current.pageIndex + 1} ({pageSize.current} per page)</span
125
- >
126
- ```
127
-
128
- Inside per-row components, `subscribeTable(table.atoms.rowSelection, (s) => !!s[row.id])` keeps
129
- that row's checkbox reactive without subscribing to the entire selection map.
130
-
131
- ## Pattern 4 — Own a slice externally with `createAtom`
132
-
133
- When the app should own a slice — share across components, sync with URL, persist to storage —
134
- create a stable atom and hand it to the table via `atoms`.
135
-
136
- ```ts
137
- import { createAtom, useSelector } from '@tanstack/svelte-store'
138
- import {
139
- createTable,
140
- rowPaginationFeature,
141
- rowSortingFeature,
142
- tableFeatures,
143
- type PaginationState,
144
- type SortingState,
145
- } from '@tanstack/svelte-table'
146
-
147
- const features = tableFeatures({
148
- rowPaginationFeature,
149
- rowSortingFeature,
150
- })
151
-
152
- const sortingAtom = createAtom<SortingState>([])
153
- const paginationAtom = createAtom<PaginationState>({
154
- pageIndex: 0,
155
- pageSize: 10,
156
- })
157
-
158
- // Optional: a Svelte-reactive view onto each atom for use in markup.
159
- const sorting = useSelector(sortingAtom)
160
- const pagination = useSelector(paginationAtom)
161
-
162
- const table = createTable({
163
- features,
164
- rowModels: {
165
- /* ... */
166
- },
167
- columns,
168
- get data() {
169
- return data
170
- },
171
- atoms: {
172
- sorting: sortingAtom,
173
- pagination: paginationAtom,
174
- },
175
- })
176
-
177
- // table.setPageIndex(2) writes through paginationAtom.
178
- // paginationAtom.set(...) updates table.atoms.pagination immediately.
179
- ```
180
-
181
- Atom precedence: external `atoms.<slice>` wins over external `state.<slice>` which writes
182
- into the internal `baseAtoms.<slice>`. **Never combine them on the same slice.**
183
-
184
- ## Pattern 5 — Cross-component / cross-module state
185
-
186
- Because atoms are first-class subscribable values, you can read them outside the table component.
187
-
188
- ```ts
189
- // stores/table-state.ts
190
- import { createAtom } from '@tanstack/svelte-store'
191
- import type { RowSelectionState } from '@tanstack/svelte-table'
192
-
193
- export const rowSelectionAtom = createAtom<RowSelectionState>({})
194
- ```
195
-
196
- ```svelte
197
- <!-- Toolbar.svelte -->
198
- <script lang="ts">
199
- import { useSelector } from '@tanstack/svelte-store'
200
- import { rowSelectionAtom } from './stores/table-state'
201
-
202
- const selection = useSelector(rowSelectionAtom)
203
- const selectedCount = $derived(
204
- Object.values(selection.current).filter(Boolean).length,
205
- )
206
- </script>
207
-
208
- <button disabled={selectedCount === 0}>Delete {selectedCount}</button>
209
- ```
210
-
211
- ```svelte
212
- <!-- TablePage.svelte -->
213
- <script lang="ts">
214
- import { rowSelectionAtom } from './stores/table-state'
215
-
216
- const table = createTable({
217
- features,
218
- rowModels: {
219
- /* ... */
220
- },
221
- columns,
222
- get data() {
223
- return data
224
- },
225
- atoms: { rowSelection: rowSelectionAtom },
226
- })
227
- </script>
228
- ```
229
-
230
- ## Pattern 6 — `useSelector` with custom equality
231
-
232
- `useSelector(source, selector, { compare })` lets you switch comparison strategies — useful
233
- for object selectors so you don't re-fire on every reference change.
234
-
235
- ```ts
236
- import { shallow, useSelector } from '@tanstack/svelte-store'
237
-
238
- const filterValues = useSelector(
239
- table.atoms.columnFilters,
240
- (filters) => Object.fromEntries(filters.map((f) => [f.id, f.value])),
241
- { compare: shallow },
242
- )
243
- ```
244
-
245
- `subscribeTable` already uses `shallow` by default, so prefer it for table sources unless you
246
- need a custom compare.
247
-
248
- ## Pattern 7 — Direct base-atom writes (last resort)
249
-
250
- When a slice is internally owned and you really need to write outside a feature API:
251
-
252
- ```ts
253
- table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
254
- ```
255
-
256
- Do not do this for externally-owned slices — write to your external atom instead. The base
257
- atom is dormant in that case and your write will be silently ignored next sync.
258
-
259
- ## Common failure modes
260
-
261
- - **Reading a slice that wasn't registered.** `table.atoms.rowSelection` is `undefined` if
262
- `rowSelectionFeature` isn't in `features`. TS will catch it if you used `tableFeatures()`.
263
- - **Creating atoms inside reactive blocks.** Atoms must be stable. Module scope or top-level
264
- component scope, never inside `$derived` / `$effect`.
265
- - **`useSelector` without `.current`.** `selection.pageIndex` is wrong — `selection.current.pageIndex`.
266
- - **Mixing `atoms.X` and `state.X`.** Atom wins, callback never fires.
267
- - **`tableState` as a plain object.** No reactivity. Use `subscribeTable`, `useSelector`, or
268
- the `createTable` selector.
269
- - **Reimplementing `useSelector` with `$effect`.** Built-in is more efficient and uses
270
- shallow compare.
271
-
272
- ## Related skills
273
-
274
- - `tanstack-table/svelte/table-state` — full reactivity model and selector patterns.
275
- - `tanstack-table/core/state-management` — atom precedence rules.
276
- - `tanstack-table/svelte/client-to-server` — atoms as the data-driver for server queries.
277
- - `tanstack-table/svelte/production-readiness` — selector / subscription tuning.