compote-ui 0.80.0 → 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.
Files changed (32) 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/types.d.ts +3 -1
  17. package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +82 -82
  18. package/dist/components/data-table-v9/virtual/index.d.ts +2 -2
  19. package/dist/components/data-table-v9/virtual/index.js +1 -1
  20. package/dist/components/dialog/alert-dialog.svelte +73 -73
  21. package/dist/components/dialog/dialog-root.svelte +45 -45
  22. package/dist/components/drawer/drawer-content.svelte +213 -213
  23. package/dist/components/popover/popover-content.svelte +97 -97
  24. package/dist/components/popover/popover-description.svelte +19 -19
  25. package/dist/components/popover/popover-root.svelte +27 -27
  26. package/dist/components/popover/popover-title.svelte +19 -19
  27. package/dist/icons/PhFunnelFill.svelte +19 -19
  28. package/package.json +1 -1
  29. package/skills/component-usage/references/overlays-and-floating-ui.md +278 -278
  30. package/skills/data-table/SKILL.md +362 -314
  31. package/skills/data-table/references/column-options.md +175 -175
  32. package/skills/data-table/references/table-state-and-reactivity.md +65 -65
@@ -1,154 +1,154 @@
1
- <script lang="ts" generics="T extends RowData">
2
- import type { RowData } from '@tanstack/svelte-table';
3
- import type { HTMLAttributes } from 'svelte/elements';
4
- import { cn, type ClassValue } from 'tailwind-variants';
5
- import type { DataTableInstance } from './data-table-utils';
6
- import { createTableViewState } from './table-view-state.svelte';
7
- import DataTableHead from './data-table-head.svelte';
8
- import DataTableFoot from './data-table-foot.svelte';
9
- import DataTableCellContent from './data-table-cell-content.svelte';
10
- import {
11
- alignClass,
12
- columnSizeStyle,
13
- getColumnMeta,
14
- getPinningStyle,
15
- getRowCells,
16
- selectionColumnSizeStyle,
17
- tableSizeStyle
18
- } from './data-table-utils';
19
-
20
- type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
21
- table: DataTableInstance<T>;
22
- caption?: string;
23
- emptyMessage?: string;
24
- class?: ClassValue;
25
- /** Show a filter funnel in the header of every filterable column. */
26
- headerFilters?: boolean;
27
- onRowClick?: (details: { row: T; event: MouseEvent }) => void;
28
- onRowDoubleClick?: (details: { row: T; event: MouseEvent }) => void;
29
- };
30
-
31
- let {
32
- table,
33
- caption,
34
- emptyMessage = 'No rows found',
35
- class: className,
36
- headerFilters = false,
37
- onRowClick,
38
- onRowDoubleClick,
39
- ...rest
40
- }: Props = $props();
41
-
42
- const view = createTableViewState(() => table);
43
-
44
- const visibleColumnCount = $derived(view.visibleLeafColumns.length);
45
- const tableColumnCount = $derived(visibleColumnCount + (view.isRowSelectionEnabled ? 1 : 0));
46
- const renderedColumnCount = $derived(tableColumnCount + 1);
47
- </script>
48
-
49
- <div
50
- class={cn(
51
- 'flex max-h-full min-h-0 flex-col overflow-hidden rounded-lg border border-surface-3 bg-surface-1',
52
- className
53
- )}
54
- {...rest}
55
- >
56
- {#if view.isColumnResizing}
57
- <div aria-hidden="true" class="fixed inset-0 z-50 cursor-col-resize select-none"></div>
58
- {/if}
59
-
60
- <div class="min-h-0 flex-1 overflow-auto">
61
- <table
62
- class="table-fixed border-separate border-spacing-0 text-sm"
63
- style={tableSizeStyle(table, view.isRowSelectionEnabled, view)}
64
- >
65
- <colgroup>
66
- {#if view.isRowSelectionEnabled}
67
- <col style={selectionColumnSizeStyle()} />
68
- {/if}
69
- {#each view.visibleLeafColumns as column (column.id)}
70
- <col
71
- style={getColumnMeta(column.columnDef)?.grow
72
- ? undefined
73
- : columnSizeStyle(column.getSize())}
74
- />
75
- {/each}
76
- {#if !view.hasGrowColumn}
77
- <col />
78
- {/if}
79
- </colgroup>
80
- {#if caption}
81
- <caption class="sr-only">{caption}</caption>
82
- {/if}
83
- <DataTableHead {table} {view} hasGrowColumn={view.hasGrowColumn} {headerFilters} />
84
- <tbody>
85
- {#each view.rowModel.rows as row (row.id)}
86
- {@const rowSelected = view.rowSelection[row.id] === true}
87
- <tr
88
- class={cn(
89
- 'group/row',
90
- '[--row-bg:var(--compote-surface-1)]',
91
- 'hover:bg-well/60 hover:[--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]',
92
- rowSelected &&
93
- 'bg-well/60 [--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]'
94
- )}
95
- onclick={(event) => onRowClick?.({ row: row.original, event })}
96
- ondblclick={(event) => onRowDoubleClick?.({ row: row.original, event })}
97
- >
98
- {#if view.isRowSelectionEnabled}
99
- <td
100
- class="border-b border-surface-2 bg-(--row-bg) px-3 py-2 text-center align-middle group-last/row:border-b-0"
101
- style="position: sticky; left: 0; z-index: 1"
102
- onclick={(event) => event.stopPropagation()}
103
- ondblclick={(event) => event.stopPropagation()}
104
- >
105
- <input
106
- type="checkbox"
107
- aria-label="Select row"
108
- class="table-checkbox mx-auto block size-4"
109
- checked={rowSelected}
110
- disabled={!row.getCanSelect()}
111
- onchange={(e) => row.toggleSelected(e.currentTarget.checked)}
112
- />
113
- </td>
114
- {/if}
115
- {#each getRowCells(row, view) as cell (cell.id)}
116
- {@const meta = getColumnMeta(cell.column.columnDef)}
117
- <td
118
- class={cn(
119
- 'h-9 truncate border-b border-b-surface-2 px-3 py-0 align-middle group-last/row:border-b-0',
120
- alignClass(meta?.align),
121
- cell.column.getIsPinned() && 'bg-(--row-bg)'
122
- )}
123
- style={getPinningStyle(cell.column, table, view, false, view.isRowSelectionEnabled)}
124
- >
125
- <DataTableCellContent {cell} />
126
- </td>
127
- {/each}
128
- {#if !view.hasGrowColumn}
129
- <td aria-hidden="true" class="border-b border-surface-2 p-0 group-last/row:border-b-0"
130
- ></td>
131
- {/if}
132
- </tr>
133
- {:else}
134
- <tr>
135
- <td class="px-3 py-10 text-center text-sm text-ink-dim" colspan={renderedColumnCount}>
136
- {emptyMessage}
137
- </td>
138
- </tr>
139
- {/each}
140
- </tbody>
141
- {#if view.hasFooter}
142
- <DataTableFoot {table} {view} hasGrowColumn={view.hasGrowColumn} />
143
- {/if}
144
- </table>
145
- </div>
146
-
147
- <div class="shrink-0 border-t border-surface-3 bg-surface-2 px-3 py-2 text-sm text-ink-dim">
148
- {#if view.isRowSelectionEnabled}
149
- {view.selectedRowCount} of {view.rowModel.rows.length} rows selected
150
- {:else}
151
- {view.rowModel.rows.length} rows
152
- {/if}
153
- </div>
154
- </div>
1
+ <script lang="ts" generics="T extends RowData">
2
+ import type { RowData } from '@tanstack/svelte-table';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn, type ClassValue } from 'tailwind-variants';
5
+ import type { DataTableInstance } from './data-table-utils';
6
+ import { createTableViewState } from './table-view-state.svelte';
7
+ import DataTableHead from './data-table-head.svelte';
8
+ import DataTableFoot from './data-table-foot.svelte';
9
+ import DataTableCellContent from './data-table-cell-content.svelte';
10
+ import {
11
+ alignClass,
12
+ columnSizeStyle,
13
+ getColumnMeta,
14
+ getPinningStyle,
15
+ getRowCells,
16
+ selectionColumnSizeStyle,
17
+ tableSizeStyle
18
+ } from './data-table-utils';
19
+
20
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
21
+ table: DataTableInstance<T>;
22
+ caption?: string;
23
+ emptyMessage?: string;
24
+ class?: ClassValue;
25
+ /** Show a filter funnel in the header of every filterable column. */
26
+ headerFilters?: boolean;
27
+ onRowClick?: (details: { row: T; event: MouseEvent }) => void;
28
+ onRowDoubleClick?: (details: { row: T; event: MouseEvent }) => void;
29
+ };
30
+
31
+ let {
32
+ table,
33
+ caption,
34
+ emptyMessage = 'No rows found',
35
+ class: className,
36
+ headerFilters = false,
37
+ onRowClick,
38
+ onRowDoubleClick,
39
+ ...rest
40
+ }: Props = $props();
41
+
42
+ const view = createTableViewState(() => table);
43
+
44
+ const visibleColumnCount = $derived(view.visibleLeafColumns.length);
45
+ const tableColumnCount = $derived(visibleColumnCount + (view.isRowSelectionEnabled ? 1 : 0));
46
+ const renderedColumnCount = $derived(tableColumnCount + 1);
47
+ </script>
48
+
49
+ <div
50
+ class={cn(
51
+ 'flex max-h-full min-h-0 flex-col overflow-hidden rounded-lg border border-surface-3 bg-surface-1',
52
+ className
53
+ )}
54
+ {...rest}
55
+ >
56
+ {#if view.isColumnResizing}
57
+ <div aria-hidden="true" class="fixed inset-0 z-50 cursor-col-resize select-none"></div>
58
+ {/if}
59
+
60
+ <div class="min-h-0 flex-1 overflow-auto">
61
+ <table
62
+ class="table-fixed border-separate border-spacing-0 text-sm"
63
+ style={tableSizeStyle(table, view.isRowSelectionEnabled, view)}
64
+ >
65
+ <colgroup>
66
+ {#if view.isRowSelectionEnabled}
67
+ <col style={selectionColumnSizeStyle()} />
68
+ {/if}
69
+ {#each view.visibleLeafColumns as column (column.id)}
70
+ <col
71
+ style={getColumnMeta(column.columnDef)?.grow
72
+ ? undefined
73
+ : columnSizeStyle(column.getSize())}
74
+ />
75
+ {/each}
76
+ {#if !view.hasGrowColumn}
77
+ <col />
78
+ {/if}
79
+ </colgroup>
80
+ {#if caption}
81
+ <caption class="sr-only">{caption}</caption>
82
+ {/if}
83
+ <DataTableHead {table} {view} hasGrowColumn={view.hasGrowColumn} {headerFilters} />
84
+ <tbody>
85
+ {#each view.rowModel.rows as row (row.id)}
86
+ {@const rowSelected = view.rowSelection[row.id] === true}
87
+ <tr
88
+ class={cn(
89
+ 'group/row',
90
+ '[--row-bg:var(--compote-surface-1)]',
91
+ 'hover:bg-well/60 hover:[--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]',
92
+ rowSelected &&
93
+ 'bg-well/60 [--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]'
94
+ )}
95
+ onclick={(event) => onRowClick?.({ row: row.original, event })}
96
+ ondblclick={(event) => onRowDoubleClick?.({ row: row.original, event })}
97
+ >
98
+ {#if view.isRowSelectionEnabled}
99
+ <td
100
+ class="border-b border-surface-2 bg-(--row-bg) px-3 py-2 text-center align-middle group-last/row:border-b-0"
101
+ style="position: sticky; left: 0; z-index: 1"
102
+ onclick={(event) => event.stopPropagation()}
103
+ ondblclick={(event) => event.stopPropagation()}
104
+ >
105
+ <input
106
+ type="checkbox"
107
+ aria-label="Select row"
108
+ class="table-checkbox mx-auto block size-4"
109
+ checked={rowSelected}
110
+ disabled={!row.getCanSelect()}
111
+ onchange={(e) => row.toggleSelected(e.currentTarget.checked)}
112
+ />
113
+ </td>
114
+ {/if}
115
+ {#each getRowCells(row, view) as cell (cell.id)}
116
+ {@const meta = getColumnMeta(cell.column.columnDef)}
117
+ <td
118
+ class={cn(
119
+ 'h-9 truncate border-b border-b-surface-2 px-3 py-0 align-middle group-last/row:border-b-0',
120
+ alignClass(meta?.align),
121
+ cell.column.getIsPinned() && 'bg-(--row-bg)'
122
+ )}
123
+ style={getPinningStyle(cell.column, table, view, false, view.isRowSelectionEnabled)}
124
+ >
125
+ <DataTableCellContent {cell} />
126
+ </td>
127
+ {/each}
128
+ {#if !view.hasGrowColumn}
129
+ <td aria-hidden="true" class="border-b border-surface-2 p-0 group-last/row:border-b-0"
130
+ ></td>
131
+ {/if}
132
+ </tr>
133
+ {:else}
134
+ <tr>
135
+ <td class="px-3 py-10 text-center text-sm text-ink-dim" colspan={renderedColumnCount}>
136
+ {emptyMessage}
137
+ </td>
138
+ </tr>
139
+ {/each}
140
+ </tbody>
141
+ {#if view.hasFooter}
142
+ <DataTableFoot {table} {view} hasGrowColumn={view.hasGrowColumn} />
143
+ {/if}
144
+ </table>
145
+ </div>
146
+
147
+ <div class="shrink-0 border-t border-surface-3 bg-surface-2 px-3 py-2 text-sm text-ink-dim">
148
+ {#if view.isRowSelectionEnabled}
149
+ {view.selectedRowCount} of {view.rowModel.rows.length} rows selected
150
+ {:else}
151
+ {view.rowModel.rows.length} rows
152
+ {/if}
153
+ </div>
154
+ </div>
@@ -1,45 +1,45 @@
1
- <script lang="ts">
2
- import { cn } from 'tailwind-variants';
3
- import type { FilterableColumn } from '../column-filter-editing.svelte';
4
-
5
- type Props = {
6
- column: FilterableColumn;
7
- };
8
-
9
- let { column }: Props = $props();
10
-
11
- const value = $derived(column.getFilterValue() as boolean | undefined);
12
- </script>
13
-
14
- <div class="flex overflow-hidden rounded border border-border text-xs">
15
- <button
16
- type="button"
17
- onclick={() => column.setFilterValue(undefined)}
18
- class={cn(
19
- 'flex-1 px-2 py-1',
20
- value === undefined ? 'bg-surface-3 font-medium text-ink' : 'text-ink-dim hover:bg-surface-2'
21
- )}
22
- >
23
- All
24
- </button>
25
- <button
26
- type="button"
27
- onclick={() => column.setFilterValue(value === true ? undefined : true)}
28
- class={cn(
29
- 'flex-1 border-x border-border px-2 py-1',
30
- value === true ? 'bg-surface-3 font-medium text-ink' : 'text-ink-dim hover:bg-surface-2'
31
- )}
32
- >
33
- Yes
34
- </button>
35
- <button
36
- type="button"
37
- onclick={() => column.setFilterValue(value === false ? undefined : false)}
38
- class={cn(
39
- 'flex-1 px-2 py-1',
40
- value === false ? 'bg-surface-3 font-medium text-ink' : 'text-ink-dim hover:bg-surface-2'
41
- )}
42
- >
43
- No
44
- </button>
45
- </div>
1
+ <script lang="ts">
2
+ import { cn } from 'tailwind-variants';
3
+ import type { FilterableColumn } from '../column-filter-editing.svelte';
4
+
5
+ type Props = {
6
+ column: FilterableColumn;
7
+ };
8
+
9
+ let { column }: Props = $props();
10
+
11
+ const value = $derived(column.getFilterValue() as boolean | undefined);
12
+ </script>
13
+
14
+ <div class="flex overflow-hidden rounded border border-border text-xs">
15
+ <button
16
+ type="button"
17
+ onclick={() => column.setFilterValue(undefined)}
18
+ class={cn(
19
+ 'flex-1 px-2 py-1',
20
+ value === undefined ? 'bg-surface-3 font-medium text-ink' : 'text-ink-dim hover:bg-surface-2'
21
+ )}
22
+ >
23
+ All
24
+ </button>
25
+ <button
26
+ type="button"
27
+ onclick={() => column.setFilterValue(value === true ? undefined : true)}
28
+ class={cn(
29
+ 'flex-1 border-x border-border px-2 py-1',
30
+ value === true ? 'bg-surface-3 font-medium text-ink' : 'text-ink-dim hover:bg-surface-2'
31
+ )}
32
+ >
33
+ Yes
34
+ </button>
35
+ <button
36
+ type="button"
37
+ onclick={() => column.setFilterValue(value === false ? undefined : false)}
38
+ class={cn(
39
+ 'flex-1 px-2 py-1',
40
+ value === false ? 'bg-surface-3 font-medium text-ink' : 'text-ink-dim hover:bg-surface-2'
41
+ )}
42
+ >
43
+ No
44
+ </button>
45
+ </div>
@@ -1,33 +1,39 @@
1
- <script lang="ts" generics="T extends RowData">
2
- import type { Column, RowData } from '@tanstack/svelte-table';
3
- import type { DataTableFeatures } from '../features';
4
- import { getColumnMeta } from '../data-table-utils';
5
- import { filterKindFor } from '../column-filter-kind';
6
- import type { ColumnFilterEditing } from '../column-filter-editing.svelte';
7
- import TextFilterEditor from './text-filter-editor.svelte';
8
- import RangeFilterEditor from './range-filter-editor.svelte';
9
- import BooleanFilterEditor from './boolean-filter-editor.svelte';
10
- import SelectFilterEditor from './select-filter-editor.svelte';
11
- import DateRangeFilterEditor from './date-range-filter-editor.svelte';
12
-
13
- type Props = {
14
- column: Column<DataTableFeatures, T, unknown>;
15
- editing: ColumnFilterEditing;
16
- };
17
-
18
- let { column, editing }: Props = $props();
19
-
20
- const kind = $derived(filterKindFor(getColumnMeta(column.columnDef)?.type));
21
- </script>
22
-
23
- {#if kind === 'range'}
24
- <RangeFilterEditor {column} {editing} />
25
- {:else if kind === 'boolean'}
26
- <BooleanFilterEditor {column} />
27
- {:else if kind === 'select'}
28
- <SelectFilterEditor {column} />
29
- {:else if kind === 'date'}
30
- <DateRangeFilterEditor {column} />
31
- {:else if kind === 'text'}
32
- <TextFilterEditor {column} {editing} />
33
- {/if}
1
+ <script lang="ts" generics="T extends RowData">
2
+ import type { Column, RowData } from '@tanstack/svelte-table';
3
+ import type { DataTableFeatures } from '../features';
4
+ import { getColumnFilterKind } from '../column-filter-kind';
5
+ import { ColumnFilterEditing } from '../column-filter-editing.svelte';
6
+ import TextFilterEditor from './text-filter-editor.svelte';
7
+ import RangeFilterEditor from './range-filter-editor.svelte';
8
+ import BooleanFilterEditor from './boolean-filter-editor.svelte';
9
+ import SelectFilterEditor from './select-filter-editor.svelte';
10
+ import DateRangeFilterEditor from './date-range-filter-editor.svelte';
11
+
12
+ type Props = {
13
+ column: Column<DataTableFeatures, T, unknown>;
14
+ /**
15
+ * Pending (debounced) text and range edits. Hosts that can tear an editor down while
16
+ * it still holds an edit pass their own, with `isActive`; otherwise the editor uses
17
+ * one bound to the column's table.
18
+ */
19
+ editing?: ColumnFilterEditing;
20
+ };
21
+
22
+ let { column, editing: editingProp }: Props = $props();
23
+
24
+ const ownEditing = new ColumnFilterEditing({ table: () => column.table });
25
+ const editing = $derived(editingProp ?? ownEditing);
26
+ const kind = $derived(getColumnFilterKind(column));
27
+ </script>
28
+
29
+ {#if kind === 'range'}
30
+ <RangeFilterEditor {column} {editing} />
31
+ {:else if kind === 'boolean'}
32
+ <BooleanFilterEditor {column} />
33
+ {:else if kind === 'select'}
34
+ <SelectFilterEditor {column} />
35
+ {:else if kind === 'date'}
36
+ <DateRangeFilterEditor {column} />
37
+ {:else if kind === 'text'}
38
+ <TextFilterEditor {column} {editing} />
39
+ {/if}
@@ -1,10 +1,15 @@
1
1
  import type { Column, RowData } from '@tanstack/svelte-table';
2
2
  import type { DataTableFeatures } from '../features';
3
- import type { ColumnFilterEditing } from '../column-filter-editing.svelte';
3
+ import { ColumnFilterEditing } from '../column-filter-editing.svelte';
4
4
  declare function $$render<T extends RowData>(): {
5
5
  props: {
6
6
  column: Column<DataTableFeatures, T, unknown>;
7
- editing: ColumnFilterEditing;
7
+ /**
8
+ * Pending (debounced) text and range edits. Hosts that can tear an editor down while
9
+ * it still holds an edit pass their own, with `isActive`; otherwise the editor uses
10
+ * one bound to the column's table.
11
+ */
12
+ editing?: ColumnFilterEditing;
8
13
  };
9
14
  exports: {};
10
15
  bindings: "";
@@ -1,52 +1,52 @@
1
- <script lang="ts" generics="T extends RowData">
2
- import type { Column, RowData } from '@tanstack/svelte-table';
3
- import DatePicker from '../../date-picker/date-picker.svelte';
4
- import type { DataTableFeatures } from '../features';
5
- import { getColumnMeta } from '../data-table-utils';
6
- import { dateCellTimeZone } from '../date-cell';
7
- import type { DateRangeFilterValue } from '../../../utils/date-filter';
8
-
9
- type Props = {
10
- column: Column<DataTableFeatures, T, unknown>;
11
- };
12
-
13
- let { column }: Props = $props();
14
-
15
- const meta = $derived(getColumnMeta(column.columnDef));
16
- // Pick days in the zone the column is shown and filtered in, so "today" matches the cells.
17
- const timeZone = $derived(
18
- dateCellTimeZone(meta?.formatOptions as Intl.DateTimeFormatOptions | undefined)
19
- );
20
-
21
- function bound(index: 0 | 1): string | null {
22
- const value = column.getFilterValue();
23
- const bound = Array.isArray(value) ? value[index] : undefined;
24
- return typeof bound === 'string' ? bound : null;
25
- }
26
-
27
- // Each picked bound applies at once. The picker emits `YYYY-MM-DD` for a day it accepted
28
- // and null when cleared; a half-typed date never reaches here.
29
- function setBound(index: 0 | 1, next: unknown) {
30
- const value = column.getFilterValue();
31
- const range: DateRangeFilterValue = Array.isArray(value)
32
- ? [value[0] as string | undefined, value[1] as string | undefined]
33
- : [undefined, undefined];
34
- range[index] = typeof next === 'string' && next ? next : undefined;
35
- column.setFilterValue(range[0] || range[1] ? range : undefined);
36
- }
37
- </script>
38
-
39
- <div class="flex flex-col gap-2">
40
- <DatePicker
41
- label="From"
42
- locale={meta?.formatLocale}
43
- {timeZone}
44
- bind:value={() => bound(0), (next) => setBound(0, next)}
45
- />
46
- <DatePicker
47
- label="To"
48
- locale={meta?.formatLocale}
49
- {timeZone}
50
- bind:value={() => bound(1), (next) => setBound(1, next)}
51
- />
52
- </div>
1
+ <script lang="ts" generics="T extends RowData">
2
+ import type { Column, RowData } from '@tanstack/svelte-table';
3
+ import DatePicker from '../../date-picker/date-picker.svelte';
4
+ import type { DataTableFeatures } from '../features';
5
+ import { getColumnMeta } from '../data-table-utils';
6
+ import { dateCellTimeZone } from '../date-cell';
7
+ import type { DateRangeFilterValue } from '../../../utils/date-filter';
8
+
9
+ type Props = {
10
+ column: Column<DataTableFeatures, T, unknown>;
11
+ };
12
+
13
+ let { column }: Props = $props();
14
+
15
+ const meta = $derived(getColumnMeta(column.columnDef));
16
+ // Pick days in the zone the column is shown and filtered in, so "today" matches the cells.
17
+ const timeZone = $derived(
18
+ dateCellTimeZone(meta?.formatOptions as Intl.DateTimeFormatOptions | undefined)
19
+ );
20
+
21
+ function bound(index: 0 | 1): string | null {
22
+ const value = column.getFilterValue();
23
+ const bound = Array.isArray(value) ? value[index] : undefined;
24
+ return typeof bound === 'string' ? bound : null;
25
+ }
26
+
27
+ // Each picked bound applies at once. The picker emits `YYYY-MM-DD` for a day it accepted
28
+ // and null when cleared; a half-typed date never reaches here.
29
+ function setBound(index: 0 | 1, next: unknown) {
30
+ const value = column.getFilterValue();
31
+ const range: DateRangeFilterValue = Array.isArray(value)
32
+ ? [value[0] as string | undefined, value[1] as string | undefined]
33
+ : [undefined, undefined];
34
+ range[index] = typeof next === 'string' && next ? next : undefined;
35
+ column.setFilterValue(range[0] || range[1] ? range : undefined);
36
+ }
37
+ </script>
38
+
39
+ <div class="flex flex-col gap-2">
40
+ <DatePicker
41
+ label="From"
42
+ locale={meta?.formatLocale}
43
+ {timeZone}
44
+ bind:value={() => bound(0), (next) => setBound(0, next)}
45
+ />
46
+ <DatePicker
47
+ label="To"
48
+ locale={meta?.formatLocale}
49
+ {timeZone}
50
+ bind:value={() => bound(1), (next) => setBound(1, next)}
51
+ />
52
+ </div>