compote-ui 0.80.0 → 0.81.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/components/data-table-v9/column-filter-kind.d.ts +10 -1
  2. package/dist/components/data-table-v9/column-filter-kind.js +7 -0
  3. package/dist/components/data-table-v9/data-table-head.svelte +230 -230
  4. package/dist/components/data-table-v9/data-table.svelte +154 -154
  5. package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte +45 -45
  6. package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte +39 -33
  7. package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte.d.ts +7 -2
  8. package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte +52 -52
  9. package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte +51 -51
  10. package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte +59 -59
  11. package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte +20 -20
  12. package/dist/components/data-table-v9/header/column-header-filter.svelte +66 -66
  13. package/dist/components/data-table-v9/index.d.ts +9 -1
  14. package/dist/components/data-table-v9/index.js +7 -0
  15. package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte +203 -203
  16. package/dist/components/data-table-v9/toolbar/data-table-toolbar.svelte +4 -1
  17. package/dist/components/data-table-v9/types.d.ts +3 -1
  18. package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +82 -82
  19. package/dist/components/data-table-v9/virtual/index.d.ts +2 -2
  20. package/dist/components/data-table-v9/virtual/index.js +1 -1
  21. package/dist/components/dialog/alert-dialog.svelte +73 -73
  22. package/dist/components/dialog/dialog-root.svelte +45 -45
  23. package/dist/components/drawer/drawer-content.svelte +213 -213
  24. package/dist/components/popover/popover-content.svelte +97 -97
  25. package/dist/components/popover/popover-description.svelte +19 -19
  26. package/dist/components/popover/popover-root.svelte +27 -27
  27. package/dist/components/popover/popover-title.svelte +19 -19
  28. package/dist/icons/PhFunnelFill.svelte +19 -19
  29. package/package.json +1 -1
  30. package/skills/component-usage/references/overlays-and-floating-ui.md +278 -278
  31. package/skills/data-table/SKILL.md +362 -314
  32. package/skills/data-table/references/column-options.md +175 -175
  33. package/skills/data-table/references/table-state-and-reactivity.md +65 -65
@@ -1,4 +1,4 @@
1
- import type { DataTableColumnType } from './types';
1
+ import type { DataTableColumnMeta, DataTableColumnType } from './types';
2
2
  /** Which filter editor (and default filterFn) a column type uses. */
3
3
  export type ColumnFilterKind = 'text' | 'range' | 'boolean' | 'select' | 'date';
4
4
  /**
@@ -8,3 +8,12 @@ export type ColumnFilterKind = 'text' | 'range' | 'boolean' | 'select' | 'date';
8
8
  * its own contract (a time-of-day range has to decide how to cross midnight).
9
9
  */
10
10
  export declare function filterKindFor(type: DataTableColumnType | undefined): ColumnFilterKind | null;
11
+ /**
12
+ * The filter kind of a table column (from its `type`): the editor `ColumnFilterEditor` shows
13
+ * for it and the filter value shape its default filterFn expects.
14
+ */
15
+ export declare function getColumnFilterKind(column: {
16
+ columnDef: {
17
+ meta?: DataTableColumnMeta;
18
+ };
19
+ }): ColumnFilterKind | null;
@@ -23,3 +23,10 @@ export function filterKindFor(type) {
23
23
  return 'text';
24
24
  }
25
25
  }
26
+ /**
27
+ * The filter kind of a table column (from its `type`): the editor `ColumnFilterEditor` shows
28
+ * for it and the filter value shape its default filterFn expects.
29
+ */
30
+ export function getColumnFilterKind(column) {
31
+ return filterKindFor(column.columnDef.meta?.type);
32
+ }
@@ -1,230 +1,230 @@
1
- <script lang="ts" generics="T extends RowData">
2
- import type { HeaderGroup, RowData } from '@tanstack/svelte-table';
3
- import { FlexRender } from '@tanstack/svelte-table';
4
- import { cn } from 'tailwind-variants';
5
- import { PhCaretDown, PhCaretUp } from '../../icons';
6
- import type { DataTableInstance } from './data-table-utils';
7
- import type { DataTableViewState } from './table-view-state.svelte';
8
- import type { DataTableFeatures } from './features';
9
- import type { DataTableAlign } from './types';
10
- import ColumnHeaderFilter from './header/column-header-filter.svelte';
11
- import {
12
- alignClass,
13
- getColumnMeta,
14
- getGroupPinningStyle,
15
- getHeaderAriaSort,
16
- getHeaderSortLabel,
17
- getPinningStyle,
18
- joinStyles,
19
- justifyClass,
20
- resizeHandleClass,
21
- resizeHandleStyle,
22
- sortButtonDirectionClass,
23
- virtualColumnSizeStyle,
24
- virtualGroupWithGrowSizeStyle,
25
- virtualGrowColumnSizeStyle,
26
- virtualSelectionColumnSizeStyle
27
- } from './data-table-utils';
28
-
29
- type Props = {
30
- table: DataTableInstance<T>;
31
- view: DataTableViewState<T>;
32
- isVirtual?: boolean;
33
- hasGrowColumn?: boolean;
34
- /** Show a filter funnel in the header of every filterable column. */
35
- headerFilters?: boolean;
36
- };
37
-
38
- let {
39
- table,
40
- view,
41
- isVirtual = false,
42
- hasGrowColumn = false,
43
- headerFilters = false
44
- }: Props = $props();
45
-
46
- type Header = HeaderGroup<DataTableFeatures, T>['headers'][number];
47
-
48
- function findGrowLeafHeader(header: Header): Header | undefined {
49
- if (header.subHeaders.length === 0) {
50
- return getColumnMeta(header.column.columnDef)?.grow ? header : undefined;
51
- }
52
- for (const sub of header.subHeaders) {
53
- const found = findGrowLeafHeader(sub);
54
- if (found) return found;
55
- }
56
- return undefined;
57
- }
58
-
59
- function showHeaderFilter(header: Header) {
60
- return headerFilters && header.subHeaders.length === 0 && header.column.getCanFilter();
61
- }
62
-
63
- /**
64
- * Padding that keeps an active (always visible) filter funnel off the label, so it never
65
- * hides the sort indicator. The funnel sits on the inner edge opposite the label.
66
- */
67
- function headerFilterPaddingClass(header: Header, align: DataTableAlign | undefined) {
68
- if (!showHeaderFilter(header)) return undefined;
69
- const filtered = view.columnFilters.some((filter) => filter.id === header.column.id);
70
- if (!filtered) return undefined;
71
- return align === 'right' ? 'pl-6' : 'pr-6';
72
- }
73
-
74
- function headerSortDirection(header: Header) {
75
- void view.sorting;
76
- return header.column.getIsSorted();
77
- }
78
-
79
- function headerCellStyle(header: Header) {
80
- const canPinHeader = header.subHeaders.length === 0;
81
-
82
- let virtualSizeStyle: string | undefined;
83
- if (isVirtual) {
84
- void view.columnSizing;
85
- const isGrowLeaf = canPinHeader && getColumnMeta(header.column.columnDef)?.grow;
86
- if (isGrowLeaf) {
87
- virtualSizeStyle = virtualGrowColumnSizeStyle();
88
- } else {
89
- const growLeaf = findGrowLeafHeader(header);
90
- virtualSizeStyle = growLeaf
91
- ? virtualGroupWithGrowSizeStyle(header.getSize() - growLeaf.getSize())
92
- : virtualColumnSizeStyle(header.getSize());
93
- }
94
- }
95
-
96
- return joinStyles(
97
- virtualSizeStyle,
98
- canPinHeader
99
- ? getPinningStyle(header.column, table, view, true, view.isRowSelectionEnabled)
100
- : getGroupPinningStyle(
101
- header,
102
- view.getHeaderSection(header),
103
- view,
104
- view.isRowSelectionEnabled
105
- )
106
- );
107
- }
108
-
109
- function selectionHeaderStyle() {
110
- return isVirtual
111
- ? joinStyles(
112
- virtualSelectionColumnSizeStyle(),
113
- 'min-width: 40px',
114
- 'max-width: 40px',
115
- 'position: sticky',
116
- 'left: 0',
117
- 'z-index: 30'
118
- )
119
- : 'width: 40px; min-width: 40px; max-width: 40px; position: sticky; left: 0; z-index: 15';
120
- }
121
-
122
- function headerRowStyle() {
123
- if (!isVirtual) return undefined;
124
- return joinStyles('display: flex', 'width: 100%');
125
- }
126
-
127
- function shouldRenderSelectionCheckbox(headerGroupIndex: number) {
128
- if (!view.isMultiRowSelectionEnabled) return false;
129
- return headerGroupIndex === view.headerGroups.length - 1;
130
- }
131
- </script>
132
-
133
- <thead
134
- class="sticky top-0 z-20 bg-surface-2 text-left text-ink-dim"
135
- style={isVirtual ? 'display: grid; position: sticky; top: 0; z-index: 20' : undefined}
136
- >
137
- {#each view.headerGroups as headerGroup, headerGroupIndex (`${headerGroup.id}:${headerGroup.headers.map((header) => `${header.id}:${header.colSpan}`).join('|')}`)}
138
- {@const visibleHeaders = headerGroup.headers.filter((header) => header.colSpan > 0)}
139
- <tr class="h-9" style={headerRowStyle()}>
140
- {#if view.isRowSelectionEnabled}
141
- <th
142
- scope="col"
143
- class={cn(
144
- 'h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 text-center align-middle leading-5 font-medium',
145
- isVirtual && 'items-center justify-center'
146
- )}
147
- style={selectionHeaderStyle()}
148
- >
149
- {#if shouldRenderSelectionCheckbox(headerGroupIndex)}
150
- <input
151
- type="checkbox"
152
- aria-label="Select all rows"
153
- class="table-checkbox mx-auto block size-4"
154
- checked={view.allRowsSelectionState === true}
155
- indeterminate={view.allRowsSelectionState === 'indeterminate'}
156
- onchange={(e) => table.toggleAllRowsSelected(e.currentTarget.checked)}
157
- />
158
- {/if}
159
- </th>
160
- {/if}
161
- {#each visibleHeaders as header, headerIndex (`${header.id}:${view.getHeaderSection(header) ?? ''}:${header.colSpan}:${header.column.getIsVisible()}`)}
162
- {@const meta = getColumnMeta(header.column.columnDef)}
163
- {@const sortDirection = headerSortDirection(header)}
164
- {@const continuesInNextFragment =
165
- visibleHeaders[headerIndex + 1]?.column.id === header.column.id}
166
- <th
167
- scope="col"
168
- class={cn(
169
- 'group/th relative h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 align-middle leading-5 font-medium',
170
- isVirtual && 'items-center',
171
- isVirtual && justifyClass(meta?.align),
172
- alignClass(meta?.align),
173
- headerFilterPaddingClass(header, meta?.align)
174
- )}
175
- colspan={header.colSpan}
176
- aria-sort={header.column.getCanSort() ? getHeaderAriaSort(sortDirection) : undefined}
177
- style={headerCellStyle(header)}
178
- >
179
- {#if !header.isPlaceholder}
180
- {#if header.column.getCanSort()}
181
- <button
182
- type="button"
183
- class={cn(
184
- 'inline-flex max-w-full appearance-none items-center gap-1 rounded-sm border-0 bg-transparent p-0 align-middle text-sm leading-5 text-inherit outline-none hover:text-ink data-focus-visible:outline-2 data-focus-visible:outline-offset-2 data-focus-visible:outline-ring',
185
- justifyClass(meta?.align),
186
- sortButtonDirectionClass(meta?.align)
187
- )}
188
- aria-label={`${getHeaderSortLabel(sortDirection)}. Toggle sorting.`}
189
- onclick={header.column.getToggleSortingHandler()}
190
- >
191
- <span class="min-w-0 truncate">
192
- <FlexRender {header} />
193
- </span>
194
- <span
195
- class="inline-flex size-3.5 shrink-0 items-center justify-center text-ink-dim"
196
- >
197
- {#if sortDirection === 'asc'}
198
- <PhCaretUp class="size-3.5" />
199
- {:else if sortDirection === 'desc'}
200
- <PhCaretDown class="size-3.5" />
201
- {/if}
202
- </span>
203
- </button>
204
- {:else}
205
- <FlexRender {header} />
206
- {/if}
207
- {/if}
208
- {#if !header.isPlaceholder && showHeaderFilter(header)}
209
- <ColumnHeaderFilter {table} column={header.column} />
210
- {/if}
211
- <!-- A group split by pinning renders as adjacent fragments; skip the
212
- divider between them so the group reads as one continuous header. -->
213
- {#if header.column.getCanResize() && !continuesInNextFragment}
214
- <div
215
- aria-hidden="true"
216
- class={resizeHandleClass(headerIndex, visibleHeaders.length)}
217
- style={resizeHandleStyle(table, header, view.columnResizing)}
218
- ondblclick={() => header.column.resetSize()}
219
- onmousedown={header.getResizeHandler()}
220
- ontouchstart={header.getResizeHandler()}
221
- ></div>
222
- {/if}
223
- </th>
224
- {/each}
225
- {#if !isVirtual && !hasGrowColumn}
226
- <th aria-hidden="true" class="h-9 border-b border-surface-3 bg-surface-2 p-0"></th>
227
- {/if}
228
- </tr>
229
- {/each}
230
- </thead>
1
+ <script lang="ts" generics="T extends RowData">
2
+ import type { HeaderGroup, RowData } from '@tanstack/svelte-table';
3
+ import { FlexRender } from '@tanstack/svelte-table';
4
+ import { cn } from 'tailwind-variants';
5
+ import { PhCaretDown, PhCaretUp } from '../../icons';
6
+ import type { DataTableInstance } from './data-table-utils';
7
+ import type { DataTableViewState } from './table-view-state.svelte';
8
+ import type { DataTableFeatures } from './features';
9
+ import type { DataTableAlign } from './types';
10
+ import ColumnHeaderFilter from './header/column-header-filter.svelte';
11
+ import {
12
+ alignClass,
13
+ getColumnMeta,
14
+ getGroupPinningStyle,
15
+ getHeaderAriaSort,
16
+ getHeaderSortLabel,
17
+ getPinningStyle,
18
+ joinStyles,
19
+ justifyClass,
20
+ resizeHandleClass,
21
+ resizeHandleStyle,
22
+ sortButtonDirectionClass,
23
+ virtualColumnSizeStyle,
24
+ virtualGroupWithGrowSizeStyle,
25
+ virtualGrowColumnSizeStyle,
26
+ virtualSelectionColumnSizeStyle
27
+ } from './data-table-utils';
28
+
29
+ type Props = {
30
+ table: DataTableInstance<T>;
31
+ view: DataTableViewState<T>;
32
+ isVirtual?: boolean;
33
+ hasGrowColumn?: boolean;
34
+ /** Show a filter funnel in the header of every filterable column. */
35
+ headerFilters?: boolean;
36
+ };
37
+
38
+ let {
39
+ table,
40
+ view,
41
+ isVirtual = false,
42
+ hasGrowColumn = false,
43
+ headerFilters = false
44
+ }: Props = $props();
45
+
46
+ type Header = HeaderGroup<DataTableFeatures, T>['headers'][number];
47
+
48
+ function findGrowLeafHeader(header: Header): Header | undefined {
49
+ if (header.subHeaders.length === 0) {
50
+ return getColumnMeta(header.column.columnDef)?.grow ? header : undefined;
51
+ }
52
+ for (const sub of header.subHeaders) {
53
+ const found = findGrowLeafHeader(sub);
54
+ if (found) return found;
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ function showHeaderFilter(header: Header) {
60
+ return headerFilters && header.subHeaders.length === 0 && header.column.getCanFilter();
61
+ }
62
+
63
+ /**
64
+ * Padding that keeps an active (always visible) filter funnel off the label, so it never
65
+ * hides the sort indicator. The funnel sits on the inner edge opposite the label.
66
+ */
67
+ function headerFilterPaddingClass(header: Header, align: DataTableAlign | undefined) {
68
+ if (!showHeaderFilter(header)) return undefined;
69
+ const filtered = view.columnFilters.some((filter) => filter.id === header.column.id);
70
+ if (!filtered) return undefined;
71
+ return align === 'right' ? 'pl-6' : 'pr-6';
72
+ }
73
+
74
+ function headerSortDirection(header: Header) {
75
+ void view.sorting;
76
+ return header.column.getIsSorted();
77
+ }
78
+
79
+ function headerCellStyle(header: Header) {
80
+ const canPinHeader = header.subHeaders.length === 0;
81
+
82
+ let virtualSizeStyle: string | undefined;
83
+ if (isVirtual) {
84
+ void view.columnSizing;
85
+ const isGrowLeaf = canPinHeader && getColumnMeta(header.column.columnDef)?.grow;
86
+ if (isGrowLeaf) {
87
+ virtualSizeStyle = virtualGrowColumnSizeStyle();
88
+ } else {
89
+ const growLeaf = findGrowLeafHeader(header);
90
+ virtualSizeStyle = growLeaf
91
+ ? virtualGroupWithGrowSizeStyle(header.getSize() - growLeaf.getSize())
92
+ : virtualColumnSizeStyle(header.getSize());
93
+ }
94
+ }
95
+
96
+ return joinStyles(
97
+ virtualSizeStyle,
98
+ canPinHeader
99
+ ? getPinningStyle(header.column, table, view, true, view.isRowSelectionEnabled)
100
+ : getGroupPinningStyle(
101
+ header,
102
+ view.getHeaderSection(header),
103
+ view,
104
+ view.isRowSelectionEnabled
105
+ )
106
+ );
107
+ }
108
+
109
+ function selectionHeaderStyle() {
110
+ return isVirtual
111
+ ? joinStyles(
112
+ virtualSelectionColumnSizeStyle(),
113
+ 'min-width: 40px',
114
+ 'max-width: 40px',
115
+ 'position: sticky',
116
+ 'left: 0',
117
+ 'z-index: 30'
118
+ )
119
+ : 'width: 40px; min-width: 40px; max-width: 40px; position: sticky; left: 0; z-index: 15';
120
+ }
121
+
122
+ function headerRowStyle() {
123
+ if (!isVirtual) return undefined;
124
+ return joinStyles('display: flex', 'width: 100%');
125
+ }
126
+
127
+ function shouldRenderSelectionCheckbox(headerGroupIndex: number) {
128
+ if (!view.isMultiRowSelectionEnabled) return false;
129
+ return headerGroupIndex === view.headerGroups.length - 1;
130
+ }
131
+ </script>
132
+
133
+ <thead
134
+ class="sticky top-0 z-20 bg-surface-2 text-left text-ink-dim"
135
+ style={isVirtual ? 'display: grid; position: sticky; top: 0; z-index: 20' : undefined}
136
+ >
137
+ {#each view.headerGroups as headerGroup, headerGroupIndex (`${headerGroup.id}:${headerGroup.headers.map((header) => `${header.id}:${header.colSpan}`).join('|')}`)}
138
+ {@const visibleHeaders = headerGroup.headers.filter((header) => header.colSpan > 0)}
139
+ <tr class="h-9" style={headerRowStyle()}>
140
+ {#if view.isRowSelectionEnabled}
141
+ <th
142
+ scope="col"
143
+ class={cn(
144
+ 'h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 text-center align-middle leading-5 font-medium',
145
+ isVirtual && 'items-center justify-center'
146
+ )}
147
+ style={selectionHeaderStyle()}
148
+ >
149
+ {#if shouldRenderSelectionCheckbox(headerGroupIndex)}
150
+ <input
151
+ type="checkbox"
152
+ aria-label="Select all rows"
153
+ class="table-checkbox mx-auto block size-4"
154
+ checked={view.allRowsSelectionState === true}
155
+ indeterminate={view.allRowsSelectionState === 'indeterminate'}
156
+ onchange={(e) => table.toggleAllRowsSelected(e.currentTarget.checked)}
157
+ />
158
+ {/if}
159
+ </th>
160
+ {/if}
161
+ {#each visibleHeaders as header, headerIndex (`${header.id}:${view.getHeaderSection(header) ?? ''}:${header.colSpan}:${header.column.getIsVisible()}`)}
162
+ {@const meta = getColumnMeta(header.column.columnDef)}
163
+ {@const sortDirection = headerSortDirection(header)}
164
+ {@const continuesInNextFragment =
165
+ visibleHeaders[headerIndex + 1]?.column.id === header.column.id}
166
+ <th
167
+ scope="col"
168
+ class={cn(
169
+ 'group/th relative h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 align-middle leading-5 font-medium',
170
+ isVirtual && 'items-center',
171
+ isVirtual && justifyClass(meta?.align),
172
+ alignClass(meta?.align),
173
+ headerFilterPaddingClass(header, meta?.align)
174
+ )}
175
+ colspan={header.colSpan}
176
+ aria-sort={header.column.getCanSort() ? getHeaderAriaSort(sortDirection) : undefined}
177
+ style={headerCellStyle(header)}
178
+ >
179
+ {#if !header.isPlaceholder}
180
+ {#if header.column.getCanSort()}
181
+ <button
182
+ type="button"
183
+ class={cn(
184
+ 'inline-flex max-w-full appearance-none items-center gap-1 rounded-sm border-0 bg-transparent p-0 align-middle text-sm leading-5 text-inherit outline-none hover:text-ink data-focus-visible:outline-2 data-focus-visible:outline-offset-2 data-focus-visible:outline-ring',
185
+ justifyClass(meta?.align),
186
+ sortButtonDirectionClass(meta?.align)
187
+ )}
188
+ aria-label={`${getHeaderSortLabel(sortDirection)}. Toggle sorting.`}
189
+ onclick={header.column.getToggleSortingHandler()}
190
+ >
191
+ <span class="min-w-0 truncate">
192
+ <FlexRender {header} />
193
+ </span>
194
+ <span
195
+ class="inline-flex size-3.5 shrink-0 items-center justify-center text-ink-dim"
196
+ >
197
+ {#if sortDirection === 'asc'}
198
+ <PhCaretUp class="size-3.5" />
199
+ {:else if sortDirection === 'desc'}
200
+ <PhCaretDown class="size-3.5" />
201
+ {/if}
202
+ </span>
203
+ </button>
204
+ {:else}
205
+ <FlexRender {header} />
206
+ {/if}
207
+ {/if}
208
+ {#if !header.isPlaceholder && showHeaderFilter(header)}
209
+ <ColumnHeaderFilter {table} column={header.column} />
210
+ {/if}
211
+ <!-- A group split by pinning renders as adjacent fragments; skip the
212
+ divider between them so the group reads as one continuous header. -->
213
+ {#if header.column.getCanResize() && !continuesInNextFragment}
214
+ <div
215
+ aria-hidden="true"
216
+ class={resizeHandleClass(headerIndex, visibleHeaders.length)}
217
+ style={resizeHandleStyle(table, header, view.columnResizing)}
218
+ ondblclick={() => header.column.resetSize()}
219
+ onmousedown={header.getResizeHandler()}
220
+ ontouchstart={header.getResizeHandler()}
221
+ ></div>
222
+ {/if}
223
+ </th>
224
+ {/each}
225
+ {#if !isVirtual && !hasGrowColumn}
226
+ <th aria-hidden="true" class="h-9 border-b border-surface-3 bg-surface-2 p-0"></th>
227
+ {/if}
228
+ </tr>
229
+ {/each}
230
+ </thead>