compote-ui 0.80.0 → 0.81.1

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 (33) hide show
  1. package/dist/components/data-table-v9/column-filter-kind.d.ts +10 -1
  2. package/dist/components/data-table-v9/column-filter-kind.js +7 -0
  3. package/dist/components/data-table-v9/data-table-head.svelte +230 -230
  4. package/dist/components/data-table-v9/data-table.svelte +154 -154
  5. package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte +45 -45
  6. package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte +39 -33
  7. package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte.d.ts +7 -2
  8. package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte +52 -52
  9. package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte +51 -51
  10. package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte +59 -59
  11. package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte +20 -20
  12. package/dist/components/data-table-v9/header/column-header-filter.svelte +66 -66
  13. package/dist/components/data-table-v9/index.d.ts +9 -1
  14. package/dist/components/data-table-v9/index.js +7 -0
  15. package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte +203 -203
  16. package/dist/components/data-table-v9/toolbar/data-table-toolbar.svelte +4 -1
  17. package/dist/components/data-table-v9/types.d.ts +3 -1
  18. package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +82 -82
  19. package/dist/components/data-table-v9/virtual/index.d.ts +2 -2
  20. package/dist/components/data-table-v9/virtual/index.js +1 -1
  21. package/dist/components/dialog/alert-dialog.svelte +73 -73
  22. package/dist/components/dialog/dialog-root.svelte +45 -45
  23. package/dist/components/drawer/drawer-content.svelte +213 -213
  24. package/dist/components/popover/popover-content.svelte +97 -97
  25. package/dist/components/popover/popover-description.svelte +19 -19
  26. package/dist/components/popover/popover-root.svelte +27 -27
  27. package/dist/components/popover/popover-title.svelte +19 -19
  28. package/dist/icons/PhFunnelFill.svelte +19 -19
  29. package/package.json +1 -1
  30. package/skills/component-usage/references/overlays-and-floating-ui.md +278 -278
  31. package/skills/data-table/SKILL.md +362 -314
  32. package/skills/data-table/references/column-options.md +175 -175
  33. package/skills/data-table/references/table-state-and-reactivity.md +65 -65
@@ -1,314 +1,362 @@
1
- ---
2
- name: data-table
3
- description: >
4
- Load when using compote-ui/data-table or compote-ui/data-table/virtual.
5
- Covers TanStack Table v9 wrapper usage, createTable,
6
- createDataTableColumnHelper, table columns, row selection, filters,
7
- formatting, toolbars, virtualized imports, and optional peer dependencies.
8
- metadata:
9
- type: composition
10
- library: compote-ui
11
- library_version: '0.80.0'
12
- requires:
13
- - component-usage
14
- - theming
15
- sources:
16
- - package.json
17
- - src/lib/components/data-table-v9/index.ts
18
- - src/lib/components/data-table-v9/types.ts
19
- - src/lib/components/data-table-v9/features.ts
20
- - src/lib/components/data-table-v9/column-helper.ts
21
- - src/lib/components/data-table-v9/create-table.svelte.ts
22
- - src/lib/components/data-table-v9/data-table.svelte
23
- - src/lib/components/data-table-v9/data-table-head.svelte
24
- - src/lib/components/data-table-v9/header/column-header-filter.svelte
25
- - src/lib/components/data-table-v9/column-filter-kind.ts
26
- - src/lib/components/data-table-v9/filter-editors/column-filter-editor.svelte
27
- - src/lib/utils/date-filter.ts
28
- - src/lib/components/data-table-v9/virtual/data-table-virtualized.svelte
29
- - src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte
30
- ---
31
-
32
- This skill builds on component-usage and theming. Read those first for Compote namespace imports and theme token behavior.
33
-
34
- # Compote UI — Data Table
35
-
36
- Use `compote-ui/data-table` for regular tables and `compote-ui/data-table/virtual` for virtualized rows.
37
-
38
- ## Integration Setup
39
-
40
- ```bash
41
- bun add @tanstack/svelte-table
42
- ```
43
-
44
- ```svelte
45
- <script lang="ts">
46
- import * as DataTable from 'compote-ui/data-table';
47
-
48
- type Invoice = {
49
- id: string;
50
- customer: string;
51
- total: number;
52
- paid: boolean;
53
- };
54
-
55
- let invoices = $state<Invoice[]>([
56
- { id: 'inv_1', customer: 'Ada', total: 1200, paid: true },
57
- { id: 'inv_2', customer: 'Grace', total: 800, paid: false }
58
- ]);
59
-
60
- const col = DataTable.createDataTableColumnHelper<Invoice>();
61
- const columns = col.columns([
62
- col.accessor('id', { header: 'ID', enableHiding: false }),
63
- col.accessor('customer', { header: 'Customer', grow: true }),
64
- col.accessor('total', { header: 'Total', type: 'currency', align: 'right' }),
65
- col.accessor('paid', { header: 'Paid', type: 'boolean', align: 'center' })
66
- ]);
67
-
68
- const table = DataTable.createTable({
69
- get data() {
70
- return invoices;
71
- },
72
- columns,
73
- getRowId: (row) => row.id,
74
- enableRowSelection: true
75
- });
76
- </script>
77
-
78
- <DataTable.Toolbar>
79
- <DataTable.Title>Invoices</DataTable.Title>
80
- {#snippet center()}
81
- <DataTable.Search {table} class="w-56" />
82
- {/snippet}
83
- {#snippet right()}
84
- <DataTable.ColumnFilter {table} />
85
- <DataTable.ColumnVisibility {table} />
86
- {/snippet}
87
- </DataTable.Toolbar>
88
-
89
- <div class="h-96 min-h-0">
90
- <DataTable.Root {table} caption="Invoices" />
91
- </div>
92
- ```
93
-
94
- ## Core Integration Patterns
95
-
96
- ### Use accessorFn with an explicit id
97
-
98
- ```ts
99
- const columns = col.columns([
100
- col.accessorFn((row) => row.quantity * row.price, {
101
- id: 'total',
102
- header: 'Total',
103
- type: 'currency',
104
- align: 'right'
105
- })
106
- ]);
107
- ```
108
-
109
- ### Make runtime column changes reactive
110
-
111
- ```ts
112
- const idCol = col.accessor('id', { header: 'ID', enableHiding: false });
113
- const totalCol = col.accessor('total', { header: 'Total', type: 'currency' });
114
-
115
- let columns = $state.raw([idCol]);
116
-
117
- const table = DataTable.createTable({
118
- get data() {
119
- return invoices;
120
- },
121
- get columns() {
122
- return columns;
123
- },
124
- getRowId: (row) => row.id
125
- });
126
-
127
- function toggleTotal() {
128
- columns = columns.includes(totalCol)
129
- ? columns.filter((column) => column !== totalCol)
130
- : [...columns, totalCol];
131
- }
132
- ```
133
-
134
- ### Use virtual import for large row sets
135
-
136
- ```bash
137
- bun add @tanstack/svelte-virtual
138
- ```
139
-
140
- ```svelte
141
- <script lang="ts">
142
- import * as VirtualDataTable from 'compote-ui/data-table/virtual';
143
-
144
- const table = VirtualDataTable.createTable({
145
- get data() {
146
- return rows;
147
- },
148
- columns,
149
- getRowId: (row) => row.id,
150
- enableRowSelection: true
151
- });
152
- </script>
153
-
154
- <div class="h-96 min-h-0">
155
- <VirtualDataTable.Root {table} caption="Large dataset" />
156
- </div>
157
- ```
158
-
159
- ### Label toolbar triggers
160
-
161
- `ColumnFilter` and `ColumnVisibility` render icon-only outline buttons by default (funnel and
162
- columns icons, with `aria-label` and a hover tooltip). Pass `triggerLabel` to show text instead. `ColumnFilter`
163
- shows the active filter count as a badge in the corner either way.
164
-
165
- ```svelte
166
- <DataTable.ColumnFilter {table} triggerLabel="Filters" />
167
- <DataTable.ColumnVisibility {table} triggerLabel="Columns" />
168
- ```
169
-
170
- ### Filter from column headers
171
-
172
- Pass `headerFilters` to `DataTable.Root` / `VirtualDataTable.Root` to put a funnel in the header
173
- of every filterable leaf column (not group headers, not `enableColumnFilter: false`, never
174
- `type: 'action'`). The funnel shows on header hover/focus (always on touch screens); once the
175
- column is filtered it stays visible, filled and in the primary color. Clicking it opens a popup
176
- titled with the column name, with a "Clear" button and the editor for the column's type:
177
-
178
- | Column `type` | Popup editor | Filter value |
179
- | ----------------------------------- | -------------------------------------- | -------------------------------- |
180
- | `number`, `currency`, `percent` | From / To number inputs | `[min?, max?]` (`inNumberRange`) |
181
- | `boolean` | All / Yes / No | `true` / `false` |
182
- | `select` | Search + checkbox list with row counts | array of options |
183
- | `date`, `date-time` | From / To date pickers | `[from?, to?]` as `YYYY-MM-DD` |
184
- | anything else (`text`, `time`, ...) | Text search | string |
185
-
186
- Edits apply immediately (text and number inputs after a 300ms debounce — also when the popup
187
- closes first). The header popups and `DataTable.ColumnFilter` edit the same table state, so a
188
- filter set in one shows in the other. `headerFilters` is opt-in and defaults to `false`.
189
-
190
- ```svelte
191
- <div class="h-96 min-h-0">
192
- <DataTable.Root {table} caption="Invoices" headerFilters />
193
- </div>
194
- ```
195
-
196
- ### Add a refresh button
197
-
198
- `DataTable.Refresh` is data-source agnostic: it calls `onRefresh` and, if that returns a promise,
199
- spins its icon and sets `aria-busy` until the promise settles. Clicks while busy are ignored. Pass
200
- `loading` to force the busy state when a refresh starts elsewhere (polling, another control).
201
-
202
- ```svelte
203
- <script lang="ts">
204
- import { getInvoices } from './invoices.remote';
205
-
206
- const invoices = getInvoices();
207
- </script>
208
-
209
- <DataTable.Toolbar>
210
- {#snippet right()}
211
- <DataTable.Refresh onRefresh={() => invoices.refresh()} />
212
- <DataTable.ColumnFilter {table} />
213
- <DataTable.ColumnVisibility {table} />
214
- {/snippet}
215
- </DataTable.Toolbar>
216
- ```
217
-
218
- Props: `onRefresh: () => void | Promise<unknown>`, `loading?: boolean`, `triggerLabel?: string`
219
- (icon-only with `aria-label="Refresh"` and a tooltip when omitted). Errors thrown by `onRefresh` are not caught.
220
-
221
- ## Common Mistakes
222
-
223
- ### CRITICAL Missing id for accessorFn column
224
-
225
- Wrong:
226
-
227
- ```ts
228
- col.accessorFn((row) => row.a + row.b, { header: 'Total' });
229
- ```
230
-
231
- Correct:
232
-
233
- ```ts
234
- col.accessorFn((row) => row.a + row.b, { id: 'total', header: 'Total' });
235
- ```
236
-
237
- Accessor function columns cannot derive a stable column id from a key.
238
-
239
- Source: `src/lib/components/data-table-v9/create-table.svelte.ts`
240
-
241
- ### HIGH Non-reactive reassigned columns
242
-
243
- Wrong:
244
-
245
- ```ts
246
- let columns = $state([idCol, nameCol]);
247
- const table = DataTable.createTable({ data: rows, columns });
248
- ```
249
-
250
- Correct:
251
-
252
- ```ts
253
- let columns = $state.raw([idCol, nameCol]);
254
- const table = DataTable.createTable({
255
- get data() {
256
- return rows;
257
- },
258
- get columns() {
259
- return columns;
260
- }
261
- });
262
- ```
263
-
264
- Reassigned columns must be passed through a getter and kept raw so identity checks keep working.
265
-
266
- Source: `src/routes/components/data-table-v9/+page.svelte`, `src/lib/components/data-table-v9/create-table.svelte.ts`
267
-
268
- ### HIGH Rendering table without height constraint
269
-
270
- Wrong:
271
-
272
- ```svelte
273
- <DataTable.Root {table} />
274
- ```
275
-
276
- Correct:
277
-
278
- ```svelte
279
- <div class="h-96 min-h-0">
280
- <DataTable.Root {table} caption="Invoices" />
281
- </div>
282
- ```
283
-
284
- `DataTable.Root` fills its container and uses internal scrolling.
285
-
286
- Source: `src/lib/components/data-table-v9/data-table.svelte`
287
-
288
- ### HIGH Importing virtual table without optional peer
289
-
290
- Wrong:
291
-
292
- ```ts
293
- import * as VirtualDataTable from 'compote-ui/data-table/virtual';
294
- ```
295
-
296
- Correct:
297
-
298
- ```bash
299
- bun add @tanstack/svelte-virtual
300
- ```
301
-
302
- ```ts
303
- import * as VirtualDataTable from 'compote-ui/data-table/virtual';
304
- ```
305
-
306
- The virtual subpath imports `@tanstack/svelte-virtual`.
307
-
308
- Source: `package.json`, `src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte`
309
-
310
- ## References
311
-
312
- - [Column options](references/column-options.md)
313
- - [Table state and reactivity](references/table-state-and-reactivity.md)
314
- - [Virtual table](references/virtual-table.md)
1
+ ---
2
+ name: data-table
3
+ description: >
4
+ Load when using compote-ui/data-table or compote-ui/data-table/virtual.
5
+ Covers TanStack Table v9 wrapper usage, createTable,
6
+ createDataTableColumnHelper, table columns, row selection, filters,
7
+ formatting, toolbars, virtualized imports, and optional peer dependencies.
8
+ metadata:
9
+ type: composition
10
+ library: compote-ui
11
+ library_version: '0.81.0'
12
+ requires:
13
+ - component-usage
14
+ - theming
15
+ sources:
16
+ - package.json
17
+ - src/lib/components/data-table-v9/index.ts
18
+ - src/lib/components/data-table-v9/types.ts
19
+ - src/lib/components/data-table-v9/features.ts
20
+ - src/lib/components/data-table-v9/column-helper.ts
21
+ - src/lib/components/data-table-v9/create-table.svelte.ts
22
+ - src/lib/components/data-table-v9/data-table.svelte
23
+ - src/lib/components/data-table-v9/data-table-head.svelte
24
+ - src/lib/components/data-table-v9/header/column-header-filter.svelte
25
+ - src/lib/components/data-table-v9/column-filter-kind.ts
26
+ - src/lib/components/data-table-v9/filter-editors/column-filter-editor.svelte
27
+ - src/lib/components/data-table-v9/column-filter-editing.svelte.ts
28
+ - src/lib/utils/select-filter.ts
29
+ - src/lib/utils/date-filter.ts
30
+ - src/lib/components/data-table-v9/virtual/data-table-virtualized.svelte
31
+ - src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte
32
+ ---
33
+
34
+ This skill builds on component-usage and theming. Read those first for Compote namespace imports and theme token behavior.
35
+
36
+ # Compote UI — Data Table
37
+
38
+ Use `compote-ui/data-table` for regular tables and `compote-ui/data-table/virtual` for virtualized rows.
39
+
40
+ ## Integration Setup
41
+
42
+ ```bash
43
+ bun add @tanstack/svelte-table
44
+ ```
45
+
46
+ ```svelte
47
+ <script lang="ts">
48
+ import * as DataTable from 'compote-ui/data-table';
49
+
50
+ type Invoice = {
51
+ id: string;
52
+ customer: string;
53
+ total: number;
54
+ paid: boolean;
55
+ };
56
+
57
+ let invoices = $state<Invoice[]>([
58
+ { id: 'inv_1', customer: 'Ada', total: 1200, paid: true },
59
+ { id: 'inv_2', customer: 'Grace', total: 800, paid: false }
60
+ ]);
61
+
62
+ const col = DataTable.createDataTableColumnHelper<Invoice>();
63
+ const columns = col.columns([
64
+ col.accessor('id', { header: 'ID', enableHiding: false }),
65
+ col.accessor('customer', { header: 'Customer', grow: true }),
66
+ col.accessor('total', { header: 'Total', type: 'currency', align: 'right' }),
67
+ col.accessor('paid', { header: 'Paid', type: 'boolean', align: 'center' })
68
+ ]);
69
+
70
+ const table = DataTable.createTable({
71
+ get data() {
72
+ return invoices;
73
+ },
74
+ columns,
75
+ getRowId: (row) => row.id,
76
+ enableRowSelection: true
77
+ });
78
+ </script>
79
+
80
+ <DataTable.Toolbar>
81
+ <DataTable.Title>Invoices</DataTable.Title>
82
+ {#snippet center()}
83
+ <DataTable.Search {table} class="w-56" />
84
+ {/snippet}
85
+ {#snippet right()}
86
+ <DataTable.ColumnFilter {table} />
87
+ <DataTable.ColumnVisibility {table} />
88
+ {/snippet}
89
+ </DataTable.Toolbar>
90
+
91
+ <div class="h-96 min-h-0">
92
+ <DataTable.Root {table} caption="Invoices" />
93
+ </div>
94
+ ```
95
+
96
+ ## Core Integration Patterns
97
+
98
+ ### Use accessorFn with an explicit id
99
+
100
+ ```ts
101
+ const columns = col.columns([
102
+ col.accessorFn((row) => row.quantity * row.price, {
103
+ id: 'total',
104
+ header: 'Total',
105
+ type: 'currency',
106
+ align: 'right'
107
+ })
108
+ ]);
109
+ ```
110
+
111
+ ### Make runtime column changes reactive
112
+
113
+ ```ts
114
+ const idCol = col.accessor('id', { header: 'ID', enableHiding: false });
115
+ const totalCol = col.accessor('total', { header: 'Total', type: 'currency' });
116
+
117
+ let columns = $state.raw([idCol]);
118
+
119
+ const table = DataTable.createTable({
120
+ get data() {
121
+ return invoices;
122
+ },
123
+ get columns() {
124
+ return columns;
125
+ },
126
+ getRowId: (row) => row.id
127
+ });
128
+
129
+ function toggleTotal() {
130
+ columns = columns.includes(totalCol)
131
+ ? columns.filter((column) => column !== totalCol)
132
+ : [...columns, totalCol];
133
+ }
134
+ ```
135
+
136
+ ### Use virtual import for large row sets
137
+
138
+ ```bash
139
+ bun add @tanstack/svelte-virtual
140
+ ```
141
+
142
+ ```svelte
143
+ <script lang="ts">
144
+ import * as VirtualDataTable from 'compote-ui/data-table/virtual';
145
+
146
+ const table = VirtualDataTable.createTable({
147
+ get data() {
148
+ return rows;
149
+ },
150
+ columns,
151
+ getRowId: (row) => row.id,
152
+ enableRowSelection: true
153
+ });
154
+ </script>
155
+
156
+ <div class="h-96 min-h-0">
157
+ <VirtualDataTable.Root {table} caption="Large dataset" />
158
+ </div>
159
+ ```
160
+
161
+ ### Label toolbar triggers
162
+
163
+ `ColumnFilter` and `ColumnVisibility` render icon-only outline buttons by default (funnel and
164
+ columns icons, with `aria-label` and a hover tooltip). Pass `triggerLabel` to show text instead. `ColumnFilter`
165
+ shows the active filter count as a badge in the corner either way.
166
+
167
+ ```svelte
168
+ <DataTable.ColumnFilter {table} triggerLabel="Filters" />
169
+ <DataTable.ColumnVisibility {table} triggerLabel="Columns" />
170
+ ```
171
+
172
+ ### Filter from column headers
173
+
174
+ Pass `headerFilters` to `DataTable.Root` / `VirtualDataTable.Root` to put a funnel in the header
175
+ of every filterable leaf column (not group headers, not `enableColumnFilter: false`, never
176
+ `type: 'action'`). The funnel shows on header hover/focus (always on touch screens); once the
177
+ column is filtered it stays visible, filled and in the primary color. Clicking it opens a popup
178
+ titled with the column name, with a "Clear" button and the editor for the column's type:
179
+
180
+ | Column `type` | Popup editor | Filter value |
181
+ | ----------------------------------- | -------------------------------------- | -------------------------------- |
182
+ | `number`, `currency`, `percent` | From / To number inputs | `[min?, max?]` (`inNumberRange`) |
183
+ | `boolean` | All / Yes / No | `true` / `false` |
184
+ | `select` | Search + checkbox list with row counts | array of options |
185
+ | `date`, `date-time` | From / To date pickers | `[from?, to?]` as `YYYY-MM-DD` |
186
+ | anything else (`text`, `time`, ...) | Text search | string |
187
+
188
+ Edits apply immediately (text and number inputs after a 300ms debounce — also when the popup
189
+ closes first). The header popups and `DataTable.ColumnFilter` edit the same table state, so a
190
+ filter set in one shows in the other. `headerFilters` is opt-in and defaults to `false`.
191
+
192
+ ```svelte
193
+ <div class="h-96 min-h-0">
194
+ <DataTable.Root {table} caption="Invoices" headerFilters />
195
+ </div>
196
+ ```
197
+
198
+ ### Build your own filter UI
199
+
200
+ For filter controls outside the toolbar and header popups (a facet panel beside the table, a
201
+ filter sidebar), use the same building blocks the Funnel uses. They read and write the table's
202
+ column filters, so the Funnel, header funnels and your UI always show the same state.
203
+
204
+ - `ColumnFilterEditor` — the Funnel's editor for one column, picked by its type (see the table in
205
+ "Filter from column headers"). Pass `editing` only if the editor can be torn down while it holds
206
+ a pending text/range edit (see below); without it the editor uses one bound to the column's table.
207
+ - `ColumnFilterEditing` — the debounced text/range edits pending on a table, shared by every host.
208
+ `isActive(columnId)` lets a host refuse writes from an editor it has already dropped (Ark's
209
+ NumberInput commits on the blur its unmount triggers).
210
+ - `getColumnFilterKind(column)` / `filterKindFor(type)` — the column's `ColumnFilterKind`
211
+ (`range`, `boolean`, `select`, `date` or `text`), or `null` for columns that never filter.
212
+ - `selectFilterOptions(column.getFacetedUniqueValues())` — a select column's options with row
213
+ counts, sorted by label, "(empty)" last with value `EMPTY_SELECT_VALUE` (`null`). Counts are
214
+ cross-filtered: every other active filter applies, the column's own does not.
215
+ - `getColumnLabel(column)` and the `DataTableColumnInstance<T>` type for the columns you list.
216
+
217
+ ```svelte
218
+ <script lang="ts">
219
+ import * as DataTable from 'compote-ui/data-table';
220
+
221
+ let { table }: { table: DataTable.DataTableInstance<Product> } = $props();
222
+
223
+ const facets = $derived(
224
+ table
225
+ .getAllLeafColumns()
226
+ .filter((column) => column.getCanFilter() && DataTable.getColumnFilterKind(column))
227
+ );
228
+ </script>
229
+
230
+ {#each facets as column (column.id)}
231
+ <h3>{DataTable.getColumnLabel(column)}</h3>
232
+ <DataTable.ColumnFilterEditor {column} />
233
+ {/each}
234
+ ```
235
+
236
+ A custom select list writes the same value the Funnel does — an array of options, `undefined`
237
+ when none is ticked:
238
+
239
+ ```ts
240
+ const selected = (column.getFilterValue() as DataTable.SelectFilterValue[] | undefined) ?? [];
241
+ column.setFilterValue(next.length ? next : undefined);
242
+ ```
243
+
244
+ ### Add a refresh button
245
+
246
+ `DataTable.Refresh` is data-source agnostic: it calls `onRefresh` and, if that returns a promise,
247
+ spins its icon and sets `aria-busy` until the promise settles. Clicks while busy are ignored. Pass
248
+ `loading` to force the busy state when a refresh starts elsewhere (polling, another control).
249
+
250
+ ```svelte
251
+ <script lang="ts">
252
+ import { getInvoices } from './invoices.remote';
253
+
254
+ const invoices = getInvoices();
255
+ </script>
256
+
257
+ <DataTable.Toolbar>
258
+ {#snippet right()}
259
+ <DataTable.Refresh onRefresh={() => invoices.refresh()} />
260
+ <DataTable.ColumnFilter {table} />
261
+ <DataTable.ColumnVisibility {table} />
262
+ {/snippet}
263
+ </DataTable.Toolbar>
264
+ ```
265
+
266
+ Props: `onRefresh: () => void | Promise<unknown>`, `loading?: boolean`, `triggerLabel?: string`
267
+ (icon-only with `aria-label="Refresh"` and a tooltip when omitted). Errors thrown by `onRefresh` are not caught.
268
+
269
+ ## Common Mistakes
270
+
271
+ ### CRITICAL Missing id for accessorFn column
272
+
273
+ Wrong:
274
+
275
+ ```ts
276
+ col.accessorFn((row) => row.a + row.b, { header: 'Total' });
277
+ ```
278
+
279
+ Correct:
280
+
281
+ ```ts
282
+ col.accessorFn((row) => row.a + row.b, { id: 'total', header: 'Total' });
283
+ ```
284
+
285
+ Accessor function columns cannot derive a stable column id from a key.
286
+
287
+ Source: `src/lib/components/data-table-v9/create-table.svelte.ts`
288
+
289
+ ### HIGH Non-reactive reassigned columns
290
+
291
+ Wrong:
292
+
293
+ ```ts
294
+ let columns = $state([idCol, nameCol]);
295
+ const table = DataTable.createTable({ data: rows, columns });
296
+ ```
297
+
298
+ Correct:
299
+
300
+ ```ts
301
+ let columns = $state.raw([idCol, nameCol]);
302
+ const table = DataTable.createTable({
303
+ get data() {
304
+ return rows;
305
+ },
306
+ get columns() {
307
+ return columns;
308
+ }
309
+ });
310
+ ```
311
+
312
+ Reassigned columns must be passed through a getter and kept raw so identity checks keep working.
313
+
314
+ Source: `src/routes/components/data-table-v9/+page.svelte`, `src/lib/components/data-table-v9/create-table.svelte.ts`
315
+
316
+ ### HIGH Rendering table without height constraint
317
+
318
+ Wrong:
319
+
320
+ ```svelte
321
+ <DataTable.Root {table} />
322
+ ```
323
+
324
+ Correct:
325
+
326
+ ```svelte
327
+ <div class="h-96 min-h-0">
328
+ <DataTable.Root {table} caption="Invoices" />
329
+ </div>
330
+ ```
331
+
332
+ `DataTable.Root` fills its container and uses internal scrolling.
333
+
334
+ Source: `src/lib/components/data-table-v9/data-table.svelte`
335
+
336
+ ### HIGH Importing virtual table without optional peer
337
+
338
+ Wrong:
339
+
340
+ ```ts
341
+ import * as VirtualDataTable from 'compote-ui/data-table/virtual';
342
+ ```
343
+
344
+ Correct:
345
+
346
+ ```bash
347
+ bun add @tanstack/svelte-virtual
348
+ ```
349
+
350
+ ```ts
351
+ import * as VirtualDataTable from 'compote-ui/data-table/virtual';
352
+ ```
353
+
354
+ The virtual subpath imports `@tanstack/svelte-virtual`.
355
+
356
+ Source: `package.json`, `src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte`
357
+
358
+ ## References
359
+
360
+ - [Column options](references/column-options.md)
361
+ - [Table state and reactivity](references/table-state-and-reactivity.md)
362
+ - [Virtual table](references/virtual-table.md)