compote-ui 0.79.2 → 0.81.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.
- package/dist/components/data-table-v9/column-filter-editing.svelte.d.ts +41 -0
- package/dist/components/data-table-v9/column-filter-editing.svelte.js +88 -0
- package/dist/components/data-table-v9/column-filter-kind.d.ts +19 -0
- package/dist/components/data-table-v9/column-filter-kind.js +32 -0
- package/dist/components/data-table-v9/create-table.svelte.js +37 -25
- package/dist/components/data-table-v9/data-table-head.svelte +32 -3
- package/dist/components/data-table-v9/data-table-head.svelte.d.ts +2 -0
- package/dist/components/data-table-v9/data-table-utils.d.ts +7 -0
- package/dist/components/data-table-v9/data-table-utils.js +4 -0
- package/dist/components/data-table-v9/data-table.svelte +4 -1
- package/dist/components/data-table-v9/data-table.svelte.d.ts +2 -0
- package/dist/components/data-table-v9/date-cell.d.ts +13 -0
- package/dist/components/data-table-v9/date-cell.js +38 -0
- package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte +45 -0
- package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte.d.ts +7 -0
- package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte +39 -0
- package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte.d.ts +35 -0
- package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte +52 -0
- package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte.d.ts +28 -0
- package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte +51 -0
- package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte.d.ts +30 -0
- package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte +59 -0
- package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte.d.ts +28 -0
- package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte +20 -0
- package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte.d.ts +8 -0
- package/dist/components/data-table-v9/header/column-header-filter.svelte +66 -0
- package/dist/components/data-table-v9/header/column-header-filter.svelte.d.ts +30 -0
- package/dist/components/data-table-v9/index.d.ts +9 -1
- package/dist/components/data-table-v9/index.js +7 -0
- package/dist/components/data-table-v9/table-view-state.svelte.d.ts +1 -0
- package/dist/components/data-table-v9/table-view-state.svelte.js +3 -0
- package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte +13 -269
- package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte.d.ts +1 -1
- package/dist/components/data-table-v9/types.d.ts +3 -1
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +4 -1
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte.d.ts +2 -0
- package/dist/components/data-table-v9/virtual/index.d.ts +2 -2
- package/dist/components/data-table-v9/virtual/index.js +1 -1
- package/dist/components/dialog/alert-dialog.svelte +2 -0
- package/dist/components/dialog/dialog-root.svelte +2 -0
- package/dist/components/drawer/drawer-content.svelte +2 -0
- package/dist/components/popover/popover-content.svelte +29 -3
- package/dist/components/popover/popover-content.svelte.d.ts +2 -1
- package/dist/components/popover/popover-description.svelte +4 -0
- package/dist/components/popover/popover-labels.svelte.d.ts +18 -0
- package/dist/components/popover/popover-labels.svelte.js +35 -0
- package/dist/components/popover/popover-root.svelte +3 -0
- package/dist/components/popover/popover-title.svelte +4 -0
- package/dist/icons/PhFunnelFill.svelte +19 -0
- package/dist/icons/PhFunnelFill.svelte.d.ts +5 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/utils/date-filter.d.ts +31 -0
- package/dist/utils/date-filter.js +58 -0
- package/dist/utils/date.d.ts +19 -1
- package/dist/utils/date.js +48 -1
- package/dist/utils/release-focus-on-exit.d.ts +22 -0
- package/dist/utils/release-focus-on-exit.js +26 -0
- package/package.json +1 -1
- package/skills/component-usage/references/overlays-and-floating-ui.md +7 -2
- package/skills/data-table/SKILL.md +80 -1
- package/skills/data-table/references/column-options.md +25 -0
- package/skills/data-table/references/table-state-and-reactivity.md +13 -8
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends RowData">
|
|
2
|
-
import { onDestroy } from 'svelte';
|
|
3
2
|
import type { Column, RowData } from '@tanstack/svelte-table';
|
|
4
3
|
import * as Popover from '../../popover';
|
|
5
4
|
import * as Tooltip from '../../tooltip';
|
|
6
5
|
import * as ScrollArea from '../../scroll-area';
|
|
7
|
-
import
|
|
8
|
-
import { cn } from 'tailwind-variants';
|
|
9
|
-
import type { DataTableInstance } from '../data-table-utils';
|
|
6
|
+
import { getColumnLabel, type DataTableInstance } from '../data-table-utils';
|
|
10
7
|
import type { DataTableFeatures } from '../features';
|
|
11
|
-
import NumberInput from '../../number-input/number-input.svelte';
|
|
12
|
-
import * as Field from '../../field';
|
|
13
8
|
import Badge from '../../badge/badge.svelte';
|
|
14
9
|
import { button } from '../../button/button.variants';
|
|
15
10
|
import { PhX, PhMagnifyingGlass, PhFunnel } from '../../../icons';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
type SelectFilterOption,
|
|
19
|
-
type SelectFilterValue
|
|
20
|
-
} from '../../../utils/select-filter';
|
|
21
|
-
import { mergeRangeFilter, rangeBounds, type PendingRange } from '../../../utils/range-filter';
|
|
22
|
-
import { DebouncedFilterEdits } from '../../../utils/debounced-filter-edit.svelte';
|
|
23
|
-
import { getFilterRevisions } from '../filter-revisions.svelte';
|
|
11
|
+
import { ColumnFilterEditing } from '../column-filter-editing.svelte';
|
|
12
|
+
import ColumnFilterEditor from '../filter-editors/column-filter-editor.svelte';
|
|
24
13
|
|
|
25
14
|
type Props = {
|
|
26
15
|
table: DataTableInstance<T>;
|
|
@@ -33,23 +22,12 @@
|
|
|
33
22
|
// Popover and tooltip share one trigger element, so both must agree on its id.
|
|
34
23
|
const triggerId = $props.id();
|
|
35
24
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const rangeEdits = new DebouncedFilterEdits<PendingRange>();
|
|
43
|
-
|
|
44
|
-
// What an edit is based on: the column's filter revision, which only ever goes up, so a
|
|
45
|
-
// filter cleared and set back to its old value still makes the edit stale. Tables not
|
|
46
|
-
// made by compote's createTable have no revisions; fall back to the filter value.
|
|
47
|
-
const filterRevisions = $derived(getFilterRevisions(table));
|
|
48
|
-
function editBase(column: Column<DataTableFeatures, T, unknown>): unknown {
|
|
49
|
-
return filterRevisions ? filterRevisions.get(column.id) : column.getFilterValue();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let localSelectSearch: Record<string, string> = $state({});
|
|
25
|
+
// Pending edits belong to the table: shared with the other filter hosts, and they still
|
|
26
|
+
// commit after this popover closes.
|
|
27
|
+
const editing = new ColumnFilterEditing({
|
|
28
|
+
table: () => table,
|
|
29
|
+
isActive: (columnId) => activeFilterIds.includes(columnId)
|
|
30
|
+
});
|
|
53
31
|
|
|
54
32
|
const columnFilters = $derived.by(() => table.atoms.columnFilters.get());
|
|
55
33
|
const columnVisibility = $derived.by(() => table.atoms.columnVisibility.get());
|
|
@@ -87,20 +65,6 @@
|
|
|
87
65
|
);
|
|
88
66
|
});
|
|
89
67
|
|
|
90
|
-
onDestroy(() => {
|
|
91
|
-
textEdits.cancelAll();
|
|
92
|
-
rangeEdits.cancelAll();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
function getColumnType(column: Column<DataTableFeatures, T, unknown>): string | undefined {
|
|
96
|
-
return (column.columnDef.meta as Record<string, unknown> | undefined)?.type as
|
|
97
|
-
string | undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function getColumnLabel(column: Column<DataTableFeatures, T, unknown>): string {
|
|
101
|
-
return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
68
|
function addFilter(column: Column<DataTableFeatures, T, unknown>) {
|
|
105
69
|
manualFilterIds = [...manualFilterIds, column.id];
|
|
106
70
|
showColumnPicker = false;
|
|
@@ -109,103 +73,17 @@
|
|
|
109
73
|
|
|
110
74
|
function removeFilter(column: Column<DataTableFeatures, T, unknown>) {
|
|
111
75
|
manualFilterIds = manualFilterIds.filter((id) => id !== column.id);
|
|
112
|
-
|
|
113
|
-
rangeEdits.cancel(column.id);
|
|
76
|
+
editing.cancel(column.id);
|
|
114
77
|
column.setFilterValue(undefined);
|
|
115
|
-
delete localSelectSearch[column.id];
|
|
116
78
|
}
|
|
117
79
|
|
|
118
80
|
function clearFilters() {
|
|
119
|
-
|
|
120
|
-
rangeEdits.cancelAll();
|
|
121
|
-
localSelectSearch = {};
|
|
81
|
+
editing.cancelAll();
|
|
122
82
|
manualFilterIds = [];
|
|
123
83
|
showColumnPicker = false;
|
|
124
84
|
columnSearchText = '';
|
|
125
85
|
table.resetColumnFilters();
|
|
126
86
|
}
|
|
127
|
-
|
|
128
|
-
function getTextValue(column: Column<DataTableFeatures, T, unknown>): string {
|
|
129
|
-
const value = column.getFilterValue();
|
|
130
|
-
return textEdits.get(column.id, editBase(column)) ?? (typeof value === 'string' ? value : '');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Whether `column` still has a filter card. A card torn down by an outside change can
|
|
135
|
-
* still emit — Ark's NumberInput commits the typed number on the blur its unmount
|
|
136
|
-
* triggers — and that must not write back a filter the app just cleared.
|
|
137
|
-
*/
|
|
138
|
-
function isCardActive(column: Column<DataTableFeatures, T, unknown>): boolean {
|
|
139
|
-
return activeFilterIds.includes(column.id);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function handleTextInput(column: Column<DataTableFeatures, T, unknown>, value: string) {
|
|
143
|
-
if (!isCardActive(column)) return;
|
|
144
|
-
textEdits.edit(
|
|
145
|
-
column.id,
|
|
146
|
-
editBase(column),
|
|
147
|
-
() => value,
|
|
148
|
-
() => editBase(column),
|
|
149
|
-
(text) => column.setFilterValue(text || undefined)
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function getRangeBound(
|
|
154
|
-
column: Column<DataTableFeatures, T, unknown>,
|
|
155
|
-
which: 'min' | 'max'
|
|
156
|
-
): number | null {
|
|
157
|
-
const value = column.getFilterValue();
|
|
158
|
-
const pending = rangeEdits.get(column.id, editBase(column));
|
|
159
|
-
if (pending && which in pending) return pending[which] ?? null;
|
|
160
|
-
return rangeBounds(value)[which === 'min' ? 0 : 1] ?? null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function handleNumericInput(
|
|
164
|
-
column: Column<DataTableFeatures, T, unknown>,
|
|
165
|
-
which: 'min' | 'max',
|
|
166
|
-
value: number | null
|
|
167
|
-
) {
|
|
168
|
-
if (!isCardActive(column)) return;
|
|
169
|
-
rangeEdits.edit(
|
|
170
|
-
column.id,
|
|
171
|
-
editBase(column),
|
|
172
|
-
(pending) => ({ ...pending, [which]: value }),
|
|
173
|
-
() => editBase(column),
|
|
174
|
-
(pending) => column.setFilterValue(mergeRangeFilter(column.getFilterValue(), pending))
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function getSelectValues(column: Column<DataTableFeatures, T, unknown>): SelectFilterValue[] {
|
|
179
|
-
return (column.getFilterValue() as SelectFilterValue[] | undefined) ?? [];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function handleSelectChange(
|
|
183
|
-
column: Column<DataTableFeatures, T, unknown>,
|
|
184
|
-
value: SelectFilterValue,
|
|
185
|
-
checked: boolean
|
|
186
|
-
) {
|
|
187
|
-
const current = getSelectValues(column);
|
|
188
|
-
const next = checked ? [...current, value] : current.filter((v) => v !== value);
|
|
189
|
-
column.setFilterValue(next.length ? next : undefined);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function getSelectOptions(column: Column<DataTableFeatures, T, unknown>): SelectFilterOption[] {
|
|
193
|
-
return selectFilterOptions(column.getFacetedUniqueValues());
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function getFacetedMinMax(
|
|
197
|
-
column: Column<DataTableFeatures, T, unknown>
|
|
198
|
-
): [number | undefined, number | undefined] {
|
|
199
|
-
const vals = column.getFacetedMinMaxValues();
|
|
200
|
-
return vals ? [vals[0] as number, vals[1] as number] : [undefined, undefined];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function getColumnFormatOptions(
|
|
204
|
-
column: Column<DataTableFeatures, T, unknown>
|
|
205
|
-
): Intl.NumberFormatOptions | undefined {
|
|
206
|
-
return (column.columnDef.meta as Record<string, unknown> | undefined)?.formatOptions as
|
|
207
|
-
Intl.NumberFormatOptions | undefined;
|
|
208
|
-
}
|
|
209
87
|
</script>
|
|
210
88
|
|
|
211
89
|
<Popover.Root ids={{ trigger: triggerId }} positioning={{ placement: 'bottom-end' }}>
|
|
@@ -243,7 +121,7 @@
|
|
|
243
121
|
|
|
244
122
|
<Popover.Content class="flex w-70 flex-col gap-3 p-3" showArrow={false}>
|
|
245
123
|
<div class="mr-1 flex items-center justify-between py-2.5">
|
|
246
|
-
<
|
|
124
|
+
<Popover.Title class="text-sm font-medium text-ink">Filters</Popover.Title>
|
|
247
125
|
{#if activeCount > 0}
|
|
248
126
|
<button type="button" onclick={clearFilters} class="text-xs text-primary hover:underline">
|
|
249
127
|
Clear all
|
|
@@ -269,141 +147,7 @@
|
|
|
269
147
|
</button>
|
|
270
148
|
</div>
|
|
271
149
|
|
|
272
|
-
{
|
|
273
|
-
{@const [facetMin, facetMax] = getFacetedMinMax(column)}
|
|
274
|
-
{@const colFormatOptions = getColumnFormatOptions(column)}
|
|
275
|
-
<div class="flex flex-col gap-1.5">
|
|
276
|
-
<div class="min-w-0 flex-1">
|
|
277
|
-
<NumberInput
|
|
278
|
-
layout="horizontal"
|
|
279
|
-
label="From"
|
|
280
|
-
bind:value={
|
|
281
|
-
() => getRangeBound(column, 'min'),
|
|
282
|
-
(bound) => handleNumericInput(column, 'min', bound ?? null)
|
|
283
|
-
}
|
|
284
|
-
min={facetMin}
|
|
285
|
-
max={facetMax}
|
|
286
|
-
formatOptions={colFormatOptions}
|
|
287
|
-
/>
|
|
288
|
-
</div>
|
|
289
|
-
<div class="min-w-0 flex-1">
|
|
290
|
-
<NumberInput
|
|
291
|
-
layout="horizontal"
|
|
292
|
-
label="To"
|
|
293
|
-
bind:value={
|
|
294
|
-
() => getRangeBound(column, 'max'),
|
|
295
|
-
(bound) => handleNumericInput(column, 'max', bound ?? null)
|
|
296
|
-
}
|
|
297
|
-
min={facetMin}
|
|
298
|
-
max={facetMax}
|
|
299
|
-
formatOptions={colFormatOptions}
|
|
300
|
-
/>
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
{:else if getColumnType(column) === 'boolean'}
|
|
304
|
-
{@const boolFilter = column.getFilterValue() as boolean | undefined}
|
|
305
|
-
<div class="flex overflow-hidden rounded border border-border text-xs">
|
|
306
|
-
<button
|
|
307
|
-
type="button"
|
|
308
|
-
onclick={() => column.setFilterValue(undefined)}
|
|
309
|
-
class={cn(
|
|
310
|
-
'flex-1 px-2 py-1',
|
|
311
|
-
boolFilter === undefined
|
|
312
|
-
? 'bg-surface-3 font-medium text-ink'
|
|
313
|
-
: 'text-ink-dim hover:bg-surface-2'
|
|
314
|
-
)}
|
|
315
|
-
>
|
|
316
|
-
All
|
|
317
|
-
</button>
|
|
318
|
-
<button
|
|
319
|
-
type="button"
|
|
320
|
-
onclick={() =>
|
|
321
|
-
column.setFilterValue(boolFilter === true ? undefined : true)}
|
|
322
|
-
class={cn(
|
|
323
|
-
'flex-1 border-x border-border px-2 py-1',
|
|
324
|
-
boolFilter === true
|
|
325
|
-
? 'bg-surface-3 font-medium text-ink'
|
|
326
|
-
: 'text-ink-dim hover:bg-surface-2'
|
|
327
|
-
)}
|
|
328
|
-
>
|
|
329
|
-
Yes
|
|
330
|
-
</button>
|
|
331
|
-
<button
|
|
332
|
-
type="button"
|
|
333
|
-
onclick={() =>
|
|
334
|
-
column.setFilterValue(boolFilter === false ? undefined : false)}
|
|
335
|
-
class={cn(
|
|
336
|
-
'flex-1 px-2 py-1',
|
|
337
|
-
boolFilter === false
|
|
338
|
-
? 'bg-surface-3 font-medium text-ink'
|
|
339
|
-
: 'text-ink-dim hover:bg-surface-2'
|
|
340
|
-
)}
|
|
341
|
-
>
|
|
342
|
-
No
|
|
343
|
-
</button>
|
|
344
|
-
</div>
|
|
345
|
-
{:else if getColumnType(column) === 'select'}
|
|
346
|
-
{@const allOptions = getSelectOptions(column)}
|
|
347
|
-
{@const search = localSelectSearch[column.id] ?? ''}
|
|
348
|
-
{@const options = search
|
|
349
|
-
? allOptions.filter((o) =>
|
|
350
|
-
o.label.toLowerCase().includes(search.toLowerCase())
|
|
351
|
-
)
|
|
352
|
-
: allOptions}
|
|
353
|
-
{@const selected = getSelectValues(column)}
|
|
354
|
-
<div class="flex flex-col gap-1">
|
|
355
|
-
<Field.Root hideMessageLine>
|
|
356
|
-
<Field.Input
|
|
357
|
-
placeholder="Search..."
|
|
358
|
-
value={search}
|
|
359
|
-
oninput={(e: Event) => {
|
|
360
|
-
localSelectSearch[column.id] = (
|
|
361
|
-
e.currentTarget as HTMLInputElement
|
|
362
|
-
).value;
|
|
363
|
-
}}
|
|
364
|
-
/>
|
|
365
|
-
</Field.Root>
|
|
366
|
-
<ScrollArea.Root>
|
|
367
|
-
<ScrollArea.Viewport class="max-h-40">
|
|
368
|
-
<ScrollArea.Content>
|
|
369
|
-
<div class="flex flex-col gap-0.5">
|
|
370
|
-
{#each options as option (option.value)}
|
|
371
|
-
<div
|
|
372
|
-
class="flex min-h-7 items-center gap-2 rounded-sm pr-2 hover:bg-surface-2"
|
|
373
|
-
>
|
|
374
|
-
<Checkbox
|
|
375
|
-
size="sm"
|
|
376
|
-
label={option.label}
|
|
377
|
-
class="min-h-7 flex-1 px-2"
|
|
378
|
-
checked={selected.includes(option.value)}
|
|
379
|
-
onCheckedChange={({ checked }) =>
|
|
380
|
-
handleSelectChange(column, option.value, checked === true)}
|
|
381
|
-
/>
|
|
382
|
-
<span class="text-xs text-ink-dim tabular-nums"
|
|
383
|
-
>{option.count}</span
|
|
384
|
-
>
|
|
385
|
-
</div>
|
|
386
|
-
{/each}
|
|
387
|
-
</div>
|
|
388
|
-
</ScrollArea.Content>
|
|
389
|
-
</ScrollArea.Viewport>
|
|
390
|
-
<ScrollArea.Scrollbar orientation="vertical">
|
|
391
|
-
<ScrollArea.Thumb />
|
|
392
|
-
</ScrollArea.Scrollbar>
|
|
393
|
-
<ScrollArea.Corner />
|
|
394
|
-
</ScrollArea.Root>
|
|
395
|
-
</div>
|
|
396
|
-
{:else}
|
|
397
|
-
<Field.Root hideMessageLine>
|
|
398
|
-
<Field.Input
|
|
399
|
-
placeholder="Search..."
|
|
400
|
-
bind:value={
|
|
401
|
-
() => getTextValue(column),
|
|
402
|
-
(text) => handleTextInput(column, String(text ?? ''))
|
|
403
|
-
}
|
|
404
|
-
/>
|
|
405
|
-
</Field.Root>
|
|
406
|
-
{/if}
|
|
150
|
+
<ColumnFilterEditor {column} {editing} />
|
|
407
151
|
</div>
|
|
408
152
|
{/each}
|
|
409
153
|
</ScrollArea.Content>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RowData } from '@tanstack/svelte-table';
|
|
2
|
-
import type
|
|
2
|
+
import { type DataTableInstance } from '../data-table-utils';
|
|
3
3
|
declare function $$render<T extends RowData>(): {
|
|
4
4
|
props: {
|
|
5
5
|
table: DataTableInstance<T>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FilterFnOption, RowData } from '@tanstack/svelte-table';
|
|
1
|
+
import type { Column, FilterFnOption, RowData } from '@tanstack/svelte-table';
|
|
2
2
|
import type { Component, Snippet } from 'svelte';
|
|
3
3
|
import type { DataTableFeatures } from './features';
|
|
4
4
|
export type DataTableAlign = 'left' | 'center' | 'right';
|
|
@@ -63,6 +63,8 @@ export type DataTableGroupColumn<T extends RowData> = DataTableColumnBase & {
|
|
|
63
63
|
formatLocale?: never;
|
|
64
64
|
};
|
|
65
65
|
export type DataTableColumn<T extends RowData> = DataTableLeafColumn<T> | DataTableGroupColumn<T>;
|
|
66
|
+
/** A column of a compote data table, as `table.getColumn()` and `getAllLeafColumns()` return it. */
|
|
67
|
+
export type DataTableColumnInstance<T extends RowData> = Column<DataTableFeatures, T, unknown>;
|
|
66
68
|
export type DataTableColumnMeta = {
|
|
67
69
|
align?: DataTableAlign;
|
|
68
70
|
type?: DataTableColumnType;
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
caption?: string;
|
|
15
15
|
emptyMessage?: string;
|
|
16
16
|
class?: ClassValue;
|
|
17
|
+
/** Show a filter funnel in the header of every filterable column. */
|
|
18
|
+
headerFilters?: boolean;
|
|
17
19
|
onRowClick?: (details: { row: T; event: MouseEvent }) => void;
|
|
18
20
|
onRowDoubleClick?: (details: { row: T; event: MouseEvent }) => void;
|
|
19
21
|
};
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
caption,
|
|
24
26
|
emptyMessage = 'No rows found',
|
|
25
27
|
class: className,
|
|
28
|
+
headerFilters = false,
|
|
26
29
|
onRowClick,
|
|
27
30
|
onRowDoubleClick,
|
|
28
31
|
...rest
|
|
@@ -52,7 +55,7 @@
|
|
|
52
55
|
{#if caption}
|
|
53
56
|
<caption class="sr-only">{caption}</caption>
|
|
54
57
|
{/if}
|
|
55
|
-
<DataTableHead {table} {view} isVirtual />
|
|
58
|
+
<DataTableHead {table} {view} isVirtual {headerFilters} />
|
|
56
59
|
{#if scrollContainerRef}
|
|
57
60
|
<DataTableVirtualRows
|
|
58
61
|
{table}
|
|
@@ -8,6 +8,8 @@ declare function $$render<T extends RowData>(): {
|
|
|
8
8
|
caption?: string;
|
|
9
9
|
emptyMessage?: string;
|
|
10
10
|
class?: ClassValue;
|
|
11
|
+
/** Show a filter funnel in the header of every filterable column. */
|
|
12
|
+
headerFilters?: boolean;
|
|
11
13
|
onRowClick?: (details: {
|
|
12
14
|
row: T;
|
|
13
15
|
event: MouseEvent;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as Root } from './data-table-virtualized.svelte';
|
|
2
|
-
export { ColumnFilter, ColumnVisibility, FlexRender, Refresh, Search, Title, Toolbar, createDataTableColumnHelper, createTable, renderComponent, renderSnippet } from '../index';
|
|
3
|
-
export type { CreateDataTableOptions, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableAlign, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableColumn, DataTableColumnBase, DataTableColumnOptions, DataTableColumnType, DataTableGroupColumn, DataTableInstance, DataTableLeafColumn, DataTableLeafColumnBase } from '../index';
|
|
2
|
+
export { ColumnFilter, ColumnFilterEditing, ColumnFilterEditor, ColumnVisibility, EMPTY_SELECT_VALUE, FlexRender, Refresh, Search, Title, Toolbar, createDataTableColumnHelper, createTable, filterKindFor, getColumnFilterKind, getColumnLabel, renderComponent, renderSnippet, selectFilterOptions } from '../index';
|
|
3
|
+
export type { ColumnFilterEditingOptions, ColumnFilterKind, CreateDataTableOptions, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableAlign, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableColumn, DataTableColumnBase, DataTableColumnInstance, DataTableColumnOptions, DataTableColumnType, DataTableGroupColumn, DataTableInstance, DataTableLeafColumn, DataTableLeafColumnBase, FilterableColumn, SelectFilterOption, SelectFilterValue } from '../index';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as Root } from './data-table-virtualized.svelte';
|
|
2
|
-
export { ColumnFilter, ColumnVisibility, FlexRender, Refresh, Search, Title, Toolbar, createDataTableColumnHelper, createTable, renderComponent, renderSnippet } from '../index';
|
|
2
|
+
export { ColumnFilter, ColumnFilterEditing, ColumnFilterEditor, ColumnVisibility, EMPTY_SELECT_VALUE, FlexRender, Refresh, Search, Title, Toolbar, createDataTableColumnHelper, createTable, filterKindFor, getColumnFilterKind, getColumnLabel, renderComponent, renderSnippet, selectFilterOptions } from '../index';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { PhX } from '../../icons';
|
|
3
3
|
import { Dialog } from '@ark-ui/svelte/dialog';
|
|
4
4
|
import { Portal } from '@ark-ui/svelte/portal';
|
|
5
|
+
import { releaseFocusOnExitProps } from '../../utils/release-focus-on-exit';
|
|
5
6
|
import type { AlertDialogProps } from './dialog.types';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
>
|
|
38
39
|
<Dialog.Content
|
|
39
40
|
class="relative w-full max-w-md rounded-lg border bg-surface-1 p-6 shadow-xl data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"
|
|
41
|
+
{...releaseFocusOnExitProps}
|
|
40
42
|
>
|
|
41
43
|
<Dialog.Title class="text-lg leading-none font-semibold tracking-tight">
|
|
42
44
|
{title}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Dialog } from '@ark-ui/svelte/dialog';
|
|
3
3
|
import { Portal } from '@ark-ui/svelte/portal';
|
|
4
|
+
import { releaseFocusOnExitProps } from '../../utils/release-focus-on-exit';
|
|
4
5
|
import { cn } from 'tailwind-variants';
|
|
5
6
|
import type { DialogProps } from './dialog.types';
|
|
6
7
|
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
'relative w-full max-w-2xl rounded-lg border bg-surface-1 p-6 shadow-xl data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
36
37
|
contentClass
|
|
37
38
|
)}
|
|
39
|
+
{...releaseFocusOnExitProps}
|
|
38
40
|
>
|
|
39
41
|
{@render children()}
|
|
40
42
|
</Dialog.Content>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { DrawerContentBaseProps } from '@ark-ui/svelte/drawer';
|
|
4
4
|
import type { ClassValue } from 'svelte/elements';
|
|
5
5
|
import { cn } from 'tailwind-variants';
|
|
6
|
+
import { releaseFocusOnExitProps } from '../../utils/release-focus-on-exit';
|
|
6
7
|
|
|
7
8
|
interface Props extends DrawerContentBaseProps {
|
|
8
9
|
class?: ClassValue;
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
'pointer-events-auto relative flex h-full w-full flex-col bg-surface-1 shadow-xl outline-none',
|
|
55
56
|
className
|
|
56
57
|
)}
|
|
58
|
+
{...releaseFocusOnExitProps}
|
|
57
59
|
>
|
|
58
60
|
{@render children?.()}
|
|
59
61
|
</Drawer.Content>
|
|
@@ -1,28 +1,54 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Popover } from '@ark-ui/svelte/popover';
|
|
2
|
+
import { Popover, usePopoverContext } from '@ark-ui/svelte/popover';
|
|
3
|
+
import type { PopoverContentProps } from '@ark-ui/svelte/popover';
|
|
3
4
|
import { Portal } from '@ark-ui/svelte/portal';
|
|
4
5
|
import { cn } from 'tailwind-variants';
|
|
6
|
+
import { releaseFocusOnExitProps } from '../../utils/release-focus-on-exit';
|
|
5
7
|
import type { ClassValue } from 'svelte/elements';
|
|
6
8
|
import type { Snippet } from 'svelte';
|
|
9
|
+
import { getPopoverLabels } from './popover-labels.svelte';
|
|
7
10
|
|
|
8
|
-
interface Props {
|
|
11
|
+
interface Props extends Omit<PopoverContentProps, 'class' | 'children'> {
|
|
9
12
|
class?: ClassValue;
|
|
10
13
|
children: Snippet;
|
|
11
14
|
showArrow?: boolean;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
let {
|
|
17
|
+
let {
|
|
18
|
+
class: className,
|
|
19
|
+
children,
|
|
20
|
+
showArrow = true,
|
|
21
|
+
'aria-labelledby': ariaLabelledby,
|
|
22
|
+
'aria-describedby': ariaDescribedby,
|
|
23
|
+
...rest
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
const popover = usePopoverContext();
|
|
27
|
+
const labels = getPopoverLabels();
|
|
28
|
+
|
|
29
|
+
// Explicit labelling wins; otherwise link the Title / Description rendered inside.
|
|
30
|
+
const labelledby = $derived(
|
|
31
|
+
ariaLabelledby ??
|
|
32
|
+
(labels?.hasTitle && !rest['aria-label'] ? popover().getTitleProps().id : undefined)
|
|
33
|
+
);
|
|
34
|
+
const describedby = $derived(
|
|
35
|
+
ariaDescribedby ?? (labels?.hasDescription ? popover().getDescriptionProps().id : undefined)
|
|
36
|
+
);
|
|
15
37
|
</script>
|
|
16
38
|
|
|
17
39
|
<Portal>
|
|
18
40
|
<Popover.Positioner>
|
|
19
41
|
<Popover.Content
|
|
42
|
+
{...rest}
|
|
43
|
+
aria-labelledby={labelledby}
|
|
44
|
+
aria-describedby={describedby}
|
|
20
45
|
class={cn(
|
|
21
46
|
'z-50 w-72 rounded-md border bg-surface-document p-4 shadow-md outline-none [--arrow-background:var(--compote-surface-1)] [--arrow-size:10px]',
|
|
22
47
|
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
23
48
|
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
24
49
|
className
|
|
25
50
|
)}
|
|
51
|
+
{...releaseFocusOnExitProps}
|
|
26
52
|
>
|
|
27
53
|
{#if showArrow}
|
|
28
54
|
<Popover.Arrow>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { PopoverContentProps } from '@ark-ui/svelte/popover';
|
|
1
2
|
import type { ClassValue } from 'svelte/elements';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
|
-
interface Props {
|
|
4
|
+
interface Props extends Omit<PopoverContentProps, 'class' | 'children'> {
|
|
4
5
|
class?: ClassValue;
|
|
5
6
|
children: Snippet;
|
|
6
7
|
showArrow?: boolean;
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
import { Popover } from '@ark-ui/svelte/popover';
|
|
3
3
|
import type { PopoverDescriptionBaseProps } from '@ark-ui/svelte/popover';
|
|
4
4
|
import type { ClassValue } from 'svelte/elements';
|
|
5
|
+
import { getPopoverLabels } from './popover-labels.svelte';
|
|
5
6
|
|
|
6
7
|
interface Props extends PopoverDescriptionBaseProps {
|
|
7
8
|
class?: ClassValue;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
let { class: className, children, ...rest }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const labels = getPopoverLabels();
|
|
14
|
+
$effect(() => labels?.registerDescription());
|
|
11
15
|
</script>
|
|
12
16
|
|
|
13
17
|
<Popover.Description {...rest} class={className ?? 'mt-1 text-sm text-ink-dim'}>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which labelling parts a popover currently renders. Zag checks for a title/description only
|
|
3
|
+
* when its machine starts — before lazily mounted content exists — so it never sets
|
|
4
|
+
* `aria-labelledby` / `aria-describedby` on the content. Title and Description register here
|
|
5
|
+
* instead, and Content links them by the ids zag gives them.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PopoverLabels {
|
|
8
|
+
#private;
|
|
9
|
+
get hasTitle(): boolean;
|
|
10
|
+
get hasDescription(): boolean;
|
|
11
|
+
/** Registers a rendered title; returns the unregister function. */
|
|
12
|
+
registerTitle(): () => void;
|
|
13
|
+
/** Registers a rendered description; returns the unregister function. */
|
|
14
|
+
registerDescription(): () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function setPopoverLabels(): PopoverLabels;
|
|
17
|
+
/** Undefined when the part is rendered outside compote's `Popover.Root`. */
|
|
18
|
+
export declare function getPopoverLabels(): PopoverLabels | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getContext, setContext, untrack } from 'svelte';
|
|
2
|
+
const key = Symbol('compote-popover-labels');
|
|
3
|
+
/**
|
|
4
|
+
* Which labelling parts a popover currently renders. Zag checks for a title/description only
|
|
5
|
+
* when its machine starts — before lazily mounted content exists — so it never sets
|
|
6
|
+
* `aria-labelledby` / `aria-describedby` on the content. Title and Description register here
|
|
7
|
+
* instead, and Content links them by the ids zag gives them.
|
|
8
|
+
*/
|
|
9
|
+
export class PopoverLabels {
|
|
10
|
+
#titles = $state(0);
|
|
11
|
+
#descriptions = $state(0);
|
|
12
|
+
get hasTitle() {
|
|
13
|
+
return this.#titles > 0;
|
|
14
|
+
}
|
|
15
|
+
get hasDescription() {
|
|
16
|
+
return this.#descriptions > 0;
|
|
17
|
+
}
|
|
18
|
+
/** Registers a rendered title; returns the unregister function. */
|
|
19
|
+
registerTitle() {
|
|
20
|
+
untrack(() => this.#titles++);
|
|
21
|
+
return () => untrack(() => this.#titles--);
|
|
22
|
+
}
|
|
23
|
+
/** Registers a rendered description; returns the unregister function. */
|
|
24
|
+
registerDescription() {
|
|
25
|
+
untrack(() => this.#descriptions++);
|
|
26
|
+
return () => untrack(() => this.#descriptions--);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function setPopoverLabels() {
|
|
30
|
+
return setContext(key, new PopoverLabels());
|
|
31
|
+
}
|
|
32
|
+
/** Undefined when the part is rendered outside compote's `Popover.Root`. */
|
|
33
|
+
export function getPopoverLabels() {
|
|
34
|
+
return getContext(key);
|
|
35
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Popover } from '@ark-ui/svelte/popover';
|
|
3
3
|
import type { PopoverRootBaseProps } from '@ark-ui/svelte/popover';
|
|
4
4
|
import type { Snippet } from 'svelte';
|
|
5
|
+
import { setPopoverLabels } from './popover-labels.svelte';
|
|
5
6
|
|
|
6
7
|
interface Props extends PopoverRootBaseProps {
|
|
7
8
|
open?: boolean;
|
|
@@ -17,6 +18,8 @@
|
|
|
17
18
|
unmountOnExit = true,
|
|
18
19
|
...restProps
|
|
19
20
|
}: Props = $props();
|
|
21
|
+
|
|
22
|
+
setPopoverLabels();
|
|
20
23
|
</script>
|
|
21
24
|
|
|
22
25
|
<Popover.Root bind:open {lazyMount} {unmountOnExit} {...restProps}>
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
import { Popover } from '@ark-ui/svelte/popover';
|
|
3
3
|
import type { PopoverTitleBaseProps } from '@ark-ui/svelte/popover';
|
|
4
4
|
import type { ClassValue } from 'svelte/elements';
|
|
5
|
+
import { getPopoverLabels } from './popover-labels.svelte';
|
|
5
6
|
|
|
6
7
|
interface Props extends PopoverTitleBaseProps {
|
|
7
8
|
class?: ClassValue;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
let { class: className, children, ...rest }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const labels = getPopoverLabels();
|
|
14
|
+
$effect(() => labels?.registerTitle());
|
|
11
15
|
</script>
|
|
12
16
|
|
|
13
17
|
<Popover.Title {...rest} class={className ?? 'text-sm font-semibold'}>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let { class: className = '', ...restProps } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
width="1em"
|
|
8
|
+
height="1em"
|
|
9
|
+
viewBox="0 0 256 256"
|
|
10
|
+
class={className}
|
|
11
|
+
{...restProps}
|
|
12
|
+
>
|
|
13
|
+
<path
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
d="m227.81 66.76l-.08.09L160 139.17v55.49a16 16 0 0 1-7.13 13.34l-32 21.34A16 16 0 0 1 96 216v-76.83L28.27 66.85l-.08-.09A16 16 0 0 1 40 40h176a16 16 0 0 1 11.84 26.76Z"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
|
|
19
|
+
<!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE -->
|