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.
- package/dist/components/data-table-v9/column-filter-kind.d.ts +10 -1
- package/dist/components/data-table-v9/column-filter-kind.js +7 -0
- package/dist/components/data-table-v9/data-table-head.svelte +230 -230
- package/dist/components/data-table-v9/data-table.svelte +154 -154
- package/dist/components/data-table-v9/filter-editors/boolean-filter-editor.svelte +45 -45
- package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte +39 -33
- package/dist/components/data-table-v9/filter-editors/column-filter-editor.svelte.d.ts +7 -2
- package/dist/components/data-table-v9/filter-editors/date-range-filter-editor.svelte +52 -52
- package/dist/components/data-table-v9/filter-editors/range-filter-editor.svelte +51 -51
- package/dist/components/data-table-v9/filter-editors/select-filter-editor.svelte +59 -59
- package/dist/components/data-table-v9/filter-editors/text-filter-editor.svelte +20 -20
- package/dist/components/data-table-v9/header/column-header-filter.svelte +66 -66
- 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/toolbar/data-table-column-filter.svelte +203 -203
- package/dist/components/data-table-v9/types.d.ts +3 -1
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +82 -82
- 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 +73 -73
- package/dist/components/dialog/dialog-root.svelte +45 -45
- package/dist/components/drawer/drawer-content.svelte +213 -213
- package/dist/components/popover/popover-content.svelte +97 -97
- package/dist/components/popover/popover-description.svelte +19 -19
- package/dist/components/popover/popover-root.svelte +27 -27
- package/dist/components/popover/popover-title.svelte +19 -19
- package/dist/icons/PhFunnelFill.svelte +19 -19
- package/package.json +1 -1
- package/skills/component-usage/references/overlays-and-floating-ui.md +278 -278
- package/skills/data-table/SKILL.md +362 -314
- package/skills/data-table/references/column-options.md +175 -175
- 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>
|