sheer-ui 0.3.1 → 0.4.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/package.json
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
selectedValues.add(option.value);
|
|
75
75
|
}
|
|
76
76
|
const filterValues = Array.from(selectedValues);
|
|
77
|
-
column
|
|
77
|
+
column.filterValue = filterValues.length ? filterValues : undefined;
|
|
78
78
|
}}>
|
|
79
79
|
<div
|
|
80
80
|
class={[
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
{#if selectedValues.size > 0}
|
|
101
101
|
<Command.Separator />
|
|
102
102
|
<Command.Group>
|
|
103
|
-
<Command.Item onSelect={() => column
|
|
103
|
+
<Command.Item onSelect={() => (column.filterValue = undefined)} class="justify-center text-center">Clear filters</Command.Item>
|
|
104
104
|
</Command.Group>
|
|
105
105
|
{/if}
|
|
106
106
|
</Command.List>
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
} = $props();
|
|
26
26
|
|
|
27
27
|
const isFiltered = $derived(table.columnFilters.length > 0);
|
|
28
|
+
const search = $derived(searchColumn === undefined ? undefined : table.column(searchColumn));
|
|
28
29
|
</script>
|
|
29
30
|
|
|
30
31
|
<div class="flex items-center justify-between">
|
|
31
|
-
{#if
|
|
32
|
+
{#if search}
|
|
32
33
|
<Input
|
|
33
34
|
{disabled}
|
|
34
35
|
placeholder={searchPlaceholder ?? 'Filter...'}
|
|
35
|
-
value={(
|
|
36
|
-
oninput={(e) =>
|
|
37
|
-
onchange={(e) => table.column(searchColumn)?.setFilterValue(e.currentTarget.value)}
|
|
36
|
+
value={(search.filterValue as string) ?? ''}
|
|
37
|
+
oninput={(e) => (search.filterValue = e.currentTarget.value)}
|
|
38
38
|
class="h-8 w-37.5 lg:w-62.5" />
|
|
39
39
|
{/if}
|
|
40
40
|
<div class="flex items-center space-x-2">
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
{/each}
|
|
47
47
|
|
|
48
48
|
{#if isFiltered}
|
|
49
|
-
<Button variant="ghost" {disabled} onclick={() => table.
|
|
49
|
+
<Button variant="ghost" {disabled} onclick={() => (table.columnFilters = [])} class="h-8! px-2! lg:px-3!">
|
|
50
50
|
Reset
|
|
51
51
|
<XIcon />
|
|
52
52
|
</Button>
|
|
@@ -20,13 +20,16 @@ export class DataTable<TData> {
|
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
21
|
readonly #defs: () => ColumnDef<TData, any>[];
|
|
22
22
|
readonly #paginated: boolean;
|
|
23
|
-
readonly
|
|
23
|
+
readonly selectedRowIds = new SvelteSet<string>();
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// Raw so a change is always a reassignment: the page record below keys on their identity.
|
|
26
|
+
sorting = $state.raw<ColumnSort[]>([]);
|
|
27
|
+
columnFilters = $state.raw<ColumnFilter[]>([]);
|
|
27
28
|
columnVisibility = $state<Record<string, boolean>>({});
|
|
28
|
-
#pageIndex = $state(0);
|
|
29
29
|
#pageSize = $state(10);
|
|
30
|
+
// A page index holds for the sorting and filters it was set under: a sort or filter change reads
|
|
31
|
+
// as page 0 without a write, while a data refetch underneath keeps the page.
|
|
32
|
+
#page = $state.raw({ index: 0, sorting: this.sorting, columnFilters: this.columnFilters });
|
|
30
33
|
|
|
31
34
|
constructor(options: DataTableOptions<TData>) {
|
|
32
35
|
this.#data = options.data;
|
|
@@ -40,7 +43,7 @@ export class DataTable<TData> {
|
|
|
40
43
|
readonly #columns: Column<TData, any>[] = $derived.by(() => this.#defs().map((def) => new Column(this, def)));
|
|
41
44
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
45
|
readonly #columnsById = $derived(new Map<string, Column<TData, any>>(this.#columns.map((column) => [column.id, column])));
|
|
43
|
-
readonly #coreRows: readonly Row<TData>[] = $derived.by(() => this.#data().map((original, index) => new Row(this, this
|
|
46
|
+
readonly #coreRows: readonly Row<TData>[] = $derived.by(() => this.#data().map((original, index) => new Row(this, this.selectedRowIds, original, index)));
|
|
44
47
|
|
|
45
48
|
readonly #activeFilters = $derived(
|
|
46
49
|
this.columnFilters.flatMap((filter) => {
|
|
@@ -65,7 +68,9 @@ export class DataTable<TData> {
|
|
|
65
68
|
|
|
66
69
|
readonly pageCount = $derived.by(() => (this.#paginated ? Math.max(1, Math.ceil(this.#sorted.length / this.#pageSize)) : 1));
|
|
67
70
|
// Clamped on read, never written back: data shrinking under a stale index lands on the last page.
|
|
68
|
-
readonly #safeIndex = $derived(
|
|
71
|
+
readonly #safeIndex = $derived(
|
|
72
|
+
this.#page.sorting === this.sorting && this.#page.columnFilters === this.columnFilters ? Math.min(this.#page.index, this.pageCount - 1) : 0,
|
|
73
|
+
);
|
|
69
74
|
|
|
70
75
|
readonly rows: readonly Row<TData>[] = $derived.by(() =>
|
|
71
76
|
this.#paginated ? this.#sorted.slice(this.#safeIndex * this.#pageSize, (this.#safeIndex + 1) * this.#pageSize) : this.#sorted,
|
|
@@ -79,7 +84,7 @@ export class DataTable<TData> {
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
setPageIndex(index: number) {
|
|
82
|
-
this.#
|
|
87
|
+
this.#page = { index: Math.min(Math.max(index, 0), this.pageCount - 1), sorting: this.sorting, columnFilters: this.columnFilters };
|
|
83
88
|
}
|
|
84
89
|
nextPage() {
|
|
85
90
|
this.setPageIndex(this.#safeIndex + 1);
|
|
@@ -90,23 +95,10 @@ export class DataTable<TData> {
|
|
|
90
95
|
setPageSize(size: number) {
|
|
91
96
|
const next = Math.max(1, size);
|
|
92
97
|
// Keep the current top row visible, matching upstream's paging math.
|
|
93
|
-
this.#
|
|
98
|
+
this.#page = { index: Math.floor((this.#safeIndex * this.#pageSize) / next), sorting: this.sorting, columnFilters: this.columnFilters };
|
|
94
99
|
this.#pageSize = next;
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
// User-initiated sort and filter changes restart paging; a data refetch underneath does not.
|
|
98
|
-
setSorting(sorting: ColumnSort[]) {
|
|
99
|
-
this.sorting = sorting;
|
|
100
|
-
this.#pageIndex = 0;
|
|
101
|
-
}
|
|
102
|
-
setColumnFilters(filters: ColumnFilter[]) {
|
|
103
|
-
this.columnFilters = filters;
|
|
104
|
-
this.#pageIndex = 0;
|
|
105
|
-
}
|
|
106
|
-
resetColumnFilters() {
|
|
107
|
-
this.setColumnFilters([]);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
102
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
103
|
get allColumns(): readonly Column<TData, any>[] {
|
|
112
104
|
return this.#columns;
|
|
@@ -122,15 +114,6 @@ export class DataTable<TData> {
|
|
|
122
114
|
{ id: '0', headers: this.visibleColumns.map((column) => new Header(this, column)) },
|
|
123
115
|
]);
|
|
124
116
|
|
|
125
|
-
get selectedRowIds(): ReadonlySet<string> {
|
|
126
|
-
return this.#selection;
|
|
127
|
-
}
|
|
128
|
-
setSelectedRowIds(ids: Iterable<string>) {
|
|
129
|
-
const next = new Set(ids);
|
|
130
|
-
for (const id of this.#selection) if (!next.has(id)) this.#selection.delete(id);
|
|
131
|
-
for (const id of next) this.#selection.add(id);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
117
|
readonly selectedRows: readonly Row<TData>[] = $derived(this.#coreRows.filter((row) => row.isSelected));
|
|
135
118
|
readonly filteredSelectedRows: readonly Row<TData>[] = $derived(this.filteredRows.filter((row) => row.isSelected));
|
|
136
119
|
|
|
@@ -194,26 +177,23 @@ export class Column<TData, TValue = unknown> {
|
|
|
194
177
|
const existingIndex = old.findIndex((sort) => sort.id === this.id);
|
|
195
178
|
|
|
196
179
|
if (old.length > 0 && this.#canMultiSort && multi) {
|
|
197
|
-
this.#table.
|
|
198
|
-
|
|
199
|
-
?
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
: [...old, { id: this.id, desc: nextDesc }],
|
|
203
|
-
);
|
|
180
|
+
this.#table.sorting = existing
|
|
181
|
+
? !hasManualValue && nextOrder === false
|
|
182
|
+
? old.filter((sort) => sort.id !== this.id)
|
|
183
|
+
: old.map((sort) => (sort.id === this.id ? { ...sort, desc: nextDesc } : sort))
|
|
184
|
+
: [...old, { id: this.id, desc: nextDesc }];
|
|
204
185
|
return;
|
|
205
186
|
}
|
|
206
187
|
|
|
207
188
|
// Single mode: only the most recently applied sort toggles through the cycle; anything else replaces.
|
|
208
189
|
if (existing !== undefined && existingIndex === old.length - 1) {
|
|
209
|
-
this.#table.
|
|
190
|
+
this.#table.sorting =
|
|
210
191
|
!hasManualValue && nextOrder === false
|
|
211
192
|
? old.filter((sort) => sort.id !== this.id)
|
|
212
|
-
: old.map((sort) => (sort.id === this.id ? { ...sort, desc: nextDesc } : sort))
|
|
213
|
-
);
|
|
193
|
+
: old.map((sort) => (sort.id === this.id ? { ...sort, desc: nextDesc } : sort));
|
|
214
194
|
return;
|
|
215
195
|
}
|
|
216
|
-
this.#table.
|
|
196
|
+
this.#table.sorting = [{ id: this.id, desc: nextDesc }];
|
|
217
197
|
}
|
|
218
198
|
|
|
219
199
|
get #canMultiSort(): boolean {
|
|
@@ -239,9 +219,9 @@ export class Column<TData, TValue = unknown> {
|
|
|
239
219
|
get filterValue(): unknown {
|
|
240
220
|
return this.#table.columnFilters.find((filter) => filter.id === this.id)?.value;
|
|
241
221
|
}
|
|
242
|
-
|
|
222
|
+
set filterValue(value: unknown) {
|
|
243
223
|
const others = this.#table.columnFilters.filter((filter) => filter.id !== this.id);
|
|
244
|
-
this.#table.
|
|
224
|
+
this.#table.columnFilters = shouldAutoRemoveFilter(value) ? others : [...others, { id: this.id, value }];
|
|
245
225
|
}
|
|
246
226
|
|
|
247
227
|
get facetedUniqueValues(): ReadonlyMap<unknown, number> {
|