@marianmeres/stuic 3.67.0 → 3.68.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.
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
no_data: "No data",
|
|
22
22
|
select_all_rows: "Select all rows on this page",
|
|
23
23
|
select_row: "Select row",
|
|
24
|
+
select_all_on_page_x: "All {count} on this page selected.",
|
|
25
|
+
select_all_results: "Select all {totalCount} results",
|
|
26
|
+
all_results_selected: "All {totalCount} results selected.",
|
|
27
|
+
clear_selection: "Clear selection",
|
|
24
28
|
};
|
|
25
29
|
let out = m[k] ?? fallback ?? k;
|
|
26
30
|
return isPlainObject(values)
|
|
@@ -74,6 +78,23 @@
|
|
|
74
78
|
/** Return true to disable selection for a specific row */
|
|
75
79
|
selectDisabledBy?: (row: T, index: number) => boolean;
|
|
76
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Allow the user to opt into "select all results across all pages" mode.
|
|
83
|
+
* When enabled and `paging.total > data.length`, a banner offers to expand
|
|
84
|
+
* selection beyond the current page. Consumers must execute batch operations
|
|
85
|
+
* as server-side filter queries (not by iterating row IDs) since off-page rows
|
|
86
|
+
* are not available locally.
|
|
87
|
+
*/
|
|
88
|
+
allowSelectAllPages?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* All-pages selection mode (bindable). When true, selection semantics invert:
|
|
91
|
+
* `excluded` holds deselected IDs, and every row not in `excluded` is selected.
|
|
92
|
+
* Newly inserted rows are implicitly selected in this mode.
|
|
93
|
+
*/
|
|
94
|
+
selectedAll?: boolean;
|
|
95
|
+
/** Set of row IDs explicitly deselected while in all-pages mode (bindable) */
|
|
96
|
+
excluded?: Set<string | number>;
|
|
97
|
+
|
|
77
98
|
/** Callback when a row is clicked */
|
|
78
99
|
onRowClick?: (row: T, index: number) => void;
|
|
79
100
|
|
|
@@ -103,12 +124,40 @@
|
|
|
103
124
|
},
|
|
104
125
|
]
|
|
105
126
|
>;
|
|
106
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Batch actions bar snippet (shown when items are selected).
|
|
129
|
+
*
|
|
130
|
+
* Note: in all-pages mode (`selectedAll === true`) `selectedRows` only contains
|
|
131
|
+
* rows from the current page that aren't excluded. Off-page rows are not
|
|
132
|
+
* materialized — execute batch operations server-side using the active filter
|
|
133
|
+
* minus `excluded`.
|
|
134
|
+
*/
|
|
107
135
|
batchActions?: Snippet<
|
|
108
136
|
[
|
|
109
137
|
{
|
|
110
138
|
selected: Set<string | number>;
|
|
111
139
|
selectedRows: T[];
|
|
140
|
+
selectedAll: boolean;
|
|
141
|
+
excluded: Set<string | number>;
|
|
142
|
+
/** `selected.size` in normal mode, or `totalItems - excluded.size` in all-pages mode */
|
|
143
|
+
effectiveCount: number;
|
|
144
|
+
totalCount: number | null;
|
|
145
|
+
clearSelection: () => void;
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
>;
|
|
149
|
+
/**
|
|
150
|
+
* Custom "select all results across pages" banner. When omitted, a default
|
|
151
|
+
* banner is rendered.
|
|
152
|
+
*/
|
|
153
|
+
selectAllBanner?: Snippet<
|
|
154
|
+
[
|
|
155
|
+
{
|
|
156
|
+
selectedAll: boolean;
|
|
157
|
+
effectiveCount: number;
|
|
158
|
+
totalCount: number;
|
|
159
|
+
pageCount: number;
|
|
160
|
+
selectAll: () => void;
|
|
112
161
|
clearSelection: () => void;
|
|
113
162
|
},
|
|
114
163
|
]
|
|
@@ -150,11 +199,15 @@
|
|
|
150
199
|
selected = $bindable(new Set()),
|
|
151
200
|
selectOnRowClick = false,
|
|
152
201
|
selectDisabledBy,
|
|
202
|
+
allowSelectAllPages = false,
|
|
203
|
+
selectedAll = $bindable(false),
|
|
204
|
+
excluded = $bindable(new Set()),
|
|
153
205
|
onRowClick,
|
|
154
206
|
loading = false,
|
|
155
207
|
cell,
|
|
156
208
|
row,
|
|
157
209
|
batchActions,
|
|
210
|
+
selectAllBanner,
|
|
158
211
|
empty,
|
|
159
212
|
mobileRow,
|
|
160
213
|
t = t_default,
|
|
@@ -181,47 +234,114 @@
|
|
|
181
234
|
.filter((id): id is string | number => id !== null);
|
|
182
235
|
});
|
|
183
236
|
|
|
237
|
+
function isRowSelected(id: string | number): boolean {
|
|
238
|
+
return selectedAll ? !excluded.has(id) : selected.has(id);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Batch variant avoids creating one Set per row for shift-range / select-all.
|
|
242
|
+
function setRowsSelected(ids: Array<string | number>, on: boolean) {
|
|
243
|
+
if (selectedAll) {
|
|
244
|
+
const next = new Set(excluded);
|
|
245
|
+
for (const id of ids) {
|
|
246
|
+
if (on) next.delete(id);
|
|
247
|
+
else next.add(id);
|
|
248
|
+
}
|
|
249
|
+
excluded = next;
|
|
250
|
+
} else {
|
|
251
|
+
const next = new Set(selected);
|
|
252
|
+
for (const id of ids) {
|
|
253
|
+
if (on) next.add(id);
|
|
254
|
+
else next.delete(id);
|
|
255
|
+
}
|
|
256
|
+
selected = next;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
184
260
|
let allOnPageSelected = $derived.by(() => {
|
|
185
261
|
if (!selectable || selectableRowIds.length === 0) return false;
|
|
186
|
-
return selectableRowIds.every((id) =>
|
|
262
|
+
return selectableRowIds.every((id) => isRowSelected(id));
|
|
187
263
|
});
|
|
188
264
|
|
|
189
265
|
let someOnPageSelected = $derived.by(() => {
|
|
190
266
|
if (!selectable || selectableRowIds.length === 0) return false;
|
|
191
|
-
return selectableRowIds.some((id) =>
|
|
267
|
+
return selectableRowIds.some((id) => isRowSelected(id)) && !allOnPageSelected;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
let totalCount = $derived(paging?.total ?? null);
|
|
271
|
+
let effectiveCount = $derived.by(() => {
|
|
272
|
+
if (selectedAll) {
|
|
273
|
+
const base = totalCount ?? data.length;
|
|
274
|
+
return Math.max(0, base - excluded.size);
|
|
275
|
+
}
|
|
276
|
+
return selected.size;
|
|
192
277
|
});
|
|
193
278
|
|
|
194
279
|
let selectedRows = $derived.by(() => {
|
|
195
|
-
if (!selectable
|
|
280
|
+
if (!selectable) return [] as T[];
|
|
281
|
+
if (selectedAll) {
|
|
282
|
+
return data.filter((row, i) => !excluded.has(getRowId(row, i)));
|
|
283
|
+
}
|
|
284
|
+
if (selected.size === 0) return [] as T[];
|
|
196
285
|
return data.filter((row, i) => selected.has(getRowId(row, i)));
|
|
197
286
|
});
|
|
198
287
|
|
|
199
288
|
function toggleSelectAll() {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
} else {
|
|
205
|
-
const next = new Set(selected);
|
|
206
|
-
for (const id of selectableRowIds) next.add(id);
|
|
207
|
-
selected = next;
|
|
289
|
+
// In all-mode the header checkbox exits the mode entirely.
|
|
290
|
+
if (selectedAll) {
|
|
291
|
+
clearAllSelection();
|
|
292
|
+
return;
|
|
208
293
|
}
|
|
294
|
+
setRowsSelected(selectableRowIds, !allOnPageSelected);
|
|
209
295
|
}
|
|
210
296
|
|
|
211
297
|
function toggleSelectRow(id: string | number) {
|
|
212
|
-
|
|
213
|
-
if (next.has(id)) {
|
|
214
|
-
next.delete(id);
|
|
215
|
-
} else {
|
|
216
|
-
next.add(id);
|
|
217
|
-
}
|
|
218
|
-
selected = next;
|
|
298
|
+
setRowsSelected([id], !isRowSelected(id));
|
|
219
299
|
}
|
|
220
300
|
|
|
221
|
-
function
|
|
301
|
+
function enterSelectAll() {
|
|
222
302
|
selected = new Set();
|
|
303
|
+
excluded = new Set();
|
|
304
|
+
selectedAll = true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function clearAllSelection() {
|
|
308
|
+
selectedAll = false;
|
|
309
|
+
excluded = new Set();
|
|
310
|
+
selected = new Set();
|
|
311
|
+
lastClickedIndex = null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Anchor for shift+click range selection; reset when data reference changes.
|
|
315
|
+
let lastClickedIndex: number | null = null;
|
|
316
|
+
$effect(() => {
|
|
317
|
+
data;
|
|
318
|
+
lastClickedIndex = null;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
function handleCheckboxClick(rowIndex: number, e: MouseEvent) {
|
|
322
|
+
const newChecked = (e.currentTarget as HTMLInputElement).checked;
|
|
323
|
+
if (e.shiftKey && lastClickedIndex !== null && lastClickedIndex !== rowIndex) {
|
|
324
|
+
const start = Math.min(lastClickedIndex, rowIndex);
|
|
325
|
+
const end = Math.max(lastClickedIndex, rowIndex);
|
|
326
|
+
const ids: Array<string | number> = [];
|
|
327
|
+
for (let i = start; i <= end; i++) {
|
|
328
|
+
if (selectDisabledBy?.(data[i], i)) continue;
|
|
329
|
+
ids.push(getRowId(data[i], i));
|
|
330
|
+
}
|
|
331
|
+
setRowsSelected(ids, newChecked);
|
|
332
|
+
} else {
|
|
333
|
+
setRowsSelected([getRowId(data[rowIndex], rowIndex)], newChecked);
|
|
334
|
+
}
|
|
335
|
+
lastClickedIndex = rowIndex;
|
|
223
336
|
}
|
|
224
337
|
|
|
338
|
+
let showSelectAllBanner = $derived.by(() => {
|
|
339
|
+
if (!allowSelectAllPages || !selectable || !paging) return false;
|
|
340
|
+
if (paging.total <= data.length) return false;
|
|
341
|
+
if (selectedAll) return true;
|
|
342
|
+
return allOnPageSelected;
|
|
343
|
+
});
|
|
344
|
+
|
|
225
345
|
// --- Row click ---
|
|
226
346
|
function handleRowClick(row: T, index: number, e: MouseEvent) {
|
|
227
347
|
const target = e.target as HTMLElement;
|
|
@@ -256,12 +376,48 @@
|
|
|
256
376
|
</script>
|
|
257
377
|
|
|
258
378
|
<!-- Batch action bar -->
|
|
259
|
-
{#if selectable &&
|
|
379
|
+
{#if selectable && effectiveCount > 0 && batchActions}
|
|
260
380
|
<div class={!unstyled ? "stuic-data-table-batch" : undefined}>
|
|
261
|
-
{@render batchActions({
|
|
381
|
+
{@render batchActions({
|
|
382
|
+
selected,
|
|
383
|
+
selectedRows,
|
|
384
|
+
selectedAll,
|
|
385
|
+
excluded,
|
|
386
|
+
effectiveCount,
|
|
387
|
+
totalCount,
|
|
388
|
+
clearSelection: clearAllSelection,
|
|
389
|
+
})}
|
|
262
390
|
</div>
|
|
263
391
|
{/if}
|
|
264
392
|
|
|
393
|
+
<!-- Select-all-across-pages banner -->
|
|
394
|
+
{#if showSelectAllBanner && paging}
|
|
395
|
+
{#if selectAllBanner}
|
|
396
|
+
{@render selectAllBanner({
|
|
397
|
+
selectedAll,
|
|
398
|
+
effectiveCount,
|
|
399
|
+
totalCount: paging.total,
|
|
400
|
+
pageCount: data.length,
|
|
401
|
+
selectAll: enterSelectAll,
|
|
402
|
+
clearSelection: clearAllSelection,
|
|
403
|
+
})}
|
|
404
|
+
{:else}
|
|
405
|
+
<div class={!unstyled ? "stuic-data-table-select-all-banner" : undefined}>
|
|
406
|
+
{#if selectedAll}
|
|
407
|
+
<span>{t("all_results_selected", { totalCount: paging.total })}</span>
|
|
408
|
+
<Button variant="ghost" size="sm" onclick={clearAllSelection}>
|
|
409
|
+
{t("clear_selection")}
|
|
410
|
+
</Button>
|
|
411
|
+
{:else}
|
|
412
|
+
<span>{t("select_all_on_page_x", { count: data.length })}</span>
|
|
413
|
+
<Button variant="ghost" size="sm" onclick={enterSelectAll}>
|
|
414
|
+
{t("select_all_results", { totalCount: paging.total })}
|
|
415
|
+
</Button>
|
|
416
|
+
{/if}
|
|
417
|
+
</div>
|
|
418
|
+
{/if}
|
|
419
|
+
{/if}
|
|
420
|
+
|
|
265
421
|
<!-- Root container -->
|
|
266
422
|
<div bind:this={el} class={rootClass} {...rest}>
|
|
267
423
|
{#if isDesktop}
|
|
@@ -303,7 +459,7 @@
|
|
|
303
459
|
<tbody>
|
|
304
460
|
{#each data as rowData, rowIndex (getRowId(rowData, rowIndex))}
|
|
305
461
|
{@const rowId = getRowId(rowData, rowIndex)}
|
|
306
|
-
{@const isSelected = selectable &&
|
|
462
|
+
{@const isSelected = selectable && isRowSelected(rowId)}
|
|
307
463
|
{@const selectDisabled = !!selectDisabledBy?.(rowData, rowIndex)}
|
|
308
464
|
{#if row}
|
|
309
465
|
{@render row({ row: rowData, columns, rowIndex, isSelected })}
|
|
@@ -322,7 +478,7 @@
|
|
|
322
478
|
type="checkbox"
|
|
323
479
|
checked={isSelected}
|
|
324
480
|
disabled={selectDisabled}
|
|
325
|
-
|
|
481
|
+
onclick={(e) => handleCheckboxClick(rowIndex, e)}
|
|
326
482
|
aria-label={t("select_row")}
|
|
327
483
|
/>
|
|
328
484
|
</td>
|
|
@@ -373,7 +529,7 @@
|
|
|
373
529
|
>
|
|
374
530
|
{#each data as rowData, rowIndex (getRowId(rowData, rowIndex))}
|
|
375
531
|
{@const rowId = getRowId(rowData, rowIndex)}
|
|
376
|
-
{@const isSelected = selectable &&
|
|
532
|
+
{@const isSelected = selectable && isRowSelected(rowId)}
|
|
377
533
|
{@const selectDisabled = !!selectDisabledBy?.(rowData, rowIndex)}
|
|
378
534
|
{#if mobileRow}
|
|
379
535
|
{@render mobileRow({
|
|
@@ -412,7 +568,7 @@
|
|
|
412
568
|
type="checkbox"
|
|
413
569
|
checked={isSelected}
|
|
414
570
|
disabled={selectDisabled}
|
|
415
|
-
|
|
571
|
+
onclick={(e) => handleCheckboxClick(rowIndex, e)}
|
|
416
572
|
aria-label={t("select_row")}
|
|
417
573
|
/>
|
|
418
574
|
</div>
|
|
@@ -40,6 +40,22 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
|
|
|
40
40
|
selectOnRowClick?: boolean;
|
|
41
41
|
/** Return true to disable selection for a specific row */
|
|
42
42
|
selectDisabledBy?: (row: T, index: number) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Allow the user to opt into "select all results across all pages" mode.
|
|
45
|
+
* When enabled and `paging.total > data.length`, a banner offers to expand
|
|
46
|
+
* selection beyond the current page. Consumers must execute batch operations
|
|
47
|
+
* as server-side filter queries (not by iterating row IDs) since off-page rows
|
|
48
|
+
* are not available locally.
|
|
49
|
+
*/
|
|
50
|
+
allowSelectAllPages?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* All-pages selection mode (bindable). When true, selection semantics invert:
|
|
53
|
+
* `excluded` holds deselected IDs, and every row not in `excluded` is selected.
|
|
54
|
+
* Newly inserted rows are implicitly selected in this mode.
|
|
55
|
+
*/
|
|
56
|
+
selectedAll?: boolean;
|
|
57
|
+
/** Set of row IDs explicitly deselected while in all-pages mode (bindable) */
|
|
58
|
+
excluded?: Set<string | number>;
|
|
43
59
|
/** Callback when a row is clicked */
|
|
44
60
|
onRowClick?: (row: T, index: number) => void;
|
|
45
61
|
/** Show loading state (spinner overlay + reduced opacity) */
|
|
@@ -63,11 +79,37 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
|
|
|
63
79
|
isSelected: boolean;
|
|
64
80
|
}
|
|
65
81
|
]>;
|
|
66
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Batch actions bar snippet (shown when items are selected).
|
|
84
|
+
*
|
|
85
|
+
* Note: in all-pages mode (`selectedAll === true`) `selectedRows` only contains
|
|
86
|
+
* rows from the current page that aren't excluded. Off-page rows are not
|
|
87
|
+
* materialized — execute batch operations server-side using the active filter
|
|
88
|
+
* minus `excluded`.
|
|
89
|
+
*/
|
|
67
90
|
batchActions?: Snippet<[
|
|
68
91
|
{
|
|
69
92
|
selected: Set<string | number>;
|
|
70
93
|
selectedRows: T[];
|
|
94
|
+
selectedAll: boolean;
|
|
95
|
+
excluded: Set<string | number>;
|
|
96
|
+
/** `selected.size` in normal mode, or `totalItems - excluded.size` in all-pages mode */
|
|
97
|
+
effectiveCount: number;
|
|
98
|
+
totalCount: number | null;
|
|
99
|
+
clearSelection: () => void;
|
|
100
|
+
}
|
|
101
|
+
]>;
|
|
102
|
+
/**
|
|
103
|
+
* Custom "select all results across pages" banner. When omitted, a default
|
|
104
|
+
* banner is rendered.
|
|
105
|
+
*/
|
|
106
|
+
selectAllBanner?: Snippet<[
|
|
107
|
+
{
|
|
108
|
+
selectedAll: boolean;
|
|
109
|
+
effectiveCount: number;
|
|
110
|
+
totalCount: number;
|
|
111
|
+
pageCount: number;
|
|
112
|
+
selectAll: () => void;
|
|
71
113
|
clearSelection: () => void;
|
|
72
114
|
}
|
|
73
115
|
]>;
|
|
@@ -93,7 +135,7 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
|
|
|
93
135
|
declare function $$render<T extends Record<string, any> = Record<string, any>>(): {
|
|
94
136
|
props: Props<T>;
|
|
95
137
|
exports: {};
|
|
96
|
-
bindings: "el" | "selected";
|
|
138
|
+
bindings: "el" | "selected" | "selectedAll" | "excluded";
|
|
97
139
|
slots: {};
|
|
98
140
|
events: {};
|
|
99
141
|
};
|
|
@@ -101,7 +143,7 @@ declare class __sveltets_Render<T extends Record<string, any> = Record<string, a
|
|
|
101
143
|
props(): ReturnType<typeof $$render<T>>['props'];
|
|
102
144
|
events(): ReturnType<typeof $$render<T>>['events'];
|
|
103
145
|
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
104
|
-
bindings(): "el" | "selected";
|
|
146
|
+
bindings(): "el" | "selected" | "selectedAll" | "excluded";
|
|
105
147
|
exports(): {};
|
|
106
148
|
}
|
|
107
149
|
interface $$IsomorphicComponent {
|
|
@@ -62,6 +62,72 @@ DataTable integrates with [`@marianmeres/paging-store`](https://github.com/maria
|
|
|
62
62
|
</DataTable>
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
Shift+click a checkbox to toggle a range of rows from the last clicked checkbox to the current one. Disabled rows in the range are skipped. The anchor resets when `data` changes (e.g. on page navigation).
|
|
66
|
+
|
|
67
|
+
### Select All Across Pages
|
|
68
|
+
|
|
69
|
+
When your data is paged, DataTable can offer a "select all results" affordance that expands selection beyond the current page. Enable it with `allowSelectAllPages`. When the user opts in, selection flips to **all-pages mode**: every row is implicitly selected, and `excluded` holds IDs the user has explicitly deselected.
|
|
70
|
+
|
|
71
|
+
```svelte
|
|
72
|
+
<script lang="ts">
|
|
73
|
+
let selected = $state(new Set<string | number>());
|
|
74
|
+
let selectedAll = $state(false);
|
|
75
|
+
let excluded = $state(new Set<string | number>());
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<DataTable
|
|
79
|
+
{columns}
|
|
80
|
+
{data}
|
|
81
|
+
{paging}
|
|
82
|
+
{onPageChange}
|
|
83
|
+
selectable
|
|
84
|
+
allowSelectAllPages
|
|
85
|
+
bind:selected
|
|
86
|
+
bind:selectedAll
|
|
87
|
+
bind:excluded
|
|
88
|
+
getRowId={(row) => row.id}
|
|
89
|
+
>
|
|
90
|
+
{#snippet batchActions({ selectedAll, excluded, effectiveCount, totalCount, clearSelection })}
|
|
91
|
+
<span>{effectiveCount} selected{selectedAll ? ` of ${totalCount}` : ""}</span>
|
|
92
|
+
<Button onclick={() => deleteSelection({ selectedAll, excluded })}>Delete</Button>
|
|
93
|
+
<Button variant="ghost" onclick={clearSelection}>Clear</Button>
|
|
94
|
+
{/snippet}
|
|
95
|
+
</DataTable>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Important — executing batch operations:** in all-pages mode the off-page rows are not loaded locally, so consumers cannot iterate IDs. Execute operations server-side using the active filter minus `excluded`:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
function deleteSelection({ selectedAll, excluded }) {
|
|
102
|
+
if (selectedAll) {
|
|
103
|
+
// Server-side: DELETE FROM rows WHERE <currentFilter> AND id NOT IN excluded
|
|
104
|
+
return api.delete({ filter: currentFilter, exclude: [...excluded] });
|
|
105
|
+
}
|
|
106
|
+
return api.delete({ ids: [...selected] });
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
New records inserted while all-pages mode is active are implicitly selected (they aren't in `excluded`). This matches the conventional intent — "delete everything matching X" should include matches that arrive before the operation runs. If you need snapshot-at-click semantics, capture a timestamp or ID list in your consumer.
|
|
111
|
+
|
|
112
|
+
**Customising the banner:** the default banner uses the built-in `t()` keys `select_all_on_page_x`, `select_all_results`, `all_results_selected`, and `clear_selection`. Override markup entirely with the `selectAllBanner` snippet:
|
|
113
|
+
|
|
114
|
+
```svelte
|
|
115
|
+
<DataTable {columns} {data} {paging} selectable allowSelectAllPages bind:selected bind:selectedAll bind:excluded>
|
|
116
|
+
{#snippet selectAllBanner({ selectedAll, totalCount, selectAll, clearSelection })}
|
|
117
|
+
<div class="my-banner">
|
|
118
|
+
{#if selectedAll}
|
|
119
|
+
<span>All {totalCount} selected.</span>
|
|
120
|
+
<button onclick={clearSelection}>Undo</button>
|
|
121
|
+
{:else}
|
|
122
|
+
<button onclick={selectAll}>Select all {totalCount}</button>
|
|
123
|
+
{/if}
|
|
124
|
+
</div>
|
|
125
|
+
{/snippet}
|
|
126
|
+
</DataTable>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Filter changes:** when filters change in the consumer, reset the bound selection stores (`selected`, `selectedAll`, `excluded`) explicitly — DataTable doesn't track which filter produced the current state.
|
|
130
|
+
|
|
65
131
|
### Custom Cell Rendering
|
|
66
132
|
|
|
67
133
|
The `cell` snippet is used for both desktop and mobile layouts. Use the `variant` param if rendering differs per layout.
|
|
@@ -134,6 +200,9 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
|
|
|
134
200
|
| `selected` | `Set<string \| number>` | `new Set()` | Selected row IDs (bindable) |
|
|
135
201
|
| `selectOnRowClick` | `boolean` | `false` | Clicking anywhere on a row toggles its selection |
|
|
136
202
|
| `selectDisabledBy` | `(row, index) => boolean` | - | Return `true` to disable selection for a specific row |
|
|
203
|
+
| `allowSelectAllPages` | `boolean` | `false` | Show a banner offering "select all results" across paged data |
|
|
204
|
+
| `selectedAll` | `boolean` | `false` | All-pages mode flag (bindable). In this mode `excluded` drives selection |
|
|
205
|
+
| `excluded` | `Set<string \| number>` | `new Set()` | Deselected row IDs while in all-pages mode (bindable) |
|
|
137
206
|
| `onRowClick` | `(row, index) => void` | - | Row click callback |
|
|
138
207
|
| `loading` | `boolean` | `false` | Show loading overlay |
|
|
139
208
|
| `small` | `boolean` | `false` | Force mobile/card layout regardless of viewport |
|
|
@@ -142,6 +211,7 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
|
|
|
142
211
|
| `row` | `Snippet` | - | Custom desktop `<tr>` renderer (overrides default row) |
|
|
143
212
|
| `mobileRow` | `Snippet` | - | Custom mobile card renderer |
|
|
144
213
|
| `batchActions` | `Snippet` | - | Batch action bar content |
|
|
214
|
+
| `selectAllBanner` | `Snippet` | - | Override default "select all across pages" banner |
|
|
145
215
|
| `empty` | `Snippet` | - | Custom empty state |
|
|
146
216
|
| `unstyled` | `boolean` | `false` | Skip default styling |
|
|
147
217
|
| `class` | `string` | - | Additional CSS classes |
|
|
@@ -149,15 +219,18 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
|
|
|
149
219
|
|
|
150
220
|
### Snippet signatures
|
|
151
221
|
|
|
152
|
-
| Snippet
|
|
153
|
-
|
|
|
154
|
-
| `cell`
|
|
155
|
-
| `row`
|
|
156
|
-
| `mobileRow`
|
|
157
|
-
| `batchActions`
|
|
158
|
-
| `
|
|
222
|
+
| Snippet | Props |
|
|
223
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| `cell` | `{ column, row, value, rowIndex, variant: "desktop" \| "mobile" }` |
|
|
225
|
+
| `row` | `{ row, columns, rowIndex, isSelected }` — desktop only |
|
|
226
|
+
| `mobileRow` | `{ row, columns, rowIndex }` — mobile only |
|
|
227
|
+
| `batchActions` | `{ selected, selectedRows, selectedAll, excluded, effectiveCount, totalCount, clearSelection }` |
|
|
228
|
+
| `selectAllBanner` | `{ selectedAll, effectiveCount, totalCount, pageCount, selectAll, clearSelection }` |
|
|
229
|
+
| `empty` | — |
|
|
159
230
|
|
|
160
|
-
> **Note:** "Select all rows" affects only the rows currently in `data` (i.e. the current page when using external paging). Rows for which `selectDisabledBy` returns `true` are excluded from "select all".
|
|
231
|
+
> **Note:** "Select all rows" affects only the rows currently in `data` (i.e. the current page when using external paging). Rows for which `selectDisabledBy` returns `true` are excluded from "select all". To select across pages, enable `allowSelectAllPages` and use the banner that appears.
|
|
232
|
+
>
|
|
233
|
+
> **Note:** `clearSelection` (exposed by the `batchActions` snippet) resets all selection state — `selected`, `selectedAll`, and `excluded` — including exiting all-pages mode.
|
|
161
234
|
|
|
162
235
|
## DataTableColumn
|
|
163
236
|
|
|
@@ -197,3 +270,6 @@ Replace the entire `<tr>` on desktop. When this snippet is provided, DataTable d
|
|
|
197
270
|
| `--stuic-data-table-card-radius` | `var(--radius-md)` | Mobile card radius |
|
|
198
271
|
| `--stuic-data-table-card-padding` | `0.75rem` | Mobile card padding |
|
|
199
272
|
| `--stuic-data-table-card-gap` | `0.5rem` | Gap between mobile cards |
|
|
273
|
+
| `--stuic-data-table-select-all-bg` | `color-mix(primary 10%)` | Select-all banner background |
|
|
274
|
+
| `--stuic-data-table-select-all-padding-x` | `0.75rem` | Banner horizontal padding |
|
|
275
|
+
| `--stuic-data-table-select-all-padding-y` | `0.5rem` | Banner vertical padding |
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
--stuic-data-table-batch-padding-x: 0.75rem;
|
|
39
39
|
--stuic-data-table-batch-padding-y: 0.5rem;
|
|
40
40
|
|
|
41
|
+
/* Select-all-across-pages banner */
|
|
42
|
+
--stuic-data-table-select-all-bg: color-mix(in srgb, var(--stuic-color-primary) 10%, var(--stuic-color-background));
|
|
43
|
+
--stuic-data-table-select-all-padding-x: 0.75rem;
|
|
44
|
+
--stuic-data-table-select-all-padding-y: 0.5rem;
|
|
45
|
+
|
|
41
46
|
/* Checkbox column */
|
|
42
47
|
--stuic-data-table-checkbox-width: 3rem;
|
|
43
48
|
|
|
@@ -210,6 +215,25 @@
|
|
|
210
215
|
margin-bottom: 0.5rem;
|
|
211
216
|
}
|
|
212
217
|
|
|
218
|
+
/* ============================================================================
|
|
219
|
+
SELECT-ALL-ACROSS-PAGES BANNER
|
|
220
|
+
============================================================================ */
|
|
221
|
+
|
|
222
|
+
.stuic-data-table-select-all-banner {
|
|
223
|
+
display: flex;
|
|
224
|
+
align-items: center;
|
|
225
|
+
justify-content: center;
|
|
226
|
+
gap: var(--stuic-data-table-paging-gap);
|
|
227
|
+
padding: var(--stuic-data-table-select-all-padding-y)
|
|
228
|
+
var(--stuic-data-table-select-all-padding-x);
|
|
229
|
+
background: var(--stuic-data-table-select-all-bg);
|
|
230
|
+
border-radius: var(--stuic-data-table-radius, var(--stuic-radius));
|
|
231
|
+
margin-bottom: 0.5rem;
|
|
232
|
+
font-size: var(--stuic-data-table-cell-font-size);
|
|
233
|
+
color: var(--stuic-data-table-header-color);
|
|
234
|
+
text-align: center;
|
|
235
|
+
}
|
|
236
|
+
|
|
213
237
|
/* ============================================================================
|
|
214
238
|
LOADING SPINNER OVERLAY
|
|
215
239
|
============================================================================ */
|