runeforge 0.0.36 → 0.0.38
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/common/Header.svelte +1 -1
- package/dist/components/crud/GenericCRUD.svelte +24 -2
- package/dist/components/crud/views/List.svelte +243 -75
- package/dist/components/crud/views/List.svelte.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/types/crud.d.ts +34 -4
- package/package.json +1 -1
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
CustomAction,
|
|
22
22
|
CustomBulkAction,
|
|
23
23
|
FieldDefinition,
|
|
24
|
-
SearchConfiguration
|
|
24
|
+
SearchConfiguration,
|
|
25
|
+
ViewBasedCustomBulkAction
|
|
25
26
|
} from '../../types/crud.js';
|
|
26
27
|
import type {
|
|
27
28
|
FilterSnapshot,
|
|
@@ -121,6 +122,9 @@
|
|
|
121
122
|
);
|
|
122
123
|
|
|
123
124
|
let activeAction = $state<{ action: CustomAction<T>; item: T } | null>(null);
|
|
125
|
+
let activeBulkAction = $state<{ action: ViewBasedCustomBulkAction<T>; items: T[] } | null>(
|
|
126
|
+
null
|
|
127
|
+
);
|
|
124
128
|
|
|
125
129
|
async function runAction(action: CustomAction<T>, item: T) {
|
|
126
130
|
if (!(action.condition?.(item) ?? true)) return;
|
|
@@ -131,6 +135,10 @@
|
|
|
131
135
|
activeAction = { action, item };
|
|
132
136
|
}
|
|
133
137
|
|
|
138
|
+
function runBulkAction(action: ViewBasedCustomBulkAction<T>, items: T[]) {
|
|
139
|
+
activeBulkAction = { action, items };
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
async function navList() {
|
|
135
143
|
await goto('?');
|
|
136
144
|
}
|
|
@@ -293,7 +301,9 @@
|
|
|
293
301
|
);
|
|
294
302
|
|
|
295
303
|
const serverError = $derived(
|
|
296
|
-
creating || reading || editing || activeAction !== null
|
|
304
|
+
creating || reading || editing || activeAction !== null || activeBulkAction !== null
|
|
305
|
+
? (form?.error ?? '')
|
|
306
|
+
: ''
|
|
297
307
|
);
|
|
298
308
|
</script>
|
|
299
309
|
|
|
@@ -359,6 +369,7 @@
|
|
|
359
369
|
onEdit={navEdit}
|
|
360
370
|
onView={navRead}
|
|
361
371
|
onAction={runAction}
|
|
372
|
+
onBulkAction={runBulkAction}
|
|
362
373
|
/>
|
|
363
374
|
{/if}
|
|
364
375
|
|
|
@@ -373,3 +384,14 @@
|
|
|
373
384
|
onSuccess={() => (activeAction = null)}
|
|
374
385
|
/>
|
|
375
386
|
{/if}
|
|
387
|
+
|
|
388
|
+
{#if activeBulkAction !== null}
|
|
389
|
+
{@const BulkActionView = activeBulkAction.action.view}
|
|
390
|
+
<BulkActionView
|
|
391
|
+
items={activeBulkAction.items}
|
|
392
|
+
label={activeBulkAction.action.label}
|
|
393
|
+
{serverError}
|
|
394
|
+
onCancel={() => (activeBulkAction = null)}
|
|
395
|
+
onSuccess={() => (activeBulkAction = null)}
|
|
396
|
+
/>
|
|
397
|
+
{/if}
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
CustomAction,
|
|
16
16
|
CustomBulkAction,
|
|
17
17
|
RowAction,
|
|
18
|
-
SearchConfiguration
|
|
18
|
+
SearchConfiguration,
|
|
19
|
+
ViewBasedCustomBulkAction
|
|
19
20
|
} from '../../../types/crud.js';
|
|
20
21
|
import type {
|
|
21
22
|
FilterSnapshot,
|
|
@@ -27,6 +28,12 @@
|
|
|
27
28
|
|
|
28
29
|
const strings = getStrings();
|
|
29
30
|
|
|
31
|
+
function isViewBasedBulkAction(
|
|
32
|
+
action: CustomBulkAction<T>
|
|
33
|
+
): action is ViewBasedCustomBulkAction<T> {
|
|
34
|
+
return action.kind === 'view';
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
let {
|
|
31
38
|
data = [] as T[],
|
|
32
39
|
labelOne = '',
|
|
@@ -52,7 +59,8 @@
|
|
|
52
59
|
onCreate,
|
|
53
60
|
onEdit,
|
|
54
61
|
onView,
|
|
55
|
-
onAction
|
|
62
|
+
onAction,
|
|
63
|
+
onBulkAction
|
|
56
64
|
}: {
|
|
57
65
|
data?: T[];
|
|
58
66
|
labelOne?: string;
|
|
@@ -85,6 +93,7 @@
|
|
|
85
93
|
onEdit?: (item: T) => void;
|
|
86
94
|
onView?: (item: T) => void;
|
|
87
95
|
onAction?: (action: CustomAction<T>, item: T) => void;
|
|
96
|
+
onBulkAction?: (action: ViewBasedCustomBulkAction<T>, items: T[]) => void;
|
|
88
97
|
} = $props();
|
|
89
98
|
|
|
90
99
|
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
@@ -97,7 +106,12 @@
|
|
|
97
106
|
const updateLabel = $derived(update.label ?? strings.edit);
|
|
98
107
|
const readLabel = $derived(read.label ?? strings.view);
|
|
99
108
|
const showRowActions = $derived(allowRead || allowUpdate || allowDelete || actions.length > 0);
|
|
100
|
-
const allowSelection = $derived(
|
|
109
|
+
const allowSelection = $derived(
|
|
110
|
+
allowDelete || customBulkActions.some((action) => !isViewBasedBulkAction(action))
|
|
111
|
+
);
|
|
112
|
+
const hasCollapsibleActions = $derived(
|
|
113
|
+
enableExport || customBulkActions.length > 0 || allowDelete
|
|
114
|
+
);
|
|
101
115
|
|
|
102
116
|
let selected = new SvelteSet<number>();
|
|
103
117
|
let pendingDeletion = $state<T[] | null>(null);
|
|
@@ -107,7 +121,33 @@
|
|
|
107
121
|
let visibleRows = $state<T[]>([]);
|
|
108
122
|
let exportQuery = $state<TableQuery | undefined>(undefined);
|
|
109
123
|
let exportPopoverEl: HTMLElement | undefined = $state();
|
|
124
|
+
let actionsPopoverEl: HTMLElement | undefined = $state();
|
|
110
125
|
const exportId = $props.id();
|
|
126
|
+
const actionsMenuId = `${exportId}-actions`;
|
|
127
|
+
|
|
128
|
+
// Collapses export/bulk-actions/delete into a single "Acciones" dropdown
|
|
129
|
+
// once the toolbar no longer fits its available width (small monitor, a
|
|
130
|
+
// browser window not maximized, many customBulkActions, ...) — measured
|
|
131
|
+
// against an identical but off-flow, always-uncollapsed clone rather than
|
|
132
|
+
// the visible row itself, since the visible row's own width changes
|
|
133
|
+
// depending on whether it's currently collapsed.
|
|
134
|
+
let toolbarEl: HTMLElement | undefined = $state();
|
|
135
|
+
let toolbarMeasureEl: HTMLElement | undefined = $state();
|
|
136
|
+
let toolbarCollapsed = $state(false);
|
|
137
|
+
|
|
138
|
+
$effect(() => {
|
|
139
|
+
if (!toolbarEl || !toolbarMeasureEl) return;
|
|
140
|
+
const el = toolbarEl;
|
|
141
|
+
const measureEl = toolbarMeasureEl;
|
|
142
|
+
const check = () => {
|
|
143
|
+
toolbarCollapsed = measureEl.scrollWidth > el.clientWidth + 1;
|
|
144
|
+
};
|
|
145
|
+
const observer = new ResizeObserver(check);
|
|
146
|
+
observer.observe(el);
|
|
147
|
+
observer.observe(measureEl);
|
|
148
|
+
check();
|
|
149
|
+
return () => observer.disconnect();
|
|
150
|
+
});
|
|
111
151
|
|
|
112
152
|
async function resolveExportRows(): Promise<T[]> {
|
|
113
153
|
if (!pagination) return visibleRows;
|
|
@@ -179,11 +219,12 @@
|
|
|
179
219
|
}
|
|
180
220
|
|
|
181
221
|
async function runBulkAction(action: CustomBulkAction<T>, items: T[]) {
|
|
222
|
+
if (isViewBasedBulkAction(action)) return;
|
|
182
223
|
await runEndpointAction(action.endpoint, items);
|
|
183
224
|
}
|
|
184
225
|
|
|
185
226
|
function requestBulkAction(action: CustomBulkAction<T>, items: T[]) {
|
|
186
|
-
if (action.confirm) {
|
|
227
|
+
if (!isViewBasedBulkAction(action) && action.confirm) {
|
|
187
228
|
pendingBulkAction = { action, items };
|
|
188
229
|
} else {
|
|
189
230
|
runBulkAction(action, items);
|
|
@@ -192,6 +233,10 @@
|
|
|
192
233
|
|
|
193
234
|
function handleBulkAction(action: CustomBulkAction<T>) {
|
|
194
235
|
const items = selectedItems;
|
|
236
|
+
if (isViewBasedBulkAction(action)) {
|
|
237
|
+
onBulkAction?.(action, items);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
195
240
|
selected.clear();
|
|
196
241
|
requestBulkAction(action, items);
|
|
197
242
|
}
|
|
@@ -231,6 +276,97 @@
|
|
|
231
276
|
]);
|
|
232
277
|
</script>
|
|
233
278
|
|
|
279
|
+
{#snippet toolbarButtons(measuring: boolean)}
|
|
280
|
+
{#if search}
|
|
281
|
+
<SearchInput config={search} />
|
|
282
|
+
{/if}
|
|
283
|
+
|
|
284
|
+
{#if enableExport}
|
|
285
|
+
{@const ExportIcon = icons.download}
|
|
286
|
+
<Button
|
|
287
|
+
variant="ghost"
|
|
288
|
+
class="btn-square"
|
|
289
|
+
popovertarget={measuring ? undefined : `export-menu-${exportId}`}
|
|
290
|
+
style={measuring ? undefined : `anchor-name:--export-anchor-${exportId}`}
|
|
291
|
+
aria-label={strings.export}
|
|
292
|
+
title={strings.export}
|
|
293
|
+
>
|
|
294
|
+
<ExportIcon class="size-4" />
|
|
295
|
+
</Button>
|
|
296
|
+
{#if !measuring}
|
|
297
|
+
<div
|
|
298
|
+
popover="auto"
|
|
299
|
+
id="export-menu-{exportId}"
|
|
300
|
+
style="position-anchor:--export-anchor-{exportId}"
|
|
301
|
+
class="dropdown dropdown-end w-40 rounded-box border border-base-content/10 bg-base-100 p-1 shadow-lg"
|
|
302
|
+
bind:this={exportPopoverEl}
|
|
303
|
+
>
|
|
304
|
+
<Button
|
|
305
|
+
variant="ghost"
|
|
306
|
+
class="btn-sm w-full justify-start"
|
|
307
|
+
onclick={() => handleExport('csv')}
|
|
308
|
+
>
|
|
309
|
+
{strings.exportCsv}
|
|
310
|
+
</Button>
|
|
311
|
+
{#if xlsx}
|
|
312
|
+
<Button
|
|
313
|
+
variant="ghost"
|
|
314
|
+
class="btn-sm w-full justify-start"
|
|
315
|
+
onclick={() => handleExport('xlsx')}
|
|
316
|
+
>
|
|
317
|
+
{strings.exportExcel}
|
|
318
|
+
</Button>
|
|
319
|
+
{/if}
|
|
320
|
+
</div>
|
|
321
|
+
{/if}
|
|
322
|
+
{/if}
|
|
323
|
+
|
|
324
|
+
{#each customBulkActions as bulkAction, i (i)}
|
|
325
|
+
{#if bulkAction.condition?.(selectedItems) ?? true}
|
|
326
|
+
{@const BulkIcon = bulkAction.icon}
|
|
327
|
+
{@const isView = isViewBasedBulkAction(bulkAction)}
|
|
328
|
+
<Button
|
|
329
|
+
variant={bulkAction.variant ?? 'ghost'}
|
|
330
|
+
class="btn-outline"
|
|
331
|
+
disabled={!isView && selected.size === 0}
|
|
332
|
+
title={bulkAction.tooltip ?? bulkAction.label}
|
|
333
|
+
aria-label={bulkAction.tooltip ?? bulkAction.label}
|
|
334
|
+
onclick={measuring ? undefined : () => handleBulkAction(bulkAction)}
|
|
335
|
+
>
|
|
336
|
+
<BulkIcon class="size-4" />
|
|
337
|
+
{#if bulkAction.label}
|
|
338
|
+
{bulkAction.label}{#if !isView}
|
|
339
|
+
({selected.size})
|
|
340
|
+
{/if}
|
|
341
|
+
{/if}
|
|
342
|
+
</Button>
|
|
343
|
+
{/if}
|
|
344
|
+
{/each}
|
|
345
|
+
|
|
346
|
+
{#if allowDelete}
|
|
347
|
+
{@const DeleteIcon = icons.delete}
|
|
348
|
+
<Button
|
|
349
|
+
variant="error"
|
|
350
|
+
class="btn-outline"
|
|
351
|
+
disabled={selected.size === 0}
|
|
352
|
+
onclick={measuring ? undefined : handleDelete}
|
|
353
|
+
>
|
|
354
|
+
<DeleteIcon class="size-4" />
|
|
355
|
+
{deleteLabel} ({selected.size})
|
|
356
|
+
</Button>
|
|
357
|
+
{/if}
|
|
358
|
+
|
|
359
|
+
{@const CreateIcon = icons.create}
|
|
360
|
+
<Button variant="primary" onclick={measuring ? undefined : handleCreate}>
|
|
361
|
+
<CreateIcon class="size-5" />
|
|
362
|
+
{#if creation.label}
|
|
363
|
+
<span>{creation.label}</span>
|
|
364
|
+
{:else}
|
|
365
|
+
<span>{strings.create}<span class="hidden sm:inline"> {labelOne}</span></span>
|
|
366
|
+
{/if}
|
|
367
|
+
</Button>
|
|
368
|
+
{/snippet}
|
|
369
|
+
|
|
234
370
|
{#snippet actionsCell(item: T)}
|
|
235
371
|
<div class="flex justify-end items-center gap-1">
|
|
236
372
|
{#each rowActions as action (action.label)}
|
|
@@ -276,85 +412,115 @@
|
|
|
276
412
|
breadcrumbs={[{ label: labelMany, icon: entityIcon, link: { href: '#' }, prominent: true }]}
|
|
277
413
|
>
|
|
278
414
|
{#snippet buttons()}
|
|
279
|
-
{
|
|
280
|
-
|
|
281
|
-
|
|
415
|
+
<div bind:this={toolbarEl} class="flex flex-wrap items-center gap-2 overflow-hidden">
|
|
416
|
+
{#if toolbarCollapsed}
|
|
417
|
+
{#if search}
|
|
418
|
+
<SearchInput config={search} />
|
|
419
|
+
{/if}
|
|
282
420
|
|
|
283
|
-
|
|
284
|
-
{@const ExportIcon = icons.download}
|
|
285
|
-
<Button
|
|
286
|
-
variant="ghost"
|
|
287
|
-
class="btn-square"
|
|
288
|
-
popovertarget="export-menu-{exportId}"
|
|
289
|
-
style="anchor-name:--export-anchor-{exportId}"
|
|
290
|
-
aria-label={strings.export}
|
|
291
|
-
title={strings.export}
|
|
292
|
-
>
|
|
293
|
-
<ExportIcon class="size-4" />
|
|
294
|
-
</Button>
|
|
295
|
-
<div
|
|
296
|
-
popover="auto"
|
|
297
|
-
id="export-menu-{exportId}"
|
|
298
|
-
style="position-anchor:--export-anchor-{exportId}"
|
|
299
|
-
class="dropdown dropdown-end w-40 rounded-box border border-base-content/10 bg-base-100 p-1 shadow-lg"
|
|
300
|
-
bind:this={exportPopoverEl}
|
|
301
|
-
>
|
|
302
|
-
<Button
|
|
303
|
-
variant="ghost"
|
|
304
|
-
class="btn-sm w-full justify-start"
|
|
305
|
-
onclick={() => handleExport('csv')}
|
|
306
|
-
>
|
|
307
|
-
{strings.exportCsv}
|
|
308
|
-
</Button>
|
|
309
|
-
{#if xlsx}
|
|
421
|
+
{#if hasCollapsibleActions}
|
|
310
422
|
<Button
|
|
311
423
|
variant="ghost"
|
|
312
|
-
class="btn-
|
|
313
|
-
|
|
424
|
+
class="btn-square"
|
|
425
|
+
popovertarget="actions-menu-{actionsMenuId}"
|
|
426
|
+
style="anchor-name:--actions-anchor-{actionsMenuId}"
|
|
427
|
+
aria-label={strings.actions}
|
|
428
|
+
title={strings.actions}
|
|
314
429
|
>
|
|
315
|
-
|
|
430
|
+
<span class="text-lg leading-none" aria-hidden="true">⋯</span>
|
|
316
431
|
</Button>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
432
|
+
<div
|
|
433
|
+
popover="auto"
|
|
434
|
+
id="actions-menu-{actionsMenuId}"
|
|
435
|
+
style="position-anchor:--actions-anchor-{actionsMenuId}"
|
|
436
|
+
class="dropdown dropdown-end w-56 rounded-box border border-base-content/10 bg-base-100 p-1 shadow-lg"
|
|
437
|
+
bind:this={actionsPopoverEl}
|
|
438
|
+
>
|
|
439
|
+
{#if enableExport}
|
|
440
|
+
<Button
|
|
441
|
+
variant="ghost"
|
|
442
|
+
class="btn-sm w-full justify-start"
|
|
443
|
+
onclick={() => {
|
|
444
|
+
actionsPopoverEl?.hidePopover();
|
|
445
|
+
handleExport('csv');
|
|
446
|
+
}}
|
|
447
|
+
>
|
|
448
|
+
{strings.exportCsv}
|
|
449
|
+
</Button>
|
|
450
|
+
{#if xlsx}
|
|
451
|
+
<Button
|
|
452
|
+
variant="ghost"
|
|
453
|
+
class="btn-sm w-full justify-start"
|
|
454
|
+
onclick={() => {
|
|
455
|
+
actionsPopoverEl?.hidePopover();
|
|
456
|
+
handleExport('xlsx');
|
|
457
|
+
}}
|
|
458
|
+
>
|
|
459
|
+
{strings.exportExcel}
|
|
460
|
+
</Button>
|
|
461
|
+
{/if}
|
|
462
|
+
{/if}
|
|
320
463
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
464
|
+
{#each customBulkActions as bulkAction, i (i)}
|
|
465
|
+
{#if bulkAction.condition?.(selectedItems) ?? true}
|
|
466
|
+
{@const BulkIcon = bulkAction.icon}
|
|
467
|
+
{@const isView = isViewBasedBulkAction(bulkAction)}
|
|
468
|
+
<Button
|
|
469
|
+
variant="ghost"
|
|
470
|
+
class="btn-sm w-full justify-start"
|
|
471
|
+
disabled={!isView && selected.size === 0}
|
|
472
|
+
title={bulkAction.tooltip}
|
|
473
|
+
onclick={() => {
|
|
474
|
+
actionsPopoverEl?.hidePopover();
|
|
475
|
+
handleBulkAction(bulkAction);
|
|
476
|
+
}}
|
|
477
|
+
>
|
|
478
|
+
<BulkIcon class="size-4" />
|
|
479
|
+
{bulkAction.label ?? bulkAction.tooltip}{#if !isView}
|
|
480
|
+
({selected.size})
|
|
481
|
+
{/if}
|
|
482
|
+
</Button>
|
|
483
|
+
{/if}
|
|
484
|
+
{/each}
|
|
335
485
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
486
|
+
{#if allowDelete}
|
|
487
|
+
<Button
|
|
488
|
+
variant="ghost"
|
|
489
|
+
class="btn-sm w-full justify-start text-error"
|
|
490
|
+
disabled={selected.size === 0}
|
|
491
|
+
onclick={() => {
|
|
492
|
+
actionsPopoverEl?.hidePopover();
|
|
493
|
+
handleDelete();
|
|
494
|
+
}}
|
|
495
|
+
>
|
|
496
|
+
{deleteLabel} ({selected.size})
|
|
497
|
+
</Button>
|
|
498
|
+
{/if}
|
|
499
|
+
</div>
|
|
500
|
+
{/if}
|
|
348
501
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
502
|
+
{@const CreateIcon = icons.create}
|
|
503
|
+
<Button variant="primary" onclick={handleCreate}>
|
|
504
|
+
<CreateIcon class="size-5" />
|
|
505
|
+
{#if creation.label}
|
|
506
|
+
<span>{creation.label}</span>
|
|
507
|
+
{:else}
|
|
508
|
+
<span>{strings.create}<span class="hidden sm:inline"> {labelOne}</span></span>
|
|
509
|
+
{/if}
|
|
510
|
+
</Button>
|
|
354
511
|
{:else}
|
|
355
|
-
|
|
512
|
+
{@render toolbarButtons(false)}
|
|
356
513
|
{/if}
|
|
357
|
-
</
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<div
|
|
517
|
+
bind:this={toolbarMeasureEl}
|
|
518
|
+
class="pointer-events-none invisible absolute flex items-center gap-2 whitespace-nowrap"
|
|
519
|
+
style="left:-9999px; top:-9999px;"
|
|
520
|
+
aria-hidden="true"
|
|
521
|
+
>
|
|
522
|
+
{@render toolbarButtons(true)}
|
|
523
|
+
</div>
|
|
358
524
|
{/snippet}
|
|
359
525
|
</Header>
|
|
360
526
|
|
|
@@ -391,8 +557,10 @@
|
|
|
391
557
|
{/if}
|
|
392
558
|
|
|
393
559
|
{#if pendingBulkAction !== null}
|
|
394
|
-
|
|
395
|
-
|
|
560
|
+
{@const pendingActionLabel =
|
|
561
|
+
pendingBulkAction.action.label ?? pendingBulkAction.action.tooltip ?? strings.actions}
|
|
562
|
+
<Modal title={pendingActionLabel} onClose={() => (pendingBulkAction = null)}>
|
|
563
|
+
<p>{strings.deleteConfirm(pendingBulkAction.items.length, pendingActionLabel)}</p>
|
|
396
564
|
<div class="flex justify-end gap-2 mt-4">
|
|
397
565
|
<Button variant="ghost" onclick={() => (pendingBulkAction = null)}>
|
|
398
566
|
{strings.cancel}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type XlsxModule } from '../../table/export.js';
|
|
2
|
-
import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, SearchConfiguration } from '../../../types/crud.js';
|
|
2
|
+
import type { ActionConfiguration, ColumnDefinition, CustomAction, CustomBulkAction, SearchConfiguration, ViewBasedCustomBulkAction } from '../../../types/crud.js';
|
|
3
3
|
import type { FilterSnapshot, ServerPagination, SortDirection, TableQuery } from '../../../types/table.js';
|
|
4
4
|
declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
5
5
|
props: {
|
|
@@ -36,6 +36,7 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
|
36
36
|
onEdit?: (item: T) => void;
|
|
37
37
|
onView?: (item: T) => void;
|
|
38
38
|
onAction?: (action: CustomAction<T>, item: T) => void;
|
|
39
|
+
onBulkAction?: (action: ViewBasedCustomBulkAction<T>, items: T[]) => void;
|
|
39
40
|
};
|
|
40
41
|
exports: {};
|
|
41
42
|
bindings: "";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { BreadcrumbItem } from './types/breadcrumb.js';
|
|
|
2
2
|
export { AttributeType } from './types/attribute.js';
|
|
3
3
|
export type { AttributeMetadata, InterfaceMetadata, SelectOption, OptionsResolver, FormatterResolver, EmbeddedItemLabelResolver } from './types/attribute.js';
|
|
4
4
|
export type { CellProps, CellComponent, CellFormatter, SortDirection, IndexedRow, DistinctEntry, PaginatedEnvelope, ServerPagination, FilterSnapshot, TableQuery } from './types/table.js';
|
|
5
|
-
export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, CustomBulkAction, RowAction, SearchConfiguration } from './types/crud.js';
|
|
5
|
+
export type { ColumnDefinition, FieldDefinition, ActionConfiguration, CustomAction, BaseCustomBulkAction, ViewBasedCustomBulkAction, EndpointBasedCustomBulkAction, CustomBulkAction, RowAction, SearchConfiguration } from './types/crud.js';
|
|
6
6
|
export type { RuneforgeConfig } from './config/context.js';
|
|
7
7
|
export { setConfig, getConfig } from './config/context.js';
|
|
8
8
|
export type { CRUDIconSet, IconComponent } from './icons/types.js';
|
package/dist/types/crud.d.ts
CHANGED
|
@@ -99,14 +99,44 @@ export interface RowAction<T extends object = Record<string, unknown>> {
|
|
|
99
99
|
condition?: (item: T) => boolean;
|
|
100
100
|
run: (item: T) => void;
|
|
101
101
|
}
|
|
102
|
-
export interface
|
|
103
|
-
|
|
102
|
+
export interface BaseCustomBulkAction<T extends object = Record<string, unknown>> {
|
|
103
|
+
/** Visible text on the button. Omit for an icon-only button — pass
|
|
104
|
+
* `tooltip` in that case so the action still has an accessible name and a
|
|
105
|
+
* hover hint, since `label` is what a collapsed "Acciones" menu item and
|
|
106
|
+
* the button's default `title`/`aria-label` fall back to otherwise. */
|
|
107
|
+
label?: string;
|
|
104
108
|
icon: any;
|
|
105
|
-
|
|
109
|
+
/** Hover title shown on the button. Defaults to `label`. Set this
|
|
110
|
+
* explicitly when `label` is omitted (icon-only button) so there's still
|
|
111
|
+
* a hint on hover and an accessible name. */
|
|
112
|
+
tooltip?: string;
|
|
106
113
|
variant?: string;
|
|
107
|
-
confirm?: boolean;
|
|
108
114
|
condition?: (items: T[]) => boolean;
|
|
109
115
|
}
|
|
116
|
+
export interface ViewBasedCustomBulkAction<T extends object = Record<string, unknown>> extends BaseCustomBulkAction<T> {
|
|
117
|
+
kind: 'view';
|
|
118
|
+
/** Renders as a modal-like panel when the action runs, instead of POSTing
|
|
119
|
+
* to an endpoint once per selected item, mirroring `CustomAction.view`.
|
|
120
|
+
*
|
|
121
|
+
* A `view` action never requires a pre-existing selection: the button is
|
|
122
|
+
* enabled even with nothing selected. The component receives the current
|
|
123
|
+
* selection as `items` (possibly empty). */
|
|
124
|
+
view: Component<any>;
|
|
125
|
+
}
|
|
126
|
+
export interface EndpointBasedCustomBulkAction<T extends object = Record<string, unknown>> extends BaseCustomBulkAction<T> {
|
|
127
|
+
kind: 'endpoint';
|
|
128
|
+
/** POSTed once per selected item (`id` set in the FormData body), then
|
|
129
|
+
* `invalidateAll()`s. */
|
|
130
|
+
endpoint: string;
|
|
131
|
+
confirm?: boolean;
|
|
132
|
+
}
|
|
133
|
+
/** A toolbar action operating on the current selection — either a `view`
|
|
134
|
+
* (opens a modal-like panel, e.g. an import wizard) or an `endpoint`
|
|
135
|
+
* (POSTed once per selected item). The required `kind` discriminant picks
|
|
136
|
+
* which one: `'view'` brings `view` and forbids `endpoint`/`confirm`;
|
|
137
|
+
* `'endpoint'` brings `endpoint`/`confirm` and forbids `view` — see
|
|
138
|
+
* `ViewBasedCustomBulkAction` / `EndpointBasedCustomBulkAction`. */
|
|
139
|
+
export type CustomBulkAction<T extends object = Record<string, unknown>> = ViewBasedCustomBulkAction<T> | EndpointBasedCustomBulkAction<T>;
|
|
110
140
|
export interface SearchConfiguration {
|
|
111
141
|
param?: string;
|
|
112
142
|
placeholder?: string;
|