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
package/dist/icons/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { default as PhFile } from './PhFile.svelte';
|
|
|
16
16
|
export { default as PhFileArchive } from './PhFileArchive.svelte';
|
|
17
17
|
export { default as PhFileText } from './PhFileText.svelte';
|
|
18
18
|
export { default as PhFunnel } from './PhFunnel.svelte';
|
|
19
|
+
export { default as PhFunnelFill } from './PhFunnelFill.svelte';
|
|
19
20
|
export { default as PhHeadphones } from './PhHeadphones.svelte';
|
|
20
21
|
export { default as PhImage } from './PhImage.svelte';
|
|
21
22
|
export { default as PhListMagnifyingGlass } from './PhListMagnifyingGlass.svelte';
|
package/dist/icons/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { default as PhFile } from './PhFile.svelte';
|
|
|
16
16
|
export { default as PhFileArchive } from './PhFileArchive.svelte';
|
|
17
17
|
export { default as PhFileText } from './PhFileText.svelte';
|
|
18
18
|
export { default as PhFunnel } from './PhFunnel.svelte';
|
|
19
|
+
export { default as PhFunnelFill } from './PhFunnelFill.svelte';
|
|
19
20
|
export { default as PhHeadphones } from './PhHeadphones.svelte';
|
|
20
21
|
export { default as PhImage } from './PhImage.svelte';
|
|
21
22
|
export { default as PhListMagnifyingGlass } from './PhListMagnifyingGlass.svelte';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar-day range filters for date and date-time columns.
|
|
3
|
+
*
|
|
4
|
+
* The filter value is `[from?, to?]`, each an ISO calendar date (`YYYY-MM-DD`) or empty — no
|
|
5
|
+
* time, no zone, so it serializes cleanly. Both bounds are inclusive days. A cell matches
|
|
6
|
+
* when the day it shows in the display zone (see `readDateInZone`) falls inside the range.
|
|
7
|
+
* For an instant that is the half-open interval [start of `from`, start of the day after
|
|
8
|
+
* `to`) in that zone, with day starts taken from the calendar — so 23- and 25-hour DST days
|
|
9
|
+
* need no special handling.
|
|
10
|
+
*/
|
|
11
|
+
import { CalendarDate } from '@internationalized/date';
|
|
12
|
+
/** A date range filter value as stored in table state. */
|
|
13
|
+
export type DateRangeFilterValue = [from?: string, to?: string];
|
|
14
|
+
/** A filter value read into calendar dates; an absent bound leaves that end open. */
|
|
15
|
+
export type DateRange = {
|
|
16
|
+
from?: CalendarDate;
|
|
17
|
+
to?: CalendarDate;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The range a filter value stands for. Invalid bounds are dropped and a reversed range is
|
|
21
|
+
* swapped (as TanStack's `inNumberRange` does). Always returns a range — never undefined —
|
|
22
|
+
* because the table falls back to the raw value when `resolveFilterValue` returns nullish.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveDateRange(filterValue: unknown): DateRange;
|
|
25
|
+
/** Whether a filter value has no usable bound, so the filter should be removed. */
|
|
26
|
+
export declare function isEmptyDateRange(filterValue: unknown): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a cell value falls in `range`, judged by the calendar day it shows in `timeZone`.
|
|
29
|
+
* Empty or unparseable cell values never match while the filter is active.
|
|
30
|
+
*/
|
|
31
|
+
export declare function matchesDateRange(value: unknown, range: DateRange, timeZone: string): boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar-day range filters for date and date-time columns.
|
|
3
|
+
*
|
|
4
|
+
* The filter value is `[from?, to?]`, each an ISO calendar date (`YYYY-MM-DD`) or empty — no
|
|
5
|
+
* time, no zone, so it serializes cleanly. Both bounds are inclusive days. A cell matches
|
|
6
|
+
* when the day it shows in the display zone (see `readDateInZone`) falls inside the range.
|
|
7
|
+
* For an instant that is the half-open interval [start of `from`, start of the day after
|
|
8
|
+
* `to`) in that zone, with day starts taken from the calendar — so 23- and 25-hour DST days
|
|
9
|
+
* need no special handling.
|
|
10
|
+
*/
|
|
11
|
+
import { CalendarDate, parseDate, toCalendarDate } from '@internationalized/date';
|
|
12
|
+
import { readDateInZone } from './date';
|
|
13
|
+
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
14
|
+
/** A bound as a calendar date; anything but a valid `YYYY-MM-DD` reads as no bound. */
|
|
15
|
+
function readBound(value) {
|
|
16
|
+
if (typeof value !== 'string' || !DATE_ONLY.test(value))
|
|
17
|
+
return undefined;
|
|
18
|
+
try {
|
|
19
|
+
return parseDate(value);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The range a filter value stands for. Invalid bounds are dropped and a reversed range is
|
|
27
|
+
* swapped (as TanStack's `inNumberRange` does). Always returns a range — never undefined —
|
|
28
|
+
* because the table falls back to the raw value when `resolveFilterValue` returns nullish.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveDateRange(filterValue) {
|
|
31
|
+
if (!Array.isArray(filterValue))
|
|
32
|
+
return {};
|
|
33
|
+
const from = readBound(filterValue[0]);
|
|
34
|
+
const to = readBound(filterValue[1]);
|
|
35
|
+
if (from && to && from.compare(to) > 0)
|
|
36
|
+
return { from: to, to: from };
|
|
37
|
+
return { from, to };
|
|
38
|
+
}
|
|
39
|
+
/** Whether a filter value has no usable bound, so the filter should be removed. */
|
|
40
|
+
export function isEmptyDateRange(filterValue) {
|
|
41
|
+
const { from, to } = resolveDateRange(filterValue);
|
|
42
|
+
return !from && !to;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether a cell value falls in `range`, judged by the calendar day it shows in `timeZone`.
|
|
46
|
+
* Empty or unparseable cell values never match while the filter is active.
|
|
47
|
+
*/
|
|
48
|
+
export function matchesDateRange(value, range, timeZone) {
|
|
49
|
+
const date = readDateInZone(value, timeZone);
|
|
50
|
+
if (!date)
|
|
51
|
+
return false;
|
|
52
|
+
const day = date instanceof CalendarDate ? date : toCalendarDate(date);
|
|
53
|
+
if (range.from && day.compare(range.from) < 0)
|
|
54
|
+
return false;
|
|
55
|
+
if (range.to && day.compare(range.to) > 0)
|
|
56
|
+
return false;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
package/dist/utils/date.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DateValue } from '@internationalized/date';
|
|
1
|
+
import { CalendarDate, ZonedDateTime, type DateValue } from '@internationalized/date';
|
|
2
2
|
export type { DateValue };
|
|
3
3
|
/**
|
|
4
4
|
* Anything the date components accept for a single value: an
|
|
@@ -24,3 +24,21 @@ export declare function dateValueToString(value: DateValue): string;
|
|
|
24
24
|
export declare function dateValueToDate(value: DateValue, timeZone?: string): Date;
|
|
25
25
|
/** Emit a `DateValue` back in the original `shape` it was bound as. */
|
|
26
26
|
export declare function fromDateValue(value: DateValue | null, shape: DateValueShape, timeZone?: string): DateValue | string | Date | null;
|
|
27
|
+
/**
|
|
28
|
+
* A value read for display in `timeZone`: a calendar date (a day, with no instant behind it)
|
|
29
|
+
* or an instant converted into `timeZone`.
|
|
30
|
+
*/
|
|
31
|
+
export type ZonedOrCalendarDate = CalendarDate | ZonedDateTime;
|
|
32
|
+
/**
|
|
33
|
+
* Read any date-ish value — a `DateValue`, ISO/DB string, native `Date` or epoch-millisecond
|
|
34
|
+
* number — the way it should be shown in `timeZone`. Never throws: anything unparseable
|
|
35
|
+
* (including an out-of-range date like `2024-02-30`) is `null`.
|
|
36
|
+
*
|
|
37
|
+
* - `"2024-03-15"` and `CalendarDate` stay calendar dates. They are never turned into an
|
|
38
|
+
* instant, which would put them on the previous day in zones west of UTC.
|
|
39
|
+
* - `Date`, epoch numbers, offset strings (`Z`, `+02:00`) and `ZonedDateTime` are instants,
|
|
40
|
+
* converted into `timeZone`.
|
|
41
|
+
* - A datetime without an offset (`"2024-03-15T10:30"`, `CalendarDateTime`) is a wall-clock
|
|
42
|
+
* time in `timeZone`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readDateInZone(value: unknown, timeZone: string): ZonedOrCalendarDate | null;
|
package/dist/utils/date.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseDate, parseDateTime, parseAbsolute, parseAbsoluteToLocal, fromDate, getLocalTimeZone } from '@internationalized/date';
|
|
1
|
+
import { CalendarDate, CalendarDateTime, ZonedDateTime, parseDate, parseDateTime, parseAbsolute, parseAbsoluteToLocal, fromAbsolute, fromDate, getLocalTimeZone, toTimeZone, toZoned } from '@internationalized/date';
|
|
2
2
|
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
3
|
// Trailing `Z`, or a `+HH`, `+HH:MM`, `+HHMM` offset.
|
|
4
4
|
const HAS_OFFSET = /(?:[zZ]|[+-]\d{2}(?::?\d{2})?)$/;
|
|
@@ -62,3 +62,50 @@ export function fromDateValue(value, shape, timeZone) {
|
|
|
62
62
|
return dateValueToDate(value, timeZone);
|
|
63
63
|
return value;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Read any date-ish value — a `DateValue`, ISO/DB string, native `Date` or epoch-millisecond
|
|
67
|
+
* number — the way it should be shown in `timeZone`. Never throws: anything unparseable
|
|
68
|
+
* (including an out-of-range date like `2024-02-30`) is `null`.
|
|
69
|
+
*
|
|
70
|
+
* - `"2024-03-15"` and `CalendarDate` stay calendar dates. They are never turned into an
|
|
71
|
+
* instant, which would put them on the previous day in zones west of UTC.
|
|
72
|
+
* - `Date`, epoch numbers, offset strings (`Z`, `+02:00`) and `ZonedDateTime` are instants,
|
|
73
|
+
* converted into `timeZone`.
|
|
74
|
+
* - A datetime without an offset (`"2024-03-15T10:30"`, `CalendarDateTime`) is a wall-clock
|
|
75
|
+
* time in `timeZone`.
|
|
76
|
+
*/
|
|
77
|
+
export function readDateInZone(value, timeZone) {
|
|
78
|
+
try {
|
|
79
|
+
return readDate(value, timeZone);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function readDate(value, timeZone) {
|
|
86
|
+
if (value == null)
|
|
87
|
+
return null;
|
|
88
|
+
if (value instanceof Date) {
|
|
89
|
+
return Number.isNaN(value.getTime()) ? null : fromDate(value, timeZone);
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === 'number') {
|
|
92
|
+
return Number.isFinite(value) ? fromAbsolute(value, timeZone) : null;
|
|
93
|
+
}
|
|
94
|
+
if (value instanceof ZonedDateTime)
|
|
95
|
+
return toTimeZone(value, timeZone);
|
|
96
|
+
if (value instanceof CalendarDateTime)
|
|
97
|
+
return toZoned(value, timeZone);
|
|
98
|
+
if (value instanceof CalendarDate)
|
|
99
|
+
return value;
|
|
100
|
+
if (typeof value !== 'string')
|
|
101
|
+
return null;
|
|
102
|
+
const raw = value.trim();
|
|
103
|
+
if (!raw)
|
|
104
|
+
return null;
|
|
105
|
+
if (DATE_ONLY.test(raw))
|
|
106
|
+
return parseDate(raw);
|
|
107
|
+
const iso = normalizeIso(raw);
|
|
108
|
+
return HAS_OFFSET.test(iso)
|
|
109
|
+
? parseAbsolute(iso, timeZone)
|
|
110
|
+
: toZoned(parseDateTime(iso), timeZone);
|
|
111
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare function releaseFocusOnExit(event: Event): void;
|
|
2
|
+
/**
|
|
3
|
+
* Spread onto Ark overlay content (popover, dialog, drawer) so a field focused inside it blurs
|
|
4
|
+
* before the content unmounts, not during the unmount.
|
|
5
|
+
*
|
|
6
|
+
* Zag's presence can unmount content from inside a Svelte effect (it does when the document is
|
|
7
|
+
* hidden). Svelte then removes the content mid-flush; a field still focused in there blurs during
|
|
8
|
+
* that removal, and zag handles some blurs with `flushSync` (DatePicker's machine, for one). That
|
|
9
|
+
* nested `flushSync` breaks the outer flush: "Cannot read properties of null (reading 'clear')"
|
|
10
|
+
* in Svelte's `flush_queued_effects` (still the case in svelte 5.57.2).
|
|
11
|
+
*
|
|
12
|
+
* Zag dispatches `exitcomplete` on the content node once presence decides to unmount, before
|
|
13
|
+
* Svelte removes it. Blurring there lets the field commit on blur as usual, at a point where a
|
|
14
|
+
* nested `flushSync` is safe. Usually focus is already back on the trigger by then (exit
|
|
15
|
+
* animations outlast the focus restore), and this does nothing.
|
|
16
|
+
*
|
|
17
|
+
* A spread because Ark's content prop types don't list the `exitcomplete` event.
|
|
18
|
+
*/
|
|
19
|
+
export declare const releaseFocusOnExitProps: {
|
|
20
|
+
onexitcomplete: typeof releaseFocusOnExit;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function releaseFocusOnExit(event) {
|
|
2
|
+
const content = event.currentTarget;
|
|
3
|
+
if (!(content instanceof HTMLElement))
|
|
4
|
+
return;
|
|
5
|
+
const active = content.ownerDocument.activeElement;
|
|
6
|
+
if (active instanceof HTMLElement && content.contains(active))
|
|
7
|
+
active.blur();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Spread onto Ark overlay content (popover, dialog, drawer) so a field focused inside it blurs
|
|
11
|
+
* before the content unmounts, not during the unmount.
|
|
12
|
+
*
|
|
13
|
+
* Zag's presence can unmount content from inside a Svelte effect (it does when the document is
|
|
14
|
+
* hidden). Svelte then removes the content mid-flush; a field still focused in there blurs during
|
|
15
|
+
* that removal, and zag handles some blurs with `flushSync` (DatePicker's machine, for one). That
|
|
16
|
+
* nested `flushSync` breaks the outer flush: "Cannot read properties of null (reading 'clear')"
|
|
17
|
+
* in Svelte's `flush_queued_effects` (still the case in svelte 5.57.2).
|
|
18
|
+
*
|
|
19
|
+
* Zag dispatches `exitcomplete` on the content node once presence decides to unmount, before
|
|
20
|
+
* Svelte removes it. Blurring there lets the field commit on blur as usual, at a point where a
|
|
21
|
+
* nested `flushSync` is safe. Usually focus is already back on the trigger by then (exit
|
|
22
|
+
* animations outlast the focus restore), and this does nothing.
|
|
23
|
+
*
|
|
24
|
+
* A spread because Ark's content prop types don't list the `exitcomplete` event.
|
|
25
|
+
*/
|
|
26
|
+
export const releaseFocusOnExitProps = { onexitcomplete: releaseFocusOnExit };
|
package/package.json
CHANGED
|
@@ -183,8 +183,13 @@ Composable floating panel anchored to a trigger.
|
|
|
183
183
|
</Popover.Root>
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
`Popover.Content` accepts `showArrow?: boolean` (default `true`)
|
|
187
|
-
`<Popover.Root positioning={{ placement: 'right' }}>`.
|
|
186
|
+
`Popover.Content` accepts `showArrow?: boolean` (default `true`) and forwards other props to Ark's
|
|
187
|
+
`Popover.Content`. Control placement via `<Popover.Root positioning={{ placement: 'right' }}>`.
|
|
188
|
+
|
|
189
|
+
The dialog is labelled automatically: while a `Popover.Title` / `Popover.Description` is rendered
|
|
190
|
+
inside, `Popover.Content` gets `aria-labelledby` / `aria-describedby` pointing at it — no `ids` or
|
|
191
|
+
manual `aria-*` needed (Zag alone misses them because content mounts lazily). An explicit
|
|
192
|
+
`aria-labelledby` / `aria-describedby` (or `aria-label`, for the title) on `Popover.Content` wins.
|
|
188
193
|
|
|
189
194
|
## HoverCard
|
|
190
195
|
|
|
@@ -8,7 +8,7 @@ description: >
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: composition
|
|
10
10
|
library: compote-ui
|
|
11
|
-
library_version: '0.
|
|
11
|
+
library_version: '0.81.0'
|
|
12
12
|
requires:
|
|
13
13
|
- component-usage
|
|
14
14
|
- theming
|
|
@@ -20,6 +20,13 @@ sources:
|
|
|
20
20
|
- src/lib/components/data-table-v9/column-helper.ts
|
|
21
21
|
- src/lib/components/data-table-v9/create-table.svelte.ts
|
|
22
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
|
|
23
30
|
- src/lib/components/data-table-v9/virtual/data-table-virtualized.svelte
|
|
24
31
|
- src/lib/components/data-table-v9/virtual/data-table-virtual-rows.svelte
|
|
25
32
|
---
|
|
@@ -162,6 +169,78 @@ shows the active filter count as a badge in the corner either way.
|
|
|
162
169
|
<DataTable.ColumnVisibility {table} triggerLabel="Columns" />
|
|
163
170
|
```
|
|
164
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
|
+
|
|
165
244
|
### Add a refresh button
|
|
166
245
|
|
|
167
246
|
`DataTable.Refresh` is data-source agnostic: it calls `onRefresh` and, if that returns a promise,
|
|
@@ -58,6 +58,29 @@ Type defaults:
|
|
|
58
58
|
|
|
59
59
|
Use one `grow: true` column to absorb extra horizontal space.
|
|
60
60
|
|
|
61
|
+
## Date columns: display and filter
|
|
62
|
+
|
|
63
|
+
`date`, `time` and `date-time` cells are read the same way for display and for the date filter,
|
|
64
|
+
in the column's display zone — `formatOptions.timeZone` when set, otherwise the viewer's local
|
|
65
|
+
zone:
|
|
66
|
+
|
|
67
|
+
- `'2024-03-15'` (and a `CalendarDate`) is a calendar day. It always shows and filters as
|
|
68
|
+
15 March, also in zones west of UTC — it is never turned into a midnight-UTC instant.
|
|
69
|
+
- `Date`, epoch milliseconds, strings with an offset (`Z`, `+02:00`, DB `+02`) and
|
|
70
|
+
`ZonedDateTime` are instants, shown and filtered on the day they fall on in the display zone.
|
|
71
|
+
- A datetime without an offset (`'2024-03-15T10:30'`) is wall-clock time in the display zone.
|
|
72
|
+
- Unreadable values (e.g. `'2024-02-30'`) render as `-` and never match an active filter.
|
|
73
|
+
|
|
74
|
+
`date` and `date-time` columns filter by calendar-day range. The filter value is
|
|
75
|
+
`[from?, to?]`, each `YYYY-MM-DD` or empty (open-ended); both days are inclusive, a reversed
|
|
76
|
+
range is swapped, and an empty range removes the filter. Instants are judged by their calendar
|
|
77
|
+
day, so 23- and 25-hour DST days need no special handling. `time` columns keep the text filter.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
table.getColumn('createdAt')?.setFilterValue(['2024-03-01', '2024-03-31']); // March
|
|
81
|
+
table.getColumn('createdAt')?.setFilterValue([undefined, '2024-03-31']); // up to 31 March
|
|
82
|
+
```
|
|
83
|
+
|
|
61
84
|
## Select columns with multiple values
|
|
62
85
|
|
|
63
86
|
A `type: 'select'` column may return an array (tags, multi-select attributes). The Funnel then
|
|
@@ -134,6 +157,8 @@ booleans and object-valued columns are excluded from global search. Also availab
|
|
|
134
157
|
|
|
135
158
|
`table: DataTableInstance<T>` (required), `caption?: string` (sr-only `<caption>`),
|
|
136
159
|
`emptyMessage?: string` (default `'No rows found'`), `class?: ClassValue`,
|
|
160
|
+
`headerFilters?: boolean` (default `false`; funnel filter popups in column headers — see
|
|
161
|
+
"Filter from column headers" in SKILL.md),
|
|
137
162
|
`onRowClick?: (details: { row: T; event: MouseEvent }) => void`,
|
|
138
163
|
`onRowDoubleClick?: (details: { row: T; event: MouseEvent }) => void` — `row` is `row.original`.
|
|
139
164
|
Clicks on the row-selection checkbox and url-cell buttons do **not** trigger either handler.
|
|
@@ -29,14 +29,19 @@ const filters = $derived(table.store.state.columnFilters);
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Change filters from app code with the table API — `column.setFilterValue(...)`,
|
|
32
|
-
`table.setColumnFilters(...)`, `table.resetColumnFilters()`. `DataTable.ColumnFilter`
|
|
33
|
-
live filter value in
|
|
34
|
-
external change clears or updates the inputs, and editing one
|
|
35
|
-
bound's current value. An edit still inside the debounce is dropped
|
|
36
|
-
changes from outside — even if the filter ends on its old value again
|
|
37
|
-
filters restored), since `createTable` keeps a per-column filter revision
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
`table.setColumnFilters(...)`, `table.resetColumnFilters()`. `DataTable.ColumnFilter` and the
|
|
33
|
+
`headerFilters` popups show the live filter value in their inputs (they only hold edits still
|
|
34
|
+
inside the 300ms debounce), so an external change clears or updates the inputs, and editing one
|
|
35
|
+
range bound keeps the other bound's current value. An edit still inside the debounce is dropped
|
|
36
|
+
when its column's filter changes from outside — even if the filter ends on its old value again
|
|
37
|
+
(e.g. reset, then saved filters restored), since `createTable` keeps a per-column filter revision
|
|
38
|
+
that only goes up.
|
|
39
|
+
|
|
40
|
+
Pending edits belong to the table, not to the control they were typed in: the latest edit to a
|
|
41
|
+
column wins whether it came from a header popup or the toolbar, and it still commits after its
|
|
42
|
+
popup closes. `table.resetColumnFilters()` and `table.reset()` cancel every pending edit — also
|
|
43
|
+
one typed into a column that had no filter yet, which a reset would otherwise leave untouched —
|
|
44
|
+
and so does destroying the component that created the table.
|
|
40
45
|
|
|
41
46
|
```ts
|
|
42
47
|
// e.g. drop every filter on columns that belong to the previous category
|