compote-ui 0.55.3 → 0.56.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-v8/create-table.svelte.js +8 -1
- package/dist/components/data-table-v9/column-helper.d.ts +12 -0
- package/dist/components/data-table-v9/column-helper.js +42 -0
- package/dist/components/data-table-v9/create-table.svelte.d.ts +42 -0
- package/dist/components/data-table-v9/create-table.svelte.js +248 -0
- package/dist/components/data-table-v9/data-table-cell-content.svelte +66 -0
- package/dist/components/data-table-v9/data-table-cell-content.svelte.d.ts +28 -0
- package/dist/components/data-table-v9/data-table-foot.svelte +111 -0
- package/dist/components/data-table-v9/data-table-foot.svelte.d.ts +32 -0
- package/dist/components/{data-table/data-table-head.svelte.md → data-table-v9/data-table-head.svelte} +47 -39
- package/dist/components/data-table-v9/data-table-head.svelte.d.ts +32 -0
- package/dist/components/data-table-v9/data-table-title.svelte.d.ts +10 -0
- package/dist/components/data-table-v9/data-table-utils.d.ts +53 -0
- package/dist/components/data-table-v9/data-table-utils.js +181 -0
- package/dist/components/data-table-v9/data-table.svelte +151 -0
- package/dist/components/data-table-v9/data-table.svelte.d.ts +41 -0
- package/dist/components/data-table-v9/features.d.ts +12 -0
- package/dist/components/data-table-v9/features.js +14 -0
- package/dist/components/data-table-v9/index.d.ts +11 -0
- package/dist/components/data-table-v9/index.js +9 -0
- package/dist/components/data-table-v9/table-view-state.svelte.d.ts +74 -0
- package/dist/components/data-table-v9/table-view-state.svelte.js +182 -0
- package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte +380 -0
- package/dist/components/data-table-v9/toolbar/data-table-column-filter.svelte.d.ts +29 -0
- package/dist/components/data-table-v9/toolbar/data-table-column-visibility.svelte +73 -0
- package/dist/components/data-table-v9/toolbar/data-table-column-visibility.svelte.d.ts +29 -0
- package/dist/components/data-table-v9/toolbar/data-table-search.svelte +58 -0
- package/dist/components/data-table-v9/toolbar/data-table-search.svelte.d.ts +32 -0
- package/dist/components/{data-table/data-table-toolbar.svelte.md → data-table-v9/toolbar/data-table-toolbar.svelte} +14 -15
- package/dist/components/data-table-v9/toolbar/data-table-toolbar.svelte.d.ts +12 -0
- package/dist/components/data-table-v9/types.d.ts +74 -0
- package/dist/components/data-table-v9/types.js +1 -0
- package/dist/components/data-table-v9/virtual/data-table-virtual-rows.svelte +131 -0
- package/dist/components/data-table-v9/virtual/data-table-virtual-rows.svelte.d.ts +40 -0
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte +79 -0
- package/dist/components/data-table-v9/virtual/data-table-virtualized.svelte.d.ts +41 -0
- package/dist/components/data-table-v9/virtual/index.d.ts +3 -0
- package/dist/components/data-table-v9/virtual/index.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/package.json +12 -2
- package/dist/components/data-table/column-helper.ts.md +0 -96
- package/dist/components/data-table/create-table.ts.md +0 -386
- package/dist/components/data-table/data-table-column-filter.svelte.md +0 -249
- package/dist/components/data-table/data-table-column-visibility.svelte.md +0 -74
- package/dist/components/data-table/data-table-new.svelte.md +0 -245
- package/dist/components/data-table/data-table-utils.ts.md +0 -179
- package/dist/components/data-table/data-table-virtual-rows.svelte.md +0 -171
- package/dist/components/data-table/data-table-virtualized.svelte.md +0 -108
- package/dist/components/data-table/data-table.svelte.md +0 -214
- package/dist/components/data-table/index.ts.md +0 -22
- package/dist/components/data-table/types.ts.md +0 -101
- package/dist/components/data-table/virtual/index.ts.md +0 -26
- /package/dist/components/{data-table/data-table-title.svelte.md → data-table-v9/data-table-title.svelte} +0 -0
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends RowData">
|
|
2
|
-
import { onDestroy } from 'svelte';
|
|
3
|
-
import type { CellData, Column, RowData } from '@tanstack/svelte-table';
|
|
4
|
-
import * as Popover from '../popover';
|
|
5
|
-
import * as ScrollArea from '../scroll-area';
|
|
6
|
-
import Checkbox from '../checkbox/checkbox.svelte';
|
|
7
|
-
import { cn } from 'tailwind-variants';
|
|
8
|
-
import type { DataTableFeatures, DataTableInstance } from './create-table';
|
|
9
|
-
import NumberInput from '../number-input/number-input.svelte';
|
|
10
|
-
import * as Field from '../field';
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
table: DataTableInstance<T>;
|
|
14
|
-
triggerLabel?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
let { table, triggerLabel = 'Filters' }: Props = $props();
|
|
18
|
-
|
|
19
|
-
let localText: Record<string, string> = $state({});
|
|
20
|
-
let localNumMin: Record<string, number> = $state({});
|
|
21
|
-
let localNumMax: Record<string, number> = $state({});
|
|
22
|
-
const timers: Record<string, ReturnType<typeof setTimeout>> = {};
|
|
23
|
-
|
|
24
|
-
const columnFilters = $derived(table.store.state.columnFilters);
|
|
25
|
-
const activeCount = $derived(columnFilters.length);
|
|
26
|
-
const filterableColumns = $derived(table.getAllLeafColumns().filter((col) => col.getCanFilter()));
|
|
27
|
-
|
|
28
|
-
onDestroy(() => {
|
|
29
|
-
Object.values(timers).forEach(clearTimeout);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
function getColumnType(column: Column<DataTableFeatures, T, CellData>): string | undefined {
|
|
33
|
-
return (column.columnDef.meta as Record<string, unknown> | undefined)?.type as
|
|
34
|
-
| string
|
|
35
|
-
| undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getColumnLabel(column: Column<DataTableFeatures, T, CellData>): string {
|
|
39
|
-
return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function clearFilters() {
|
|
43
|
-
Object.values(timers).forEach(clearTimeout);
|
|
44
|
-
for (const key of Object.keys(timers)) delete timers[key];
|
|
45
|
-
localText = {};
|
|
46
|
-
localNumMin = {};
|
|
47
|
-
localNumMax = {};
|
|
48
|
-
table.resetColumnFilters();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function handleTextInput(column: Column<DataTableFeatures, T, CellData>, value: string) {
|
|
52
|
-
localText[column.id] = value;
|
|
53
|
-
clearTimeout(timers[column.id]);
|
|
54
|
-
timers[column.id] = setTimeout(() => {
|
|
55
|
-
column.setFilterValue(value || undefined);
|
|
56
|
-
}, 300);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function handleNumericInput(
|
|
60
|
-
column: Column<DataTableFeatures, T, CellData>,
|
|
61
|
-
which: 'min' | 'max',
|
|
62
|
-
value: number | null
|
|
63
|
-
) {
|
|
64
|
-
if (value === null) {
|
|
65
|
-
if (which === 'min') delete localNumMin[column.id];
|
|
66
|
-
else delete localNumMax[column.id];
|
|
67
|
-
} else {
|
|
68
|
-
if (which === 'min') localNumMin[column.id] = value;
|
|
69
|
-
else localNumMax[column.id] = value;
|
|
70
|
-
}
|
|
71
|
-
clearTimeout(timers[`${column.id}_${which}`]);
|
|
72
|
-
timers[`${column.id}_${which}`] = setTimeout(() => {
|
|
73
|
-
const min = localNumMin[column.id];
|
|
74
|
-
const max = localNumMax[column.id];
|
|
75
|
-
column.setFilterValue(min === undefined && max === undefined ? undefined : [min, max]);
|
|
76
|
-
}, 300);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function getSelectValues(column: Column<DataTableFeatures, T, CellData>): string[] {
|
|
80
|
-
return (column.getFilterValue() as string[] | undefined) ?? [];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function handleSelectChange(
|
|
84
|
-
column: Column<DataTableFeatures, T, CellData>,
|
|
85
|
-
value: string,
|
|
86
|
-
checked: boolean
|
|
87
|
-
) {
|
|
88
|
-
const current = getSelectValues(column);
|
|
89
|
-
const next = checked ? [...current, value] : current.filter((v) => v !== value);
|
|
90
|
-
column.setFilterValue(next.length ? next : undefined);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function getFacetedValues(column: Column<DataTableFeatures, T, CellData>): string[] {
|
|
94
|
-
return Array.from(column.getFacetedUniqueValues().keys()).map(String).sort();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function getFacetedMinMax(
|
|
98
|
-
column: Column<DataTableFeatures, T, CellData>
|
|
99
|
-
): [number | undefined, number | undefined] {
|
|
100
|
-
const vals = column.getFacetedMinMaxValues();
|
|
101
|
-
return vals ? [vals[0] as number, vals[1] as number] : [undefined, undefined];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function getColumnFormatOptions(
|
|
105
|
-
column: Column<DataTableFeatures, T, CellData>
|
|
106
|
-
): Intl.NumberFormatOptions | undefined {
|
|
107
|
-
return (column.columnDef.meta as Record<string, unknown> | undefined)?.formatOptions as
|
|
108
|
-
| Intl.NumberFormatOptions
|
|
109
|
-
| undefined;
|
|
110
|
-
}
|
|
111
|
-
</script>
|
|
112
|
-
|
|
113
|
-
<Popover.Root positioning={{ placement: 'bottom-end' }}>
|
|
114
|
-
<Popover.Trigger
|
|
115
|
-
class="flex h-9 cursor-pointer items-center rounded-md border border-surface-3 bg-surface-1 px-3 text-sm font-medium text-ink shadow-sm outline-none hover:bg-surface-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring" >
|
|
116
|
-
{triggerLabel}{#if activeCount > 0}
|
|
117
|
-
({activeCount}){/if}
|
|
118
|
-
</Popover.Trigger>
|
|
119
|
-
|
|
120
|
-
<Popover.Content class="w-72 px-0" showArrow={false}>
|
|
121
|
-
<div class="mb-2 flex items-center justify-between px-4">
|
|
122
|
-
<span class="text-sm font-medium text-ink">Filters</span>
|
|
123
|
-
{#if activeCount > 0}
|
|
124
|
-
<button type="button" onclick={clearFilters} class="text-xs text-primary hover:underline">
|
|
125
|
-
Clear all
|
|
126
|
-
</button>
|
|
127
|
-
{/if}
|
|
128
|
-
</div>
|
|
129
|
-
<div class="mb-2 border-b border-surface-3"></div>
|
|
130
|
-
|
|
131
|
-
<ScrollArea.Root class="h-80">
|
|
132
|
-
<ScrollArea.Viewport>
|
|
133
|
-
<ScrollArea.Content>
|
|
134
|
-
{#each filterableColumns as column (column.id)}
|
|
135
|
-
<div class="mb-3">
|
|
136
|
-
<p class="mb-1 text-xs font-medium text-ink">{getColumnLabel(column)}</p>
|
|
137
|
-
|
|
138
|
-
{#if getColumnType(column) === 'number' || getColumnType(column) === 'currency' || getColumnType(column) === 'percent'}
|
|
139
|
-
{@const [facetMin, facetMax] = getFacetedMinMax(column)}
|
|
140
|
-
{@const colFormatOptions = getColumnFormatOptions(column)}
|
|
141
|
-
<div class="flex gap-1.5">
|
|
142
|
-
<div class="min-w-0 flex-1">
|
|
143
|
-
<p class="mb-1 text-xs text-ink-dim">From</p>
|
|
144
|
-
<NumberInput
|
|
145
|
-
value={localNumMin[column.id] ?? facetMin ?? null}
|
|
146
|
-
min={facetMin}
|
|
147
|
-
max={facetMax}
|
|
148
|
-
formatOptions={colFormatOptions}
|
|
149
|
-
onValueChange={({ valueAsNumber }) =>
|
|
150
|
-
handleNumericInput(
|
|
151
|
-
column,
|
|
152
|
-
'min',
|
|
153
|
-
isNaN(valueAsNumber) ? null : valueAsNumber
|
|
154
|
-
)}
|
|
155
|
-
/>
|
|
156
|
-
</div>
|
|
157
|
-
<div class="min-w-0 flex-1">
|
|
158
|
-
<p class="mb-1 text-xs text-ink-dim">To</p>
|
|
159
|
-
<NumberInput
|
|
160
|
-
value={localNumMax[column.id] ?? facetMax ?? null}
|
|
161
|
-
min={facetMin}
|
|
162
|
-
max={facetMax}
|
|
163
|
-
formatOptions={colFormatOptions}
|
|
164
|
-
onValueChange={({ valueAsNumber }) =>
|
|
165
|
-
handleNumericInput(
|
|
166
|
-
column,
|
|
167
|
-
'max',
|
|
168
|
-
isNaN(valueAsNumber) ? null : valueAsNumber
|
|
169
|
-
)}
|
|
170
|
-
/>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
{:else if getColumnType(column) === 'boolean'}
|
|
174
|
-
{@const boolFilter = column.getFilterValue() as boolean | undefined}
|
|
175
|
-
<div class="flex overflow-hidden rounded border border-border text-xs">
|
|
176
|
-
<button
|
|
177
|
-
type="button"
|
|
178
|
-
onclick={() => column.setFilterValue(undefined)}
|
|
179
|
-
class={cn(
|
|
180
|
-
'flex-1 px-2 py-1',
|
|
181
|
-
boolFilter === undefined
|
|
182
|
-
? 'bg-surface-3 font-medium text-ink'
|
|
183
|
-
: 'text-ink-dim hover:bg-surface-2'
|
|
184
|
-
)}
|
|
185
|
-
>
|
|
186
|
-
All
|
|
187
|
-
</button>
|
|
188
|
-
<button
|
|
189
|
-
type="button"
|
|
190
|
-
onclick={() => column.setFilterValue(boolFilter === true ? undefined : true)}
|
|
191
|
-
class={cn(
|
|
192
|
-
'flex-1 border-x border-border px-2 py-1',
|
|
193
|
-
boolFilter === true
|
|
194
|
-
? 'bg-surface-3 font-medium text-ink'
|
|
195
|
-
: 'text-ink-dim hover:bg-surface-2'
|
|
196
|
-
)}
|
|
197
|
-
>
|
|
198
|
-
Yes
|
|
199
|
-
</button>
|
|
200
|
-
<button
|
|
201
|
-
type="button"
|
|
202
|
-
onclick={() => column.setFilterValue(boolFilter === false ? undefined : false)}
|
|
203
|
-
class={cn(
|
|
204
|
-
'flex-1 px-2 py-1',
|
|
205
|
-
boolFilter === false
|
|
206
|
-
? 'bg-surface-3 font-medium text-ink'
|
|
207
|
-
: 'text-ink-dim hover:bg-surface-2'
|
|
208
|
-
)}
|
|
209
|
-
>
|
|
210
|
-
No
|
|
211
|
-
</button>
|
|
212
|
-
</div>
|
|
213
|
-
{:else if getColumnType(column) === 'select'}
|
|
214
|
-
{@const options = getFacetedValues(column)}
|
|
215
|
-
{@const selected = getSelectValues(column)}
|
|
216
|
-
<div class="flex flex-col gap-0.5">
|
|
217
|
-
{#each options as option (option)}
|
|
218
|
-
<Checkbox
|
|
219
|
-
size="sm"
|
|
220
|
-
label={option}
|
|
221
|
-
class="min-h-7 rounded-sm px-2 hover:bg-surface-2"
|
|
222
|
-
checked={selected.includes(option)}
|
|
223
|
-
onCheckedChange={({ checked }) =>
|
|
224
|
-
handleSelectChange(column, option, checked === true)}
|
|
225
|
-
/>
|
|
226
|
-
{/each}
|
|
227
|
-
</div>
|
|
228
|
-
{:else}
|
|
229
|
-
<Field.Root>
|
|
230
|
-
<Field.Input
|
|
231
|
-
placeholder="Search..."
|
|
232
|
-
value={localText[column.id] ?? ''}
|
|
233
|
-
oninput={(e: Event) =>
|
|
234
|
-
handleTextInput(column, (e.currentTarget as HTMLInputElement).value)}
|
|
235
|
-
/>
|
|
236
|
-
</Field.Root>
|
|
237
|
-
{/if}
|
|
238
|
-
</div>
|
|
239
|
-
{/each}
|
|
240
|
-
</ScrollArea.Content>
|
|
241
|
-
</ScrollArea.Viewport>
|
|
242
|
-
<ScrollArea.Scrollbar orientation="vertical">
|
|
243
|
-
<ScrollArea.Thumb />
|
|
244
|
-
</ScrollArea.Scrollbar>
|
|
245
|
-
<ScrollArea.Corner />
|
|
246
|
-
</ScrollArea.Root>
|
|
247
|
-
</Popover.Content>
|
|
248
|
-
|
|
249
|
-
</Popover.Root>
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends RowData">
|
|
2
|
-
import type { CellData, Column, RowData } from '@tanstack/svelte-table';
|
|
3
|
-
import * as Popover from '../popover';
|
|
4
|
-
import * as ScrollArea from '../scroll-area';
|
|
5
|
-
import Checkbox from '../checkbox/checkbox.svelte';
|
|
6
|
-
import type { DataTableFeatures, DataTableInstance } from './create-table';
|
|
7
|
-
|
|
8
|
-
type Props = {
|
|
9
|
-
table: DataTableInstance<T>;
|
|
10
|
-
triggerLabel?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
let { table, triggerLabel = 'Columns' }: Props = $props();
|
|
14
|
-
|
|
15
|
-
const columnVisibility = $derived(table.store.state.columnVisibility);
|
|
16
|
-
const allColumnsVisible = $derived(table.getIsAllColumnsVisible());
|
|
17
|
-
const someColumnsVisible = $derived(
|
|
18
|
-
table.getAllLeafColumns().some((column) => column.getCanHide() && column.getIsVisible())
|
|
19
|
-
);
|
|
20
|
-
const allColumnsVisibilityState = $derived(
|
|
21
|
-
allColumnsVisible ? true : someColumnsVisible ? 'indeterminate' : false
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
function getColumnLabel(column: Column<DataTableFeatures, T, CellData>) {
|
|
25
|
-
return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getColumnIsVisible(column: Column<DataTableFeatures, T, CellData>, state: unknown) {
|
|
29
|
-
void state;
|
|
30
|
-
return column.getIsVisible();
|
|
31
|
-
}
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
<Popover.Root positioning={{ placement: 'bottom-end' }}>
|
|
35
|
-
<Popover.Trigger
|
|
36
|
-
class="flex h-9 cursor-pointer items-center rounded-md border border-surface-3 bg-surface-1 px-3 text-sm font-medium text-ink shadow-sm outline-none hover:bg-surface-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring" >
|
|
37
|
-
{triggerLabel}
|
|
38
|
-
</Popover.Trigger>
|
|
39
|
-
|
|
40
|
-
<Popover.Content class="w-56 p-2" showArrow={false}>
|
|
41
|
-
<div class="border-b border-surface-3 px-2 pb-2">
|
|
42
|
-
<Checkbox
|
|
43
|
-
size="sm"
|
|
44
|
-
label="All columns"
|
|
45
|
-
checked={allColumnsVisibilityState}
|
|
46
|
-
onCheckedChange={({ checked }) => table.toggleAllColumnsVisible(checked === true)}
|
|
47
|
-
/>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<ScrollArea.Root class="h-72">
|
|
51
|
-
<ScrollArea.Viewport>
|
|
52
|
-
<ScrollArea.Content class="py-1 pe-3">
|
|
53
|
-
<div class="flex flex-col">
|
|
54
|
-
{#each table.getAllLeafColumns() as column (column.id)}
|
|
55
|
-
<Checkbox
|
|
56
|
-
size="md"
|
|
57
|
-
label={getColumnLabel(column)}
|
|
58
|
-
class="min-h-8 rounded-sm px-2 hover:bg-surface-2"
|
|
59
|
-
checked={getColumnIsVisible(column, columnVisibility)}
|
|
60
|
-
disabled={!column.getCanHide()}
|
|
61
|
-
onCheckedChange={({ checked }) => column.toggleVisibility(checked === true)}
|
|
62
|
-
/>
|
|
63
|
-
{/each}
|
|
64
|
-
</div>
|
|
65
|
-
</ScrollArea.Content>
|
|
66
|
-
</ScrollArea.Viewport>
|
|
67
|
-
<ScrollArea.Scrollbar orientation="vertical">
|
|
68
|
-
<ScrollArea.Thumb />
|
|
69
|
-
</ScrollArea.Scrollbar>
|
|
70
|
-
<ScrollArea.Corner />
|
|
71
|
-
</ScrollArea.Root>
|
|
72
|
-
</Popover.Content>
|
|
73
|
-
|
|
74
|
-
</Popover.Root>
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
<script lang="ts" generics="T extends RowData">
|
|
2
|
-
import { FlexRender } from '@tanstack/svelte-table';
|
|
3
|
-
import type { RowData } from '@tanstack/svelte-table';
|
|
4
|
-
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
-
import { untrack } from 'svelte';
|
|
6
|
-
import { createSubscriber } from 'svelte/reactivity';
|
|
7
|
-
import { cn, type ClassValue } from 'tailwind-variants';
|
|
8
|
-
import { PhArrowSquareOut, PhCheck, PhX } from '$lib/icons';
|
|
9
|
-
import Checkbox from '../checkbox/checkbox.svelte';
|
|
10
|
-
import { type DataTableInstance } from './create-table';
|
|
11
|
-
import {
|
|
12
|
-
alignClass,
|
|
13
|
-
getBooleanCellValue,
|
|
14
|
-
getColumnMeta,
|
|
15
|
-
getUrlCellValue,
|
|
16
|
-
justifyClass,
|
|
17
|
-
openUrlCell
|
|
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
|
-
onRowClick?: (details: { row: T; event: MouseEvent }) => void;
|
|
26
|
-
onRowDoubleClick?: (details: { row: T; event: MouseEvent }) => void;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
let {
|
|
30
|
-
table,
|
|
31
|
-
caption,
|
|
32
|
-
emptyMessage = 'No rows found',
|
|
33
|
-
class: className,
|
|
34
|
-
onRowClick,
|
|
35
|
-
onRowDoubleClick,
|
|
36
|
-
...rest
|
|
37
|
-
}: Props = $props();
|
|
38
|
-
|
|
39
|
-
const subscribeToTable = createSubscriber((update) => {
|
|
40
|
-
const sub = table.store.subscribe(() => update());
|
|
41
|
-
return () => sub.unsubscribe();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const rowModel = $derived(table.getRowModel());
|
|
45
|
-
const headerGroups = $derived(table.getHeaderGroups());
|
|
46
|
-
|
|
47
|
-
const columnSizes = $derived.by(() => {
|
|
48
|
-
subscribeToTable();
|
|
49
|
-
return untrack(() =>
|
|
50
|
-
table.getVisibleLeafColumns().map((col) => ({
|
|
51
|
-
id: col.id,
|
|
52
|
-
size: col.getSize(),
|
|
53
|
-
grow: getColumnMeta(col.columnDef)?.grow ?? false
|
|
54
|
-
}))
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
const hasGrowColumn = $derived(columnSizes.some((c) => c.grow));
|
|
58
|
-
const tableWidth = $derived.by(() => {
|
|
59
|
-
subscribeToTable();
|
|
60
|
-
return untrack(() => table.getTotalSize());
|
|
61
|
-
});
|
|
62
|
-
const isRowSelectionEnabled = $derived(Boolean(table.options.enableRowSelection));
|
|
63
|
-
const isMultiRowSelectionEnabled = $derived(table.options.enableMultiRowSelection !== false);
|
|
64
|
-
// table.state is the public selected state — safe to read directly in template
|
|
65
|
-
// table.store.state is the internal TanStack atom — accumulates subscriptions
|
|
66
|
-
const rowSelection = $derived(table.state.rowSelection);
|
|
67
|
-
const selectedCount = $derived(Object.keys(rowSelection).length);
|
|
68
|
-
</script>
|
|
69
|
-
|
|
70
|
-
<div
|
|
71
|
-
class={cn(
|
|
72
|
-
'flex max-h-full min-h-0 flex-col overflow-hidden rounded-lg border border-surface-3 bg-surface-1',
|
|
73
|
-
className
|
|
74
|
-
)}
|
|
75
|
-
{...rest}
|
|
76
|
-
>
|
|
77
|
-
<div class="min-h-0 flex-1 overflow-auto">
|
|
78
|
-
<table
|
|
79
|
-
class="table-fixed border-separate border-spacing-0 text-sm"
|
|
80
|
-
style="width: max(100%, {tableWidth + (isRowSelectionEnabled ? 40 : 0)}px)"
|
|
81
|
-
>
|
|
82
|
-
<colgroup>
|
|
83
|
-
{#if isRowSelectionEnabled}
|
|
84
|
-
<col style="width: 40px" />
|
|
85
|
-
{/if}
|
|
86
|
-
{#each columnSizes as col (col.id)}
|
|
87
|
-
<col style={col.grow ? undefined : `width: ${col.size}px`} />
|
|
88
|
-
{/each}
|
|
89
|
-
{#if !hasGrowColumn}
|
|
90
|
-
<col />
|
|
91
|
-
{/if}
|
|
92
|
-
</colgroup>
|
|
93
|
-
{#if caption}
|
|
94
|
-
<caption class="sr-only">{caption}</caption>
|
|
95
|
-
{/if}
|
|
96
|
-
<thead class="sticky top-0 z-20 bg-surface-2 text-left text-ink-dim">
|
|
97
|
-
{#each headerGroups as headerGroup, headerGroupIndex (headerGroup.id)}
|
|
98
|
-
<tr class="h-9">
|
|
99
|
-
{#if isRowSelectionEnabled}
|
|
100
|
-
<th
|
|
101
|
-
class="h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 text-center align-middle"
|
|
102
|
-
style="position: sticky; left: 0; z-index: 15; width: 40px; min-width: 40px; max-width: 40px"
|
|
103
|
-
>
|
|
104
|
-
{#if isMultiRowSelectionEnabled && headerGroupIndex === headerGroups.length - 1}
|
|
105
|
-
<Checkbox
|
|
106
|
-
size="sm"
|
|
107
|
-
aria-label="Select all rows"
|
|
108
|
-
class="mx-auto size-4"
|
|
109
|
-
checked={table.getIsAllRowsSelected()
|
|
110
|
-
? true
|
|
111
|
-
: table.getIsSomeRowsSelected()
|
|
112
|
-
? 'indeterminate'
|
|
113
|
-
: false}
|
|
114
|
-
onCheckedChange={({ checked }) => table.toggleAllRowsSelected(checked === true)}
|
|
115
|
-
/>
|
|
116
|
-
{/if}
|
|
117
|
-
</th>
|
|
118
|
-
{/if}
|
|
119
|
-
{#each headerGroup.headers as header (header.id)}
|
|
120
|
-
{@const columnDef = getColumnMeta(header.column.columnDef)}
|
|
121
|
-
<th
|
|
122
|
-
class={cn(
|
|
123
|
-
'h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 align-middle leading-5 font-medium',
|
|
124
|
-
alignClass(columnDef?.align)
|
|
125
|
-
)}
|
|
126
|
-
colspan={header.colSpan}
|
|
127
|
-
>
|
|
128
|
-
{#if !header.isPlaceholder}
|
|
129
|
-
<FlexRender {header} />
|
|
130
|
-
{/if}
|
|
131
|
-
</th>
|
|
132
|
-
{/each}
|
|
133
|
-
{#if !hasGrowColumn}
|
|
134
|
-
<th aria-hidden="true" class="h-9 border-b border-surface-3 bg-surface-2 p-0"></th>
|
|
135
|
-
{/if}
|
|
136
|
-
</tr>
|
|
137
|
-
{/each}
|
|
138
|
-
</thead>
|
|
139
|
-
<tbody>
|
|
140
|
-
{#each rowModel.rows as row (row.id)}
|
|
141
|
-
{@const isSelected = Boolean(rowSelection[row.id])}
|
|
142
|
-
<tr
|
|
143
|
-
class={cn(
|
|
144
|
-
'group/row',
|
|
145
|
-
'[--row-bg:var(--compote-surface-1)]',
|
|
146
|
-
'hover:bg-well/60 hover:[--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]',
|
|
147
|
-
isSelected &&
|
|
148
|
-
'bg-well/60 [--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]'
|
|
149
|
-
)}
|
|
150
|
-
onclick={(event) => onRowClick?.({ row: row.original, event })}
|
|
151
|
-
ondblclick={(event) => onRowDoubleClick?.({ row: row.original, event })}
|
|
152
|
-
>
|
|
153
|
-
{#if isRowSelectionEnabled}
|
|
154
|
-
<td
|
|
155
|
-
class="border-b border-surface-2 bg-(--row-bg) px-3 py-2 text-center align-middle group-last/row:border-b-0"
|
|
156
|
-
style="position: sticky; left: 0; z-index: 1"
|
|
157
|
-
>
|
|
158
|
-
<Checkbox
|
|
159
|
-
size="sm"
|
|
160
|
-
aria-label="Select row"
|
|
161
|
-
class="mx-auto size-4"
|
|
162
|
-
checked={isSelected}
|
|
163
|
-
disabled={!row.getCanSelect()}
|
|
164
|
-
onCheckedChange={({ checked }) => row.toggleSelected(checked === true)}
|
|
165
|
-
/>
|
|
166
|
-
</td>
|
|
167
|
-
{/if}
|
|
168
|
-
{#each row.getVisibleCells() as cell (cell.id)}
|
|
169
|
-
{@const columnDef = getColumnMeta(cell.column.columnDef)}
|
|
170
|
-
<td
|
|
171
|
-
class={cn(
|
|
172
|
-
'truncate border-b border-b-surface-2 px-3 py-2 group-last/row:border-b-0',
|
|
173
|
-
alignClass(columnDef?.align)
|
|
174
|
-
)}
|
|
175
|
-
>
|
|
176
|
-
{#if columnDef?.type === 'boolean'}
|
|
177
|
-
{@const value = getBooleanCellValue(cell.getValue())}
|
|
178
|
-
{#if value === true}
|
|
179
|
-
<span
|
|
180
|
-
class="inline-flex size-5 items-center justify-center text-success"
|
|
181
|
-
role="img"
|
|
182
|
-
aria-label="Yes"
|
|
183
|
-
>
|
|
184
|
-
<PhCheck class="size-4" />
|
|
185
|
-
</span>
|
|
186
|
-
{:else if value === false}
|
|
187
|
-
<span
|
|
188
|
-
class="inline-flex size-5 items-center justify-center text-danger"
|
|
189
|
-
role="img"
|
|
190
|
-
aria-label="No"
|
|
191
|
-
>
|
|
192
|
-
<PhX class="size-4" />
|
|
193
|
-
</span>
|
|
194
|
-
{:else}
|
|
195
|
-
-
|
|
196
|
-
{/if}
|
|
197
|
-
{:else if columnDef?.type === 'url'}
|
|
198
|
-
{@const value = getUrlCellValue(cell.getValue())}
|
|
199
|
-
{#if value}
|
|
200
|
-
<button
|
|
201
|
-
type="button"
|
|
202
|
-
class={cn(
|
|
203
|
-
'inline-flex max-w-full appearance-none items-center gap-1.5 rounded-sm border-0 bg-transparent p-0 align-middle leading-5 font-medium text-ink underline decoration-border decoration-dotted underline-offset-4 outline-none hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
|
|
204
|
-
justifyClass(columnDef.align)
|
|
205
|
-
)}
|
|
206
|
-
onclick={() => openUrlCell(value)}
|
|
207
|
-
>
|
|
208
|
-
<PhArrowSquareOut class="size-3.5 shrink-0" />
|
|
209
|
-
</button>
|
|
210
|
-
{:else}
|
|
211
|
-
-
|
|
212
|
-
{/if}
|
|
213
|
-
{:else}
|
|
214
|
-
<FlexRender {cell} />
|
|
215
|
-
{/if}
|
|
216
|
-
</td>
|
|
217
|
-
{/each}
|
|
218
|
-
{#if !hasGrowColumn}
|
|
219
|
-
<td aria-hidden="true" class="border-b border-surface-2 p-0 group-last/row:border-b-0"
|
|
220
|
-
></td>
|
|
221
|
-
{/if}
|
|
222
|
-
</tr>
|
|
223
|
-
{:else}
|
|
224
|
-
<tr>
|
|
225
|
-
<td
|
|
226
|
-
class="px-3 py-10 text-center text-sm text-ink-dim"
|
|
227
|
-
colspan={columnSizes.length + (isRowSelectionEnabled ? 2 : 1)}
|
|
228
|
-
>
|
|
229
|
-
{emptyMessage}
|
|
230
|
-
</td>
|
|
231
|
-
</tr>
|
|
232
|
-
{/each}
|
|
233
|
-
</tbody>
|
|
234
|
-
</table>
|
|
235
|
-
</div>
|
|
236
|
-
|
|
237
|
-
<div class="shrink-0 border-t border-surface-3 bg-surface-2 px-3 py-2 text-sm text-ink-dim">
|
|
238
|
-
{#if isRowSelectionEnabled}
|
|
239
|
-
{selectedCount} of {rowModel.rows.length} rows selected
|
|
240
|
-
{:else}
|
|
241
|
-
{rowModel.rows.length} rows
|
|
242
|
-
{/if}
|
|
243
|
-
</div>
|
|
244
|
-
|
|
245
|
-
</div>
|