@tanstack/svelte-table 9.0.0-alpha.5 → 9.0.0-alpha.50
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.
- package/README.md +127 -0
- package/dist/AppCell.svelte +13 -0
- package/dist/AppCell.svelte.d.ts +9 -0
- package/dist/AppHeader.svelte +13 -0
- package/dist/AppHeader.svelte.d.ts +9 -0
- package/dist/AppTable.svelte +11 -0
- package/dist/AppTable.svelte.d.ts +7 -0
- package/dist/FlexRender.svelte +103 -0
- package/dist/FlexRender.svelte.d.ts +51 -0
- package/dist/context-keys.d.ts +3 -0
- package/dist/context-keys.js +3 -0
- package/dist/createTable.svelte.d.ts +38 -0
- package/dist/createTable.svelte.js +79 -0
- package/dist/createTableHook.svelte.d.ts +235 -0
- package/dist/createTableHook.svelte.js +170 -0
- package/dist/createTableState.svelte.d.ts +17 -0
- package/dist/createTableState.svelte.js +27 -0
- package/dist/flex-render.d.ts +1 -0
- package/dist/flex-render.js +2 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +6 -3
- package/dist/merge-objects.d.ts +24 -0
- package/dist/merge-objects.js +45 -0
- package/dist/reactivity.svelte.d.ts +9 -0
- package/dist/reactivity.svelte.js +55 -0
- package/dist/render-component.d.ts +66 -9
- package/dist/render-component.js +62 -4
- package/dist/static-functions.d.ts +1 -0
- package/dist/static-functions.js +1 -0
- package/dist/subscribe.d.ts +24 -0
- package/dist/subscribe.js +4 -0
- package/package.json +31 -11
- package/skills/svelte/client-to-server/SKILL.md +238 -0
- package/skills/svelte/compose-with-tanstack-form/SKILL.md +295 -0
- package/skills/svelte/compose-with-tanstack-pacer/SKILL.md +176 -0
- package/skills/svelte/compose-with-tanstack-query/SKILL.md +299 -0
- package/skills/svelte/compose-with-tanstack-store/SKILL.md +277 -0
- package/skills/svelte/compose-with-tanstack-virtual/SKILL.md +286 -0
- package/skills/svelte/getting-started/SKILL.md +340 -0
- package/skills/svelte/migrate-v8-to-v9/SKILL.md +256 -0
- package/skills/svelte/production-readiness/SKILL.md +256 -0
- package/skills/svelte/table-state/SKILL.md +441 -0
- package/src/AppCell.svelte +13 -0
- package/src/AppHeader.svelte +13 -0
- package/src/AppTable.svelte +11 -0
- package/src/FlexRender.svelte +103 -0
- package/src/context-keys.ts +3 -0
- package/src/createTable.svelte.ts +127 -0
- package/src/createTableHook.svelte.ts +639 -0
- package/src/createTableState.svelte.ts +30 -0
- package/src/flex-render.ts +3 -0
- package/src/index.ts +20 -3
- package/src/merge-objects.ts +79 -0
- package/src/reactivity.svelte.ts +74 -0
- package/src/render-component.ts +75 -9
- package/src/static-functions.ts +1 -0
- package/src/subscribe.ts +46 -0
- package/dist/flex-render.svelte +0 -35
- package/dist/flex-render.svelte.d.ts +0 -28
- package/dist/table.svelte.d.ts +0 -28
- package/dist/table.svelte.js +0 -87
- package/src/flex-render.svelte +0 -35
- package/src/table.svelte.ts +0 -117
|
@@ -1,20 +1,51 @@
|
|
|
1
|
-
import type { Component, ComponentProps } from 'svelte';
|
|
1
|
+
import type { Component, ComponentProps, Snippet } from 'svelte';
|
|
2
2
|
/**
|
|
3
|
-
* A helper class to make it easy to identify Svelte components in
|
|
3
|
+
* A helper class to make it easy to identify Svelte components in
|
|
4
|
+
* `columnDef.cell` and `columnDef.header` properties.
|
|
5
|
+
*
|
|
6
|
+
* > NOTE: This class should only be used internally by the adapter. If you're
|
|
7
|
+
* reading this and you don't know what this is for, you probably don't need it.
|
|
8
|
+
*
|
|
4
9
|
* @example
|
|
5
10
|
* ```svelte
|
|
6
|
-
* {
|
|
7
|
-
*
|
|
11
|
+
* {@const result = content(context as any)}
|
|
12
|
+
* {#if result instanceof RenderComponentConfig}
|
|
13
|
+
* {@const { component: Component, props } = result}
|
|
14
|
+
* <Component {...props} />
|
|
8
15
|
* {/if}
|
|
9
16
|
* ```
|
|
10
17
|
* */
|
|
11
|
-
export declare class RenderComponentConfig<TComponent extends Component
|
|
18
|
+
export declare class RenderComponentConfig<TComponent extends Component> {
|
|
12
19
|
component: TComponent;
|
|
13
|
-
props
|
|
14
|
-
constructor(component: TComponent, props
|
|
20
|
+
props?: (ComponentProps<TComponent> | Record<string, never>) | undefined;
|
|
21
|
+
constructor(component: TComponent, props?: (ComponentProps<TComponent> | Record<string, never>) | undefined);
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
|
-
* A helper
|
|
24
|
+
* A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties.
|
|
25
|
+
*
|
|
26
|
+
* > NOTE: This class should only be used internally by the adapter. If you're
|
|
27
|
+
* reading this and you don't know what this is for, you probably don't need it.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```svelte
|
|
31
|
+
* {@const result = content(context as any)}
|
|
32
|
+
* {#if result instanceof RenderSnippetConfig}
|
|
33
|
+
* {@const { snippet, params } = result}
|
|
34
|
+
* {@render snippet(params)}
|
|
35
|
+
* {/if}
|
|
36
|
+
* ```
|
|
37
|
+
* */
|
|
38
|
+
export declare class RenderSnippetConfig<TProps> {
|
|
39
|
+
snippet: Snippet<[TProps]>;
|
|
40
|
+
params?: TProps | undefined;
|
|
41
|
+
constructor(snippet: Snippet<[TProps]>, params?: TProps | undefined);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Wraps a Svelte component so it can be returned from a column definition
|
|
45
|
+
* renderer such as `cell`, `header`, or `footer`.
|
|
46
|
+
*
|
|
47
|
+
* This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets.
|
|
48
|
+
*
|
|
18
49
|
* @param component A Svelte component
|
|
19
50
|
* @param props The props to pass to `component`
|
|
20
51
|
* @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component.
|
|
@@ -32,4 +63,30 @@ export declare class RenderComponentConfig<TComponent extends Component<any>> {
|
|
|
32
63
|
* ```
|
|
33
64
|
* @see {@link https://tanstack.com/table/latest/docs/guide/column-defs}
|
|
34
65
|
*/
|
|
35
|
-
export declare const renderComponent: <TComponent extends Component<any
|
|
66
|
+
export declare const renderComponent: <TComponent extends Component<any>, TProps extends ComponentProps<TComponent>>(component: TComponent, props?: TProps) => RenderComponentConfig<TComponent>;
|
|
67
|
+
/**
|
|
68
|
+
* Wraps a Svelte snippet so it can be returned from a column definition
|
|
69
|
+
* renderer such as `cell`, `header`, or `footer`.
|
|
70
|
+
*
|
|
71
|
+
* *The snippet must only take one parameter.*
|
|
72
|
+
*
|
|
73
|
+
* This is only to be used with Snippets - use `renderComponent` for Svelte Components.
|
|
74
|
+
*
|
|
75
|
+
* @param snippet The snippet to render.
|
|
76
|
+
* @param params The single parameter object passed to the snippet.
|
|
77
|
+
* @returns A `RenderSnippetConfig` consumed by the Svelte `FlexRender` component.
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // +page.svelte
|
|
81
|
+
* const defaultColumns = [
|
|
82
|
+
* columnHelper.accessor('name', {
|
|
83
|
+
* cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }),
|
|
84
|
+
* }),
|
|
85
|
+
* columnHelper.accessor('state', {
|
|
86
|
+
* cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }),
|
|
87
|
+
* }),
|
|
88
|
+
* ]
|
|
89
|
+
* ```
|
|
90
|
+
* @see {@link https://tanstack.com/table/latest/docs/guide/column-defs}
|
|
91
|
+
*/
|
|
92
|
+
export declare const renderSnippet: <TProps>(snippet: Snippet<[TProps]>, params?: TProps) => RenderSnippetConfig<TProps>;
|
package/dist/render-component.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A helper class to make it easy to identify Svelte components in
|
|
2
|
+
* A helper class to make it easy to identify Svelte components in
|
|
3
|
+
* `columnDef.cell` and `columnDef.header` properties.
|
|
4
|
+
*
|
|
5
|
+
* > NOTE: This class should only be used internally by the adapter. If you're
|
|
6
|
+
* reading this and you don't know what this is for, you probably don't need it.
|
|
7
|
+
*
|
|
3
8
|
* @example
|
|
4
9
|
* ```svelte
|
|
5
|
-
* {
|
|
6
|
-
*
|
|
10
|
+
* {@const result = content(context as any)}
|
|
11
|
+
* {#if result instanceof RenderComponentConfig}
|
|
12
|
+
* {@const { component: Component, props } = result}
|
|
13
|
+
* <Component {...props} />
|
|
7
14
|
* {/if}
|
|
8
15
|
* ```
|
|
9
16
|
* */
|
|
@@ -14,7 +21,32 @@ export class RenderComponentConfig {
|
|
|
14
21
|
}
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
|
-
* A helper
|
|
24
|
+
* A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties.
|
|
25
|
+
*
|
|
26
|
+
* > NOTE: This class should only be used internally by the adapter. If you're
|
|
27
|
+
* reading this and you don't know what this is for, you probably don't need it.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```svelte
|
|
31
|
+
* {@const result = content(context as any)}
|
|
32
|
+
* {#if result instanceof RenderSnippetConfig}
|
|
33
|
+
* {@const { snippet, params } = result}
|
|
34
|
+
* {@render snippet(params)}
|
|
35
|
+
* {/if}
|
|
36
|
+
* ```
|
|
37
|
+
* */
|
|
38
|
+
export class RenderSnippetConfig {
|
|
39
|
+
constructor(snippet, params) {
|
|
40
|
+
this.snippet = snippet;
|
|
41
|
+
this.params = params;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Wraps a Svelte component so it can be returned from a column definition
|
|
46
|
+
* renderer such as `cell`, `header`, or `footer`.
|
|
47
|
+
*
|
|
48
|
+
* This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets.
|
|
49
|
+
*
|
|
18
50
|
* @param component A Svelte component
|
|
19
51
|
* @param props The props to pass to `component`
|
|
20
52
|
* @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component.
|
|
@@ -33,3 +65,29 @@ export class RenderComponentConfig {
|
|
|
33
65
|
* @see {@link https://tanstack.com/table/latest/docs/guide/column-defs}
|
|
34
66
|
*/
|
|
35
67
|
export const renderComponent = (component, props) => new RenderComponentConfig(component, props);
|
|
68
|
+
/**
|
|
69
|
+
* Wraps a Svelte snippet so it can be returned from a column definition
|
|
70
|
+
* renderer such as `cell`, `header`, or `footer`.
|
|
71
|
+
*
|
|
72
|
+
* *The snippet must only take one parameter.*
|
|
73
|
+
*
|
|
74
|
+
* This is only to be used with Snippets - use `renderComponent` for Svelte Components.
|
|
75
|
+
*
|
|
76
|
+
* @param snippet The snippet to render.
|
|
77
|
+
* @param params The single parameter object passed to the snippet.
|
|
78
|
+
* @returns A `RenderSnippetConfig` consumed by the Svelte `FlexRender` component.
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* // +page.svelte
|
|
82
|
+
* const defaultColumns = [
|
|
83
|
+
* columnHelper.accessor('name', {
|
|
84
|
+
* cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }),
|
|
85
|
+
* }),
|
|
86
|
+
* columnHelper.accessor('state', {
|
|
87
|
+
* cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }),
|
|
88
|
+
* }),
|
|
89
|
+
* ]
|
|
90
|
+
* ```
|
|
91
|
+
* @see {@link https://tanstack.com/table/latest/docs/guide/column-defs}
|
|
92
|
+
*/
|
|
93
|
+
export const renderSnippet = (snippet, params) => new RenderSnippetConfig(snippet, params);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tanstack/table-core/static-functions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tanstack/table-core/static-functions';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useSelector } from '@tanstack/svelte-store';
|
|
2
|
+
import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/svelte-store';
|
|
3
|
+
export type SubscribeSource<TValue> = Atom<TValue> | ReadonlyAtom<TValue> | Store<TValue> | ReadonlyStore<TValue>;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a fine-grained Svelte subscription to a TanStack Store source.
|
|
6
|
+
*
|
|
7
|
+
* Pass a table atom or store and optionally project it with a selector. The
|
|
8
|
+
* returned selector store exposes `.current`, making it useful for reading
|
|
9
|
+
* focused table state outside the broad `createTable` selector.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <script lang="ts">
|
|
14
|
+
* const selected = subscribeTable(
|
|
15
|
+
* table.atoms.rowSelection,
|
|
16
|
+
* (rowSelection) => rowSelection[row.id],
|
|
17
|
+
* )
|
|
18
|
+
* </script>
|
|
19
|
+
*
|
|
20
|
+
* <input type="checkbox" checked={!!selected.current} />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function subscribeTable<TSourceValue>(source: SubscribeSource<TSourceValue>): ReturnType<typeof useSelector<TSourceValue>>;
|
|
24
|
+
export declare function subscribeTable<TSourceValue, TSelected>(source: SubscribeSource<TSourceValue>, selector: (state: TSourceValue) => TSelected): ReturnType<typeof useSelector<TSourceValue, TSelected>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/svelte-table",
|
|
3
|
-
"version": "9.0.0-alpha.
|
|
3
|
+
"version": "9.0.0-alpha.50",
|
|
4
4
|
"description": "Headless UI for building powerful tables & datagrids for Svelte.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"svelte",
|
|
19
19
|
"table",
|
|
20
20
|
"svelte-table",
|
|
21
|
-
"datagrid"
|
|
21
|
+
"datagrid",
|
|
22
|
+
"tanstack-intent"
|
|
22
23
|
],
|
|
23
24
|
"type": "module",
|
|
24
25
|
"types": "dist/index.d.ts",
|
|
@@ -30,26 +31,45 @@
|
|
|
30
31
|
"svelte": "./dist/index.js",
|
|
31
32
|
"import": "./dist/index.js"
|
|
32
33
|
},
|
|
34
|
+
"./static-functions": {
|
|
35
|
+
"types": "./dist/static-functions.d.ts",
|
|
36
|
+
"svelte": "./dist/static-functions.js",
|
|
37
|
+
"import": "./dist/static-functions.js"
|
|
38
|
+
},
|
|
39
|
+
"./flex-render": {
|
|
40
|
+
"types": "./dist/flex-render.d.ts",
|
|
41
|
+
"svelte": "./dist/flex-render.js",
|
|
42
|
+
"import": "./dist/flex-render.js"
|
|
43
|
+
},
|
|
33
44
|
"./package.json": "./package.json"
|
|
34
45
|
},
|
|
35
46
|
"engines": {
|
|
36
|
-
"node": ">=
|
|
47
|
+
"node": ">=16"
|
|
37
48
|
},
|
|
38
49
|
"files": [
|
|
39
50
|
"dist",
|
|
40
|
-
"src"
|
|
51
|
+
"src",
|
|
52
|
+
"skills"
|
|
41
53
|
],
|
|
42
54
|
"dependencies": {
|
|
43
|
-
"@tanstack/
|
|
55
|
+
"@tanstack/svelte-store": "^0.12.0",
|
|
56
|
+
"@tanstack/table-core": "9.0.0-alpha.49"
|
|
44
57
|
},
|
|
45
58
|
"devDependencies": {
|
|
46
|
-
"@sveltejs/package": "^2.
|
|
47
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
48
|
-
"svelte": "^
|
|
49
|
-
"svelte
|
|
59
|
+
"@sveltejs/package": "^2.5.7",
|
|
60
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
61
|
+
"eslint-plugin-svelte": "^3.17.1",
|
|
62
|
+
"svelte": "^5.55.7",
|
|
63
|
+
"svelte-check": "^4.4.8"
|
|
50
64
|
},
|
|
51
65
|
"peerDependencies": {
|
|
52
|
-
"svelte": "^5.0.0
|
|
66
|
+
"svelte": "^5.0.0"
|
|
53
67
|
},
|
|
54
|
-
"scripts": {
|
|
68
|
+
"scripts": {
|
|
69
|
+
"clean": "rimraf ./build && rimraf ./dist",
|
|
70
|
+
"test:eslint": "eslint ./src",
|
|
71
|
+
"test:types": "svelte-check --tsconfig ./tsconfig.json",
|
|
72
|
+
"test:build": "publint --strict",
|
|
73
|
+
"build": "svelte-package --input ./src --output ./dist"
|
|
74
|
+
}
|
|
55
75
|
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: svelte/client-to-server
|
|
3
|
+
description: >
|
|
4
|
+
Convert a client-side Svelte table to server-side (manual) modes. Toggle `manualPagination`,
|
|
5
|
+
`manualSorting`, `manualFiltering`, `manualGrouping`, `manualExpanding` for whatever the server
|
|
6
|
+
owns, drop the matching `_rowModels` factories and `_features` you no longer need, supply
|
|
7
|
+
`rowCount` for the pager, then drive the request from `table.atoms.pagination` /
|
|
8
|
+
`table.atoms.sorting` / etc. (or external atoms you own) — using rune-aware getters
|
|
9
|
+
(`get data()`, `get rowCount()`) so the table re-syncs in `$effect.pre`. Svelte 5+ only.
|
|
10
|
+
type: lifecycle
|
|
11
|
+
library: tanstack-table
|
|
12
|
+
framework: svelte
|
|
13
|
+
library_version: '9.0.0-alpha.48'
|
|
14
|
+
requires:
|
|
15
|
+
- state-management
|
|
16
|
+
- pagination
|
|
17
|
+
- filtering
|
|
18
|
+
- sorting
|
|
19
|
+
- svelte/table-state
|
|
20
|
+
sources:
|
|
21
|
+
- TanStack/table:examples/svelte/basic-external-atoms/
|
|
22
|
+
- TanStack/table:examples/svelte/basic-external-state/
|
|
23
|
+
- TanStack/table:examples/svelte/with-tanstack-query/
|
|
24
|
+
- TanStack/table:docs/framework/svelte/guide/table-state.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Client → Server (Svelte)
|
|
28
|
+
|
|
29
|
+
You have a working client-side table. The dataset is too big to ship to the browser, or it
|
|
30
|
+
lives behind an API. You want sorting / filtering / pagination to run on the server while the
|
|
31
|
+
table still feels the same in the UI.
|
|
32
|
+
|
|
33
|
+
## Mental model
|
|
34
|
+
|
|
35
|
+
Each "manual mode" flag tells the table: **don't run this stage of the pipeline; trust the data
|
|
36
|
+
you receive.** You can mix modes freely — manual pagination + client-side sorting on the
|
|
37
|
+
already-paged window is perfectly valid for medium datasets.
|
|
38
|
+
|
|
39
|
+
| Flag | Meaning | What you must provide |
|
|
40
|
+
| ------------------ | -------------------------------------------- | ----------------------------------------------------------- |
|
|
41
|
+
| `manualPagination` | Server owns slicing; do not paginate locally | `rowCount` (or `pageCount`) |
|
|
42
|
+
| `manualSorting` | Server owns ordering | Sort the server query by `sorting` state |
|
|
43
|
+
| `manualFiltering` | Server owns row filtering | Filter the server query by `columnFilters` / `globalFilter` |
|
|
44
|
+
| `manualGrouping` | Server returns already-grouped rows | Pre-shaped data |
|
|
45
|
+
| `manualExpanding` | Server resolves sub-rows | Server-provided sub-row tree |
|
|
46
|
+
|
|
47
|
+
When a stage is manual, you can drop its row-model factory. `manualPagination: true` does not
|
|
48
|
+
need `paginatedRowModel: createPaginatedRowModel()`.
|
|
49
|
+
|
|
50
|
+
## Step 1 — Identify what's moving server-side
|
|
51
|
+
|
|
52
|
+
For a typical "search and paginate against a database" screen:
|
|
53
|
+
|
|
54
|
+
- Pagination → server
|
|
55
|
+
- Filtering (column filter inputs + a global search box) → server
|
|
56
|
+
- Sorting → server (usually, since a partial page can't be sorted client-side meaningfully)
|
|
57
|
+
- Selection / visibility / column ordering → still client
|
|
58
|
+
|
|
59
|
+
So the table keeps `rowSelectionFeature` etc., drops `columnFilteringFeature` /
|
|
60
|
+
`rowPaginationFeature` / `rowSortingFeature` _row models_ but keeps the _features_ so the
|
|
61
|
+
state slices and UI APIs still exist.
|
|
62
|
+
|
|
63
|
+
> Subtle point: keep the **feature** even if you drop the row model. The feature is what gives
|
|
64
|
+
> you `column.getCanSort()`, `table.setPageIndex()`, `column.setFilterValue()` — all the
|
|
65
|
+
> control-surface APIs. Dropping it kills the UI.
|
|
66
|
+
|
|
67
|
+
## Step 2 — Own the relevant state with external atoms
|
|
68
|
+
|
|
69
|
+
External atoms make state portable: the data layer (a fetch / query / store) can read the
|
|
70
|
+
same atoms the table writes to. Use `@tanstack/svelte-store`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { createAtom, useSelector } from '@tanstack/svelte-store'
|
|
74
|
+
import type {
|
|
75
|
+
ColumnFiltersState,
|
|
76
|
+
PaginationState,
|
|
77
|
+
SortingState,
|
|
78
|
+
} from '@tanstack/svelte-table'
|
|
79
|
+
|
|
80
|
+
const paginationAtom = createAtom<PaginationState>({
|
|
81
|
+
pageIndex: 0,
|
|
82
|
+
pageSize: 10,
|
|
83
|
+
})
|
|
84
|
+
const sortingAtom = createAtom<SortingState>([])
|
|
85
|
+
const filtersAtom = createAtom<ColumnFiltersState>([])
|
|
86
|
+
|
|
87
|
+
// For Svelte markup that should react to changes:
|
|
88
|
+
const pagination = useSelector(paginationAtom)
|
|
89
|
+
const sorting = useSelector(sortingAtom)
|
|
90
|
+
const filters = useSelector(filtersAtom)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Step 3 — Configure the table
|
|
94
|
+
|
|
95
|
+
```svelte
|
|
96
|
+
<script lang="ts">
|
|
97
|
+
import {
|
|
98
|
+
columnFilteringFeature,
|
|
99
|
+
createTable,
|
|
100
|
+
rowPaginationFeature,
|
|
101
|
+
rowSortingFeature,
|
|
102
|
+
tableFeatures,
|
|
103
|
+
} from '@tanstack/svelte-table'
|
|
104
|
+
|
|
105
|
+
const _features = tableFeatures({
|
|
106
|
+
columnFilteringFeature,
|
|
107
|
+
rowPaginationFeature,
|
|
108
|
+
rowSortingFeature,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// No row-model factories for these — server owns them.
|
|
112
|
+
const table = createTable({
|
|
113
|
+
_features,
|
|
114
|
+
_rowModels: {},
|
|
115
|
+
columns,
|
|
116
|
+
get data() {
|
|
117
|
+
return query.data?.rows ?? []
|
|
118
|
+
},
|
|
119
|
+
get rowCount() {
|
|
120
|
+
return query.data?.rowCount
|
|
121
|
+
},
|
|
122
|
+
atoms: {
|
|
123
|
+
pagination: paginationAtom,
|
|
124
|
+
sorting: sortingAtom,
|
|
125
|
+
columnFilters: filtersAtom,
|
|
126
|
+
},
|
|
127
|
+
manualPagination: true,
|
|
128
|
+
manualSorting: true,
|
|
129
|
+
manualFiltering: true,
|
|
130
|
+
})
|
|
131
|
+
</script>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`rowCount` is what makes `table.getPageCount()` / `table.getCanNextPage()` correct under
|
|
135
|
+
manual pagination. Without it the pager has no idea how many pages exist.
|
|
136
|
+
|
|
137
|
+
## Step 4 — Drive the fetch from those atoms
|
|
138
|
+
|
|
139
|
+
Wire whatever data layer you use (TanStack Query, a raw `fetch`, SvelteKit `load`, etc.) to
|
|
140
|
+
read the atoms. With TanStack Query:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { createQuery, keepPreviousData } from '@tanstack/svelte-query'
|
|
144
|
+
|
|
145
|
+
const dataQuery = createQuery<{ rows: Array<Person>; rowCount: number }>(
|
|
146
|
+
() => ({
|
|
147
|
+
queryKey: ['people', pagination.current, sorting.current, filters.current],
|
|
148
|
+
queryFn: () =>
|
|
149
|
+
fetch('/api/people', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
pageIndex: pagination.current.pageIndex,
|
|
153
|
+
pageSize: pagination.current.pageSize,
|
|
154
|
+
sorting: sorting.current,
|
|
155
|
+
filters: filters.current,
|
|
156
|
+
}),
|
|
157
|
+
}).then((r) => r.json()),
|
|
158
|
+
placeholderData: keepPreviousData,
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`placeholderData: keepPreviousData` is what kills the "rows blank for one tick on every
|
|
164
|
+
page change" flash.
|
|
165
|
+
|
|
166
|
+
## Step 5 — Reset behavior
|
|
167
|
+
|
|
168
|
+
When the user changes a filter, you usually want to jump back to page 0. The table does this
|
|
169
|
+
automatically when client-side filtering owns the data, but with manual mode the data layer
|
|
170
|
+
controls it. Simplest fix: explicitly reset.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
$effect(() => {
|
|
174
|
+
// re-runs whenever filters.current identity changes
|
|
175
|
+
filters.current
|
|
176
|
+
table.setPageIndex(0)
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Or wrap your filter `onChange` handlers to also call `table.setPageIndex(0)`.
|
|
181
|
+
|
|
182
|
+
## Step 6 — A note on global filtering
|
|
183
|
+
|
|
184
|
+
If you also support `globalFilterFeature`, debounce the input. `column.setFilterValue` and
|
|
185
|
+
`table.setGlobalFilter` fire per keystroke; without debouncing you fire one request per typed
|
|
186
|
+
character. See the `compose-with-tanstack-pacer` skill for the pattern.
|
|
187
|
+
|
|
188
|
+
## Hybrid example — manual pagination only
|
|
189
|
+
|
|
190
|
+
Sometimes you only paginate server-side and let the page-sized window sort/filter on the
|
|
191
|
+
client.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const table = createTable({
|
|
195
|
+
_features: tableFeatures({
|
|
196
|
+
columnFilteringFeature,
|
|
197
|
+
rowPaginationFeature,
|
|
198
|
+
rowSortingFeature,
|
|
199
|
+
}),
|
|
200
|
+
_rowModels: {
|
|
201
|
+
filteredRowModel: createFilteredRowModel(filterFns), // client filters the page
|
|
202
|
+
sortedRowModel: createSortedRowModel(sortFns), // client sorts the page
|
|
203
|
+
},
|
|
204
|
+
columns,
|
|
205
|
+
get data() {
|
|
206
|
+
return query.data?.rows ?? []
|
|
207
|
+
},
|
|
208
|
+
get rowCount() {
|
|
209
|
+
return query.data?.rowCount
|
|
210
|
+
},
|
|
211
|
+
atoms: { pagination: paginationAtom },
|
|
212
|
+
manualPagination: true,
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Only the manual flag for the stage you're moving server-side.
|
|
217
|
+
|
|
218
|
+
## Common failure modes
|
|
219
|
+
|
|
220
|
+
- **Forgot `rowCount`.** `table.getPageCount()` returns `-1`, the pager looks broken.
|
|
221
|
+
- **Dropped the feature, not just the row model.** Lost `column.getCanSort()` and friends.
|
|
222
|
+
Keep the feature when you still need its UI APIs; only drop the row-model factory.
|
|
223
|
+
- **Both `state.pagination` and `atoms.pagination`.** Atoms silently win; the `on*Change`
|
|
224
|
+
callback never fires.
|
|
225
|
+
- **Re-creating atoms inside reactive blocks.** Atoms must be stable across renders. Declare
|
|
226
|
+
them at module / component-init scope, not inside `$derived` or `$effect`.
|
|
227
|
+
- **Forgetting to reset page on filter change.** Stay on page 12 of a now-2-page result set.
|
|
228
|
+
- **Plain `data: query.data?.rows`.** No getter, no reactivity. Use `get data()`.
|
|
229
|
+
- **Reimplementing pagination math.** `table.setPageIndex / nextPage / previousPage /
|
|
230
|
+
firstPage / lastPage / setPageSize / getCanNextPage / getCanPreviousPage / getPageCount`
|
|
231
|
+
already exist and respect manual mode.
|
|
232
|
+
|
|
233
|
+
## Related skills
|
|
234
|
+
|
|
235
|
+
- `tanstack-table/svelte/compose-with-tanstack-query` — the same flow with a Query data layer.
|
|
236
|
+
- `tanstack-table/svelte/compose-with-tanstack-pacer` — debouncing filter inputs.
|
|
237
|
+
- `tanstack-table/svelte/compose-with-tanstack-store` — atom interop and per-slice subscription.
|
|
238
|
+
- `tanstack-table/core/pagination` / `filtering` / `sorting` — feature deep dives.
|