compote-ui 0.79.2 → 0.80.0

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 (58) hide show
  1. package/dist/components/data-table-v9/column-filter-editing.svelte.d.ts +41 -0
  2. package/dist/components/data-table-v9/column-filter-editing.svelte.js +88 -0
  3. package/dist/components/data-table-v9/column-filter-kind.d.ts +10 -0
  4. package/dist/components/data-table-v9/column-filter-kind.js +25 -0
  5. package/dist/components/data-table-v9/create-table.svelte.js +37 -25
  6. package/dist/components/data-table-v9/data-table-head.svelte +230 -201
  7. package/dist/components/data-table-v9/data-table-head.svelte.d.ts +2 -0
  8. package/dist/components/data-table-v9/data-table-utils.d.ts +7 -0
  9. package/dist/components/data-table-v9/data-table-utils.js +4 -0
  10. package/dist/components/data-table-v9/data-table.svelte +154 -151
  11. package/dist/components/data-table-v9/data-table.svelte.d.ts +2 -0
  12. package/dist/components/data-table-v9/date-cell.d.ts +13 -0
  13. package/dist/components/data-table-v9/date-cell.js +38 -0
  14. package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte +45 -0
  15. package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte.d.ts +7 -0
  16. package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte +33 -0
  17. package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte.d.ts +30 -0
  18. package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte +52 -0
  19. package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte.d.ts +28 -0
  20. package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte +51 -0
  21. package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte.d.ts +30 -0
  22. package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte +59 -0
  23. package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte.d.ts +28 -0
  24. package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte +20 -0
  25. package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte.d.ts +8 -0
  26. package/dist/components/data-table-v9/header/column-header-filter.svelte +66 -0
  27. package/dist/components/data-table-v9/header/column-header-filter.svelte.d.ts +30 -0
  28. package/dist/components/data-table-v9/table-view-state.svelte.d.ts +1 -0
  29. package/dist/components/data-table-v9/table-view-state.svelte.js +3 -0
  30. package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte +203 -459
  31. package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte.d.ts +1 -1
  32. package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +82 -79
  33. package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte.d.ts +2 -0
  34. package/dist/components/dialog/alert-dialog.svelte +73 -71
  35. package/dist/components/dialog/dialog-root.svelte +45 -43
  36. package/dist/components/drawer/drawer-content.svelte +213 -211
  37. package/dist/components/popover/popover-content.svelte +97 -71
  38. package/dist/components/popover/popover-content.svelte.d.ts +2 -1
  39. package/dist/components/popover/popover-description.svelte +19 -15
  40. package/dist/components/popover/popover-labels.svelte.d.ts +18 -0
  41. package/dist/components/popover/popover-labels.svelte.js +35 -0
  42. package/dist/components/popover/popover-root.svelte +27 -24
  43. package/dist/components/popover/popover-title.svelte +19 -15
  44. package/dist/icons/PhFunnelFill.svelte +19 -0
  45. package/dist/icons/PhFunnelFill.svelte.d.ts +5 -0
  46. package/dist/icons/index.d.ts +1 -0
  47. package/dist/icons/index.js +1 -0
  48. package/dist/utils/date-filter.d.ts +31 -0
  49. package/dist/utils/date-filter.js +58 -0
  50. package/dist/utils/date.d.ts +19 -1
  51. package/dist/utils/date.js +48 -1
  52. package/dist/utils/release-focus-on-exit.d.ts +22 -0
  53. package/dist/utils/release-focus-on-exit.js +26 -0
  54. package/package.json +1 -1
  55. package/skills/component-usage/references/overlays-and-floating-ui.md +278 -273
  56. package/skills/data-table/SKILL.md +314 -283
  57. package/skills/data-table/references/column-options.md +175 -150
  58. package/skills/data-table/references/table-state-and-reactivity.md +65 -60
@@ -1,283 +1,314 @@
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.79.2'
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/virtual/data-table-virtualized.svelte
24
- - src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte
25
- ---
26
-
27
- This skill builds on component-usage and theming. Read those first for Compote namespace imports and theme token behavior.
28
-
29
- # Compote UI — Data Table
30
-
31
- Use `compote-ui/data-table` for regular tables and `compote-ui/data-table/virtual` for virtualized rows.
32
-
33
- ## Integration Setup
34
-
35
- ```bash
36
- bun add @tanstack/svelte-table
37
- ```
38
-
39
- ```svelte
40
- <script lang="ts">
41
- import * as DataTable from 'compote-ui/data-table';
42
-
43
- type Invoice = {
44
- id: string;
45
- customer: string;
46
- total: number;
47
- paid: boolean;
48
- };
49
-
50
- let invoices = $state<Invoice[]>([
51
- { id: 'inv_1', customer: 'Ada', total: 1200, paid: true },
52
- { id: 'inv_2', customer: 'Grace', total: 800, paid: false }
53
- ]);
54
-
55
- const col = DataTable.createDataTableColumnHelper<Invoice>();
56
- const columns = col.columns([
57
- col.accessor('id', { header: 'ID', enableHiding: false }),
58
- col.accessor('customer', { header: 'Customer', grow: true }),
59
- col.accessor('total', { header: 'Total', type: 'currency', align: 'right' }),
60
- col.accessor('paid', { header: 'Paid', type: 'boolean', align: 'center' })
61
- ]);
62
-
63
- const table = DataTable.createTable({
64
- get data() {
65
- return invoices;
66
- },
67
- columns,
68
- getRowId: (row) => row.id,
69
- enableRowSelection: true
70
- });
71
- </script>
72
-
73
- <DataTable.Toolbar>
74
- <DataTable.Title>Invoices</DataTable.Title>
75
- {#snippet center()}
76
- <DataTable.Search {table} class="w-56" />
77
- {/snippet}
78
- {#snippet right()}
79
- <DataTable.ColumnFilter {table} />
80
- <DataTable.ColumnVisibility {table} />
81
- {/snippet}
82
- </DataTable.Toolbar>
83
-
84
- <div class="h-96 min-h-0">
85
- <DataTable.Root {table} caption="Invoices" />
86
- </div>
87
- ```
88
-
89
- ## Core Integration Patterns
90
-
91
- ### Use accessorFn with an explicit id
92
-
93
- ```ts
94
- const columns = col.columns([
95
- col.accessorFn((row) => row.quantity * row.price, {
96
- id: 'total',
97
- header: 'Total',
98
- type: 'currency',
99
- align: 'right'
100
- })
101
- ]);
102
- ```
103
-
104
- ### Make runtime column changes reactive
105
-
106
- ```ts
107
- const idCol = col.accessor('id', { header: 'ID', enableHiding: false });
108
- const totalCol = col.accessor('total', { header: 'Total', type: 'currency' });
109
-
110
- let columns = $state.raw([idCol]);
111
-
112
- const table = DataTable.createTable({
113
- get data() {
114
- return invoices;
115
- },
116
- get columns() {
117
- return columns;
118
- },
119
- getRowId: (row) => row.id
120
- });
121
-
122
- function toggleTotal() {
123
- columns = columns.includes(totalCol)
124
- ? columns.filter((column) => column !== totalCol)
125
- : [...columns, totalCol];
126
- }
127
- ```
128
-
129
- ### Use virtual import for large row sets
130
-
131
- ```bash
132
- bun add @tanstack/svelte-virtual
133
- ```
134
-
135
- ```svelte
136
- <script lang="ts">
137
- import * as VirtualDataTable from 'compote-ui/data-table/virtual';
138
-
139
- const table = VirtualDataTable.createTable({
140
- get data() {
141
- return rows;
142
- },
143
- columns,
144
- getRowId: (row) => row.id,
145
- enableRowSelection: true
146
- });
147
- </script>
148
-
149
- <div class="h-96 min-h-0">
150
- <VirtualDataTable.Root {table} caption="Large dataset" />
151
- </div>
152
- ```
153
-
154
- ### Label toolbar triggers
155
-
156
- `ColumnFilter` and `ColumnVisibility` render icon-only outline buttons by default (funnel and
157
- columns icons, with `aria-label` and a hover tooltip). Pass `triggerLabel` to show text instead. `ColumnFilter`
158
- shows the active filter count as a badge in the corner either way.
159
-
160
- ```svelte
161
- <DataTable.ColumnFilter {table} triggerLabel="Filters" />
162
- <DataTable.ColumnVisibility {table} triggerLabel="Columns" />
163
- ```
164
-
165
- ### Add a refresh button
166
-
167
- `DataTable.Refresh` is data-source agnostic: it calls `onRefresh` and, if that returns a promise,
168
- spins its icon and sets `aria-busy` until the promise settles. Clicks while busy are ignored. Pass
169
- `loading` to force the busy state when a refresh starts elsewhere (polling, another control).
170
-
171
- ```svelte
172
- <script lang="ts">
173
- import { getInvoices } from './invoices.remote';
174
-
175
- const invoices = getInvoices();
176
- </script>
177
-
178
- <DataTable.Toolbar>
179
- {#snippet right()}
180
- <DataTable.Refresh onRefresh={() => invoices.refresh()} />
181
- <DataTable.ColumnFilter {table} />
182
- <DataTable.ColumnVisibility {table} />
183
- {/snippet}
184
- </DataTable.Toolbar>
185
- ```
186
-
187
- Props: `onRefresh: () => void | Promise<unknown>`, `loading?: boolean`, `triggerLabel?: string`
188
- (icon-only with `aria-label="Refresh"` and a tooltip when omitted). Errors thrown by `onRefresh` are not caught.
189
-
190
- ## Common Mistakes
191
-
192
- ### CRITICAL Missing id for accessorFn column
193
-
194
- Wrong:
195
-
196
- ```ts
197
- col.accessorFn((row) => row.a + row.b, { header: 'Total' });
198
- ```
199
-
200
- Correct:
201
-
202
- ```ts
203
- col.accessorFn((row) => row.a + row.b, { id: 'total', header: 'Total' });
204
- ```
205
-
206
- Accessor function columns cannot derive a stable column id from a key.
207
-
208
- Source: `src/lib/components/data-table-v9/create-table.svelte.ts`
209
-
210
- ### HIGH Non-reactive reassigned columns
211
-
212
- Wrong:
213
-
214
- ```ts
215
- let columns = $state([idCol, nameCol]);
216
- const table = DataTable.createTable({ data: rows, columns });
217
- ```
218
-
219
- Correct:
220
-
221
- ```ts
222
- let columns = $state.raw([idCol, nameCol]);
223
- const table = DataTable.createTable({
224
- get data() {
225
- return rows;
226
- },
227
- get columns() {
228
- return columns;
229
- }
230
- });
231
- ```
232
-
233
- Reassigned columns must be passed through a getter and kept raw so identity checks keep working.
234
-
235
- Source: `src/routes/components/data-table-v9/+page.svelte`, `src/lib/components/data-table-v9/create-table.svelte.ts`
236
-
237
- ### HIGH Rendering table without height constraint
238
-
239
- Wrong:
240
-
241
- ```svelte
242
- <DataTable.Root {table} />
243
- ```
244
-
245
- Correct:
246
-
247
- ```svelte
248
- <div class="h-96 min-h-0">
249
- <DataTable.Root {table} caption="Invoices" />
250
- </div>
251
- ```
252
-
253
- `DataTable.Root` fills its container and uses internal scrolling.
254
-
255
- Source: `src/lib/components/data-table-v9/data-table.svelte`
256
-
257
- ### HIGH Importing virtual table without optional peer
258
-
259
- Wrong:
260
-
261
- ```ts
262
- import * as VirtualDataTable from 'compote-ui/data-table/virtual';
263
- ```
264
-
265
- Correct:
266
-
267
- ```bash
268
- bun add @tanstack/svelte-virtual
269
- ```
270
-
271
- ```ts
272
- import * as VirtualDataTable from 'compote-ui/data-table/virtual';
273
- ```
274
-
275
- The virtual subpath imports `@tanstack/svelte-virtual`.
276
-
277
- Source: `package.json`, `src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte`
278
-
279
- ## References
280
-
281
- - [Column options](references/column-options.md)
282
- - [Table state and reactivity](references/table-state-and-reactivity.md)
283
- - [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.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)