@marianmeres/stuic 3.66.1 → 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.
- package/dist/actions/autoscroll.d.ts +7 -0
- package/dist/actions/autoscroll.js +7 -0
- package/dist/actions/focus-trap.d.ts +7 -0
- package/dist/actions/focus-trap.js +8 -3
- package/dist/actions/typeahead.svelte.js +40 -4
- package/dist/components/Carousel/Carousel.svelte +9 -2
- package/dist/components/Carousel/README.md +8 -2
- package/dist/components/Cart/Cart.svelte +3 -0
- package/dist/components/Cart/README.md +18 -1
- package/dist/components/Checkout/CheckoutOrderReview.svelte +4 -14
- package/dist/components/Checkout/README.md +184 -0
- package/dist/components/Checkout/_internal/checkout-utils.d.ts +6 -0
- package/dist/components/Checkout/_internal/checkout-utils.js +24 -0
- package/dist/components/Checkout/index.d.ts +1 -1
- package/dist/components/Checkout/index.js +1 -1
- package/dist/components/CommandMenu/CommandMenu.svelte +23 -7
- package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +2 -0
- package/dist/components/CronInput/CronInput.svelte +44 -9
- package/dist/components/CronInput/CronInput.svelte.d.ts +2 -0
- package/dist/components/CronInput/README.md +145 -0
- package/dist/components/CronInput/cron-next-run.svelte.d.ts +11 -0
- package/dist/components/CronInput/cron-next-run.svelte.js +11 -0
- package/dist/components/CronInput/index.css +0 -8
- package/dist/components/DataTable/DataTable.svelte +276 -83
- package/dist/components/DataTable/DataTable.svelte.d.ts +58 -6
- package/dist/components/DataTable/README.md +155 -25
- package/dist/components/DataTable/index.css +31 -0
- package/dist/components/DropdownMenu/DropdownMenu.svelte +43 -26
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +5 -1
- package/dist/components/DropdownMenu/README.md +37 -9
- package/dist/components/Input/FieldAssets.svelte +9 -7
- package/dist/components/Input/FieldAssets.svelte.d.ts +3 -7
- package/dist/components/Input/FieldFile.svelte +13 -7
- package/dist/components/Input/FieldFile.svelte.d.ts +4 -7
- package/dist/components/Input/FieldInput.svelte +10 -8
- package/dist/components/Input/FieldInput.svelte.d.ts +3 -8
- package/dist/components/Input/FieldInputLocalized.svelte +8 -7
- package/dist/components/Input/FieldInputLocalized.svelte.d.ts +2 -7
- package/dist/components/Input/FieldKeyValues.svelte +8 -7
- package/dist/components/Input/FieldKeyValues.svelte.d.ts +2 -7
- package/dist/components/Input/FieldLikeButton.svelte +9 -7
- package/dist/components/Input/FieldLikeButton.svelte.d.ts +3 -7
- package/dist/components/Input/FieldObject.svelte +8 -7
- package/dist/components/Input/FieldObject.svelte.d.ts +2 -7
- package/dist/components/Input/FieldOptions.svelte +9 -7
- package/dist/components/Input/FieldOptions.svelte.d.ts +3 -7
- package/dist/components/Input/FieldPhoneNumber.svelte +7 -8
- package/dist/components/Input/FieldPhoneNumber.svelte.d.ts +3 -8
- package/dist/components/Input/FieldSelect.svelte +9 -8
- package/dist/components/Input/FieldSelect.svelte.d.ts +3 -8
- package/dist/components/Input/FieldSwitch.svelte +9 -7
- package/dist/components/Input/FieldSwitch.svelte.d.ts +3 -7
- package/dist/components/Input/FieldTextarea.svelte +7 -8
- package/dist/components/Input/FieldTextarea.svelte.d.ts +3 -8
- package/dist/components/Input/README.md +20 -0
- package/dist/components/Input/_internal/InputWrap.svelte +2 -10
- package/dist/components/Input/_internal/InputWrap.svelte.d.ts +2 -10
- package/dist/components/Input/types.d.ts +28 -0
- package/dist/components/Nav/Nav.svelte +5 -4
- package/dist/components/Nav/Nav.svelte.d.ts +2 -2
- package/dist/components/Nav/README.md +2 -2
- package/dist/components/Nav/index.css +4 -0
- package/dist/components/Tree/README.md +189 -0
- package/dist/components/Tree/Tree.svelte +46 -2
- package/dist/components/Tree/Tree.svelte.d.ts +5 -0
- package/dist/utils/input-history.svelte.d.ts +12 -0
- package/dist/utils/input-history.svelte.js +12 -0
- package/dist/utils/observe-exists.svelte.d.ts +1 -0
- package/dist/utils/observe-exists.svelte.js +11 -3
- package/dist/utils/switch.svelte.d.ts +12 -0
- package/dist/utils/switch.svelte.js +12 -1
- package/docs/architecture.md +0 -1
- package/docs/testing.md +72 -0
- package/docs/upgrading.md +281 -0
- package/package.json +12 -13
|
@@ -19,8 +19,12 @@
|
|
|
19
19
|
next_page: "Next",
|
|
20
20
|
page_x_of_y: "Page {page} of {pageCount}",
|
|
21
21
|
no_data: "No data",
|
|
22
|
-
select_all_rows: "Select all rows",
|
|
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)
|
|
@@ -71,6 +75,25 @@
|
|
|
71
75
|
selected?: Set<string | number>;
|
|
72
76
|
/** Toggle row selection when clicking anywhere on the row */
|
|
73
77
|
selectOnRowClick?: boolean;
|
|
78
|
+
/** Return true to disable selection for a specific row */
|
|
79
|
+
selectDisabledBy?: (row: T, index: number) => boolean;
|
|
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>;
|
|
74
97
|
|
|
75
98
|
/** Callback when a row is clicked */
|
|
76
99
|
onRowClick?: (row: T, index: number) => void;
|
|
@@ -78,16 +101,63 @@
|
|
|
78
101
|
/** Show loading state (spinner overlay + reduced opacity) */
|
|
79
102
|
loading?: boolean;
|
|
80
103
|
|
|
81
|
-
/** Custom cell renderer snippet */
|
|
104
|
+
/** Custom cell renderer snippet (rendered in both desktop table and mobile card layouts; use `variant` to tell them apart) */
|
|
82
105
|
cell?: Snippet<
|
|
83
|
-
[
|
|
106
|
+
[
|
|
107
|
+
{
|
|
108
|
+
column: DataTableColumn<T>;
|
|
109
|
+
row: T;
|
|
110
|
+
value: any;
|
|
111
|
+
rowIndex: number;
|
|
112
|
+
variant: "desktop" | "mobile";
|
|
113
|
+
},
|
|
114
|
+
]
|
|
84
115
|
>;
|
|
85
|
-
/**
|
|
116
|
+
/** Custom desktop row renderer — replaces the entire `<tr>` */
|
|
117
|
+
row?: Snippet<
|
|
118
|
+
[
|
|
119
|
+
{
|
|
120
|
+
row: T;
|
|
121
|
+
columns: DataTableColumn<T>[];
|
|
122
|
+
rowIndex: number;
|
|
123
|
+
isSelected: boolean;
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
>;
|
|
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
|
+
*/
|
|
86
135
|
batchActions?: Snippet<
|
|
87
136
|
[
|
|
88
137
|
{
|
|
89
138
|
selected: Set<string | number>;
|
|
90
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;
|
|
91
161
|
clearSelection: () => void;
|
|
92
162
|
},
|
|
93
163
|
]
|
|
@@ -96,8 +166,6 @@
|
|
|
96
166
|
empty?: Snippet;
|
|
97
167
|
/** Custom mobile row card snippet */
|
|
98
168
|
mobileRow?: Snippet<[{ row: T; columns: DataTableColumn<T>[]; rowIndex: number }]>;
|
|
99
|
-
/** Default children snippet (not used directly) */
|
|
100
|
-
children?: Snippet;
|
|
101
169
|
|
|
102
170
|
/** Optional translate function */
|
|
103
171
|
t?: TranslateFn;
|
|
@@ -130,13 +198,18 @@
|
|
|
130
198
|
selectable = false,
|
|
131
199
|
selected = $bindable(new Set()),
|
|
132
200
|
selectOnRowClick = false,
|
|
201
|
+
selectDisabledBy,
|
|
202
|
+
allowSelectAllPages = false,
|
|
203
|
+
selectedAll = $bindable(false),
|
|
204
|
+
excluded = $bindable(new Set()),
|
|
133
205
|
onRowClick,
|
|
134
206
|
loading = false,
|
|
135
207
|
cell,
|
|
208
|
+
row,
|
|
136
209
|
batchActions,
|
|
210
|
+
selectAllBanner,
|
|
137
211
|
empty,
|
|
138
212
|
mobileRow,
|
|
139
|
-
children,
|
|
140
213
|
t = t_default,
|
|
141
214
|
small = false,
|
|
142
215
|
unstyled = false,
|
|
@@ -154,48 +227,121 @@
|
|
|
154
227
|
|
|
155
228
|
// --- Selection ---
|
|
156
229
|
let allRowIds = $derived(data.map((row, i) => getRowId(row, i)));
|
|
230
|
+
let selectableRowIds = $derived.by(() => {
|
|
231
|
+
if (!selectDisabledBy) return allRowIds;
|
|
232
|
+
return data
|
|
233
|
+
.map((row, i) => (selectDisabledBy(row, i) ? null : getRowId(row, i)))
|
|
234
|
+
.filter((id): id is string | number => id !== null);
|
|
235
|
+
});
|
|
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
|
+
}
|
|
157
259
|
|
|
158
260
|
let allOnPageSelected = $derived.by(() => {
|
|
159
|
-
if (!selectable ||
|
|
160
|
-
return
|
|
261
|
+
if (!selectable || selectableRowIds.length === 0) return false;
|
|
262
|
+
return selectableRowIds.every((id) => isRowSelected(id));
|
|
161
263
|
});
|
|
162
264
|
|
|
163
265
|
let someOnPageSelected = $derived.by(() => {
|
|
164
|
-
if (!selectable ||
|
|
165
|
-
return
|
|
266
|
+
if (!selectable || selectableRowIds.length === 0) return false;
|
|
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;
|
|
166
277
|
});
|
|
167
278
|
|
|
168
279
|
let selectedRows = $derived.by(() => {
|
|
169
|
-
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[];
|
|
170
285
|
return data.filter((row, i) => selected.has(getRowId(row, i)));
|
|
171
286
|
});
|
|
172
287
|
|
|
173
288
|
function toggleSelectAll() {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
} else {
|
|
179
|
-
const next = new Set(selected);
|
|
180
|
-
for (const id of allRowIds) next.add(id);
|
|
181
|
-
selected = next;
|
|
289
|
+
// In all-mode the header checkbox exits the mode entirely.
|
|
290
|
+
if (selectedAll) {
|
|
291
|
+
clearAllSelection();
|
|
292
|
+
return;
|
|
182
293
|
}
|
|
294
|
+
setRowsSelected(selectableRowIds, !allOnPageSelected);
|
|
183
295
|
}
|
|
184
296
|
|
|
185
297
|
function toggleSelectRow(id: string | number) {
|
|
186
|
-
|
|
187
|
-
if (next.has(id)) {
|
|
188
|
-
next.delete(id);
|
|
189
|
-
} else {
|
|
190
|
-
next.add(id);
|
|
191
|
-
}
|
|
192
|
-
selected = next;
|
|
298
|
+
setRowsSelected([id], !isRowSelected(id));
|
|
193
299
|
}
|
|
194
300
|
|
|
195
|
-
function
|
|
301
|
+
function enterSelectAll() {
|
|
196
302
|
selected = new Set();
|
|
303
|
+
excluded = new Set();
|
|
304
|
+
selectedAll = true;
|
|
197
305
|
}
|
|
198
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;
|
|
336
|
+
}
|
|
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
|
+
|
|
199
345
|
// --- Row click ---
|
|
200
346
|
function handleRowClick(row: T, index: number, e: MouseEvent) {
|
|
201
347
|
const target = e.target as HTMLElement;
|
|
@@ -230,12 +376,48 @@
|
|
|
230
376
|
</script>
|
|
231
377
|
|
|
232
378
|
<!-- Batch action bar -->
|
|
233
|
-
{#if selectable &&
|
|
379
|
+
{#if selectable && effectiveCount > 0 && batchActions}
|
|
234
380
|
<div class={!unstyled ? "stuic-data-table-batch" : undefined}>
|
|
235
|
-
{@render batchActions({
|
|
381
|
+
{@render batchActions({
|
|
382
|
+
selected,
|
|
383
|
+
selectedRows,
|
|
384
|
+
selectedAll,
|
|
385
|
+
excluded,
|
|
386
|
+
effectiveCount,
|
|
387
|
+
totalCount,
|
|
388
|
+
clearSelection: clearAllSelection,
|
|
389
|
+
})}
|
|
236
390
|
</div>
|
|
237
391
|
{/if}
|
|
238
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
|
+
|
|
239
421
|
<!-- Root container -->
|
|
240
422
|
<div bind:this={el} class={rootClass} {...rest}>
|
|
241
423
|
{#if isDesktop}
|
|
@@ -248,7 +430,7 @@
|
|
|
248
430
|
<thead>
|
|
249
431
|
<tr>
|
|
250
432
|
{#if selectable}
|
|
251
|
-
<th data-checkbox class="stuic-checkbox">
|
|
433
|
+
<th scope="col" data-checkbox class="stuic-checkbox">
|
|
252
434
|
<input
|
|
253
435
|
type="checkbox"
|
|
254
436
|
checked={allOnPageSelected}
|
|
@@ -260,6 +442,7 @@
|
|
|
260
442
|
{/if}
|
|
261
443
|
{#each columns as col (col.key)}
|
|
262
444
|
<th
|
|
445
|
+
scope="col"
|
|
263
446
|
class={col.classHeader}
|
|
264
447
|
data-align={!unstyled && col.align ? col.align : undefined}
|
|
265
448
|
style={col.width ? `width: ${col.width}` : undefined}
|
|
@@ -274,46 +457,53 @@
|
|
|
274
457
|
</tr>
|
|
275
458
|
</thead>
|
|
276
459
|
<tbody>
|
|
277
|
-
{#each data as
|
|
278
|
-
{@const rowId = getRowId(
|
|
279
|
-
{@const isSelected = selectable &&
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
{
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
460
|
+
{#each data as rowData, rowIndex (getRowId(rowData, rowIndex))}
|
|
461
|
+
{@const rowId = getRowId(rowData, rowIndex)}
|
|
462
|
+
{@const isSelected = selectable && isRowSelected(rowId)}
|
|
463
|
+
{@const selectDisabled = !!selectDisabledBy?.(rowData, rowIndex)}
|
|
464
|
+
{#if row}
|
|
465
|
+
{@render row({ row: rowData, columns, rowIndex, isSelected })}
|
|
466
|
+
{:else}
|
|
467
|
+
<tr
|
|
468
|
+
data-hoverable={!unstyled ? "true" : undefined}
|
|
469
|
+
data-clickable={!unstyled && (onRowClick || selectOnRowClick)
|
|
470
|
+
? "true"
|
|
471
|
+
: undefined}
|
|
472
|
+
data-selected={!unstyled && isSelected ? "true" : undefined}
|
|
473
|
+
onclick={(e) => handleRowClick(rowData, rowIndex, e)}
|
|
474
|
+
>
|
|
475
|
+
{#if selectable}
|
|
476
|
+
<td data-checkbox class="stuic-checkbox">
|
|
477
|
+
<input
|
|
478
|
+
type="checkbox"
|
|
479
|
+
checked={isSelected}
|
|
480
|
+
disabled={selectDisabled}
|
|
481
|
+
onclick={(e) => handleCheckboxClick(rowIndex, e)}
|
|
482
|
+
aria-label={t("select_row")}
|
|
483
|
+
/>
|
|
484
|
+
</td>
|
|
485
|
+
{/if}
|
|
486
|
+
{#each columns as col (col.key)}
|
|
487
|
+
{@const value = getCellValue(rowData, col)}
|
|
488
|
+
<td
|
|
489
|
+
class={col.class}
|
|
490
|
+
data-align={!unstyled && col.align ? col.align : undefined}
|
|
491
|
+
>
|
|
492
|
+
{#if cell}
|
|
493
|
+
{@render cell({
|
|
494
|
+
column: col,
|
|
495
|
+
row: rowData,
|
|
496
|
+
value,
|
|
497
|
+
rowIndex,
|
|
498
|
+
variant: "desktop",
|
|
499
|
+
})}
|
|
500
|
+
{:else}
|
|
501
|
+
{getCellDisplay(rowData, col)}
|
|
502
|
+
{/if}
|
|
503
|
+
</td>
|
|
504
|
+
{/each}
|
|
505
|
+
</tr>
|
|
506
|
+
{/if}
|
|
317
507
|
{:else}
|
|
318
508
|
<tr>
|
|
319
509
|
<td
|
|
@@ -337,12 +527,13 @@
|
|
|
337
527
|
class={!unstyled ? "stuic-data-table-cards" : undefined}
|
|
338
528
|
data-loading={!unstyled && loading ? "true" : undefined}
|
|
339
529
|
>
|
|
340
|
-
{#each data as
|
|
341
|
-
{@const rowId = getRowId(
|
|
342
|
-
{@const isSelected = selectable &&
|
|
530
|
+
{#each data as rowData, rowIndex (getRowId(rowData, rowIndex))}
|
|
531
|
+
{@const rowId = getRowId(rowData, rowIndex)}
|
|
532
|
+
{@const isSelected = selectable && isRowSelected(rowId)}
|
|
533
|
+
{@const selectDisabled = !!selectDisabledBy?.(rowData, rowIndex)}
|
|
343
534
|
{#if mobileRow}
|
|
344
535
|
{@render mobileRow({
|
|
345
|
-
row,
|
|
536
|
+
row: rowData,
|
|
346
537
|
columns: mobileColumns,
|
|
347
538
|
rowIndex,
|
|
348
539
|
})}
|
|
@@ -357,7 +548,7 @@
|
|
|
357
548
|
data-selected={!unstyled && isSelected ? "true" : undefined}
|
|
358
549
|
role={onRowClick || selectOnRowClick ? "button" : undefined}
|
|
359
550
|
tabindex={onRowClick || selectOnRowClick ? 0 : undefined}
|
|
360
|
-
onclick={(e) => handleRowClick(
|
|
551
|
+
onclick={(e) => handleRowClick(rowData, rowIndex, e)}
|
|
361
552
|
onkeydown={(e) => {
|
|
362
553
|
if (
|
|
363
554
|
(onRowClick || selectOnRowClick) &&
|
|
@@ -367,22 +558,23 @@
|
|
|
367
558
|
if (selectable && selectOnRowClick) {
|
|
368
559
|
toggleSelectRow(rowId);
|
|
369
560
|
}
|
|
370
|
-
onRowClick?.(
|
|
561
|
+
onRowClick?.(rowData, rowIndex);
|
|
371
562
|
}
|
|
372
563
|
}}
|
|
373
564
|
>
|
|
374
565
|
{#if selectable}
|
|
375
|
-
<div class="stuic-checkbox
|
|
566
|
+
<div class={!unstyled ? "stuic-checkbox stuic-data-table-card-checkbox" : undefined}>
|
|
376
567
|
<input
|
|
377
568
|
type="checkbox"
|
|
378
569
|
checked={isSelected}
|
|
379
|
-
|
|
570
|
+
disabled={selectDisabled}
|
|
571
|
+
onclick={(e) => handleCheckboxClick(rowIndex, e)}
|
|
380
572
|
aria-label={t("select_row")}
|
|
381
573
|
/>
|
|
382
574
|
</div>
|
|
383
575
|
{/if}
|
|
384
576
|
{#each mobileColumns as col (col.key)}
|
|
385
|
-
{@const value = getCellValue(
|
|
577
|
+
{@const value = getCellValue(rowData, col)}
|
|
386
578
|
<div class={!unstyled ? "stuic-data-table-card-row" : undefined}>
|
|
387
579
|
<span class={!unstyled ? "stuic-data-table-card-label" : undefined}>
|
|
388
580
|
{#if isTHCNotEmpty(col.label)}
|
|
@@ -395,12 +587,13 @@
|
|
|
395
587
|
{#if cell}
|
|
396
588
|
{@render cell({
|
|
397
589
|
column: col,
|
|
398
|
-
row,
|
|
590
|
+
row: rowData,
|
|
399
591
|
value,
|
|
400
592
|
rowIndex,
|
|
593
|
+
variant: "mobile",
|
|
401
594
|
})}
|
|
402
595
|
{:else}
|
|
403
|
-
{getCellDisplay(
|
|
596
|
+
{getCellDisplay(rowData, col)}
|
|
404
597
|
{/if}
|
|
405
598
|
</span>
|
|
406
599
|
</div>
|
|
@@ -412,7 +605,7 @@
|
|
|
412
605
|
{#if empty}
|
|
413
606
|
{@render empty()}
|
|
414
607
|
{:else}
|
|
415
|
-
|
|
608
|
+
{t("no_data")}
|
|
416
609
|
{/if}
|
|
417
610
|
</div>
|
|
418
611
|
{/each}
|
|
@@ -38,24 +38,78 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
|
|
|
38
38
|
selected?: Set<string | number>;
|
|
39
39
|
/** Toggle row selection when clicking anywhere on the row */
|
|
40
40
|
selectOnRowClick?: boolean;
|
|
41
|
+
/** Return true to disable selection for a specific row */
|
|
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>;
|
|
41
59
|
/** Callback when a row is clicked */
|
|
42
60
|
onRowClick?: (row: T, index: number) => void;
|
|
43
61
|
/** Show loading state (spinner overlay + reduced opacity) */
|
|
44
62
|
loading?: boolean;
|
|
45
|
-
/** Custom cell renderer snippet */
|
|
63
|
+
/** Custom cell renderer snippet (rendered in both desktop table and mobile card layouts; use `variant` to tell them apart) */
|
|
46
64
|
cell?: Snippet<[
|
|
47
65
|
{
|
|
48
66
|
column: DataTableColumn<T>;
|
|
49
67
|
row: T;
|
|
50
68
|
value: any;
|
|
51
69
|
rowIndex: number;
|
|
70
|
+
variant: "desktop" | "mobile";
|
|
52
71
|
}
|
|
53
72
|
]>;
|
|
54
|
-
/**
|
|
73
|
+
/** Custom desktop row renderer — replaces the entire `<tr>` */
|
|
74
|
+
row?: Snippet<[
|
|
75
|
+
{
|
|
76
|
+
row: T;
|
|
77
|
+
columns: DataTableColumn<T>[];
|
|
78
|
+
rowIndex: number;
|
|
79
|
+
isSelected: boolean;
|
|
80
|
+
}
|
|
81
|
+
]>;
|
|
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
|
+
*/
|
|
55
90
|
batchActions?: Snippet<[
|
|
56
91
|
{
|
|
57
92
|
selected: Set<string | number>;
|
|
58
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;
|
|
59
113
|
clearSelection: () => void;
|
|
60
114
|
}
|
|
61
115
|
]>;
|
|
@@ -67,8 +121,6 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
|
|
|
67
121
|
columns: DataTableColumn<T>[];
|
|
68
122
|
rowIndex: number;
|
|
69
123
|
}]>;
|
|
70
|
-
/** Default children snippet (not used directly) */
|
|
71
|
-
children?: Snippet;
|
|
72
124
|
/** Optional translate function */
|
|
73
125
|
t?: TranslateFn;
|
|
74
126
|
/** Force mobile/card layout regardless of breakpoint */
|
|
@@ -83,7 +135,7 @@ export interface Props<T = Record<string, any>> extends Omit<HTMLAttributes<HTML
|
|
|
83
135
|
declare function $$render<T extends Record<string, any> = Record<string, any>>(): {
|
|
84
136
|
props: Props<T>;
|
|
85
137
|
exports: {};
|
|
86
|
-
bindings: "el" | "selected";
|
|
138
|
+
bindings: "el" | "selected" | "selectedAll" | "excluded";
|
|
87
139
|
slots: {};
|
|
88
140
|
events: {};
|
|
89
141
|
};
|
|
@@ -91,7 +143,7 @@ declare class __sveltets_Render<T extends Record<string, any> = Record<string, a
|
|
|
91
143
|
props(): ReturnType<typeof $$render<T>>['props'];
|
|
92
144
|
events(): ReturnType<typeof $$render<T>>['events'];
|
|
93
145
|
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
94
|
-
bindings(): "el" | "selected";
|
|
146
|
+
bindings(): "el" | "selected" | "selectedAll" | "excluded";
|
|
95
147
|
exports(): {};
|
|
96
148
|
}
|
|
97
149
|
interface $$IsomorphicComponent {
|