runeforge 0.0.37 → 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 +6 -4
- package/dist/components/crud/views/List.svelte +237 -78
- package/dist/components/crud/views/List.svelte.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/types/crud.d.ts +32 -12
- 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,7 +122,9 @@
|
|
|
121
122
|
);
|
|
122
123
|
|
|
123
124
|
let activeAction = $state<{ action: CustomAction<T>; item: T } | null>(null);
|
|
124
|
-
let activeBulkAction = $state<{ action:
|
|
125
|
+
let activeBulkAction = $state<{ action: ViewBasedCustomBulkAction<T>; items: T[] } | null>(
|
|
126
|
+
null
|
|
127
|
+
);
|
|
125
128
|
|
|
126
129
|
async function runAction(action: CustomAction<T>, item: T) {
|
|
127
130
|
if (!(action.condition?.(item) ?? true)) return;
|
|
@@ -132,7 +135,7 @@
|
|
|
132
135
|
activeAction = { action, item };
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
function runBulkAction(action:
|
|
138
|
+
function runBulkAction(action: ViewBasedCustomBulkAction<T>, items: T[]) {
|
|
136
139
|
activeBulkAction = { action, items };
|
|
137
140
|
}
|
|
138
141
|
|
|
@@ -387,7 +390,6 @@
|
|
|
387
390
|
<BulkActionView
|
|
388
391
|
items={activeBulkAction.items}
|
|
389
392
|
label={activeBulkAction.action.label}
|
|
390
|
-
endpoint={activeBulkAction.action.endpoint}
|
|
391
393
|
{serverError}
|
|
392
394
|
onCancel={() => (activeBulkAction = null)}
|
|
393
395
|
onSuccess={() => (activeBulkAction = null)}
|
|
@@ -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 = '',
|
|
@@ -86,7 +93,7 @@
|
|
|
86
93
|
onEdit?: (item: T) => void;
|
|
87
94
|
onView?: (item: T) => void;
|
|
88
95
|
onAction?: (action: CustomAction<T>, item: T) => void;
|
|
89
|
-
onBulkAction?: (action:
|
|
96
|
+
onBulkAction?: (action: ViewBasedCustomBulkAction<T>, items: T[]) => void;
|
|
90
97
|
} = $props();
|
|
91
98
|
|
|
92
99
|
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
@@ -99,7 +106,12 @@
|
|
|
99
106
|
const updateLabel = $derived(update.label ?? strings.edit);
|
|
100
107
|
const readLabel = $derived(read.label ?? strings.view);
|
|
101
108
|
const showRowActions = $derived(allowRead || allowUpdate || allowDelete || actions.length > 0);
|
|
102
|
-
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
|
+
);
|
|
103
115
|
|
|
104
116
|
let selected = new SvelteSet<number>();
|
|
105
117
|
let pendingDeletion = $state<T[] | null>(null);
|
|
@@ -109,7 +121,33 @@
|
|
|
109
121
|
let visibleRows = $state<T[]>([]);
|
|
110
122
|
let exportQuery = $state<TableQuery | undefined>(undefined);
|
|
111
123
|
let exportPopoverEl: HTMLElement | undefined = $state();
|
|
124
|
+
let actionsPopoverEl: HTMLElement | undefined = $state();
|
|
112
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
|
+
});
|
|
113
151
|
|
|
114
152
|
async function resolveExportRows(): Promise<T[]> {
|
|
115
153
|
if (!pagination) return visibleRows;
|
|
@@ -181,12 +219,12 @@
|
|
|
181
219
|
}
|
|
182
220
|
|
|
183
221
|
async function runBulkAction(action: CustomBulkAction<T>, items: T[]) {
|
|
184
|
-
if (
|
|
222
|
+
if (isViewBasedBulkAction(action)) return;
|
|
185
223
|
await runEndpointAction(action.endpoint, items);
|
|
186
224
|
}
|
|
187
225
|
|
|
188
226
|
function requestBulkAction(action: CustomBulkAction<T>, items: T[]) {
|
|
189
|
-
if (action.confirm) {
|
|
227
|
+
if (!isViewBasedBulkAction(action) && action.confirm) {
|
|
190
228
|
pendingBulkAction = { action, items };
|
|
191
229
|
} else {
|
|
192
230
|
runBulkAction(action, items);
|
|
@@ -195,7 +233,7 @@
|
|
|
195
233
|
|
|
196
234
|
function handleBulkAction(action: CustomBulkAction<T>) {
|
|
197
235
|
const items = selectedItems;
|
|
198
|
-
if (action
|
|
236
|
+
if (isViewBasedBulkAction(action)) {
|
|
199
237
|
onBulkAction?.(action, items);
|
|
200
238
|
return;
|
|
201
239
|
}
|
|
@@ -238,6 +276,97 @@
|
|
|
238
276
|
]);
|
|
239
277
|
</script>
|
|
240
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
|
+
|
|
241
370
|
{#snippet actionsCell(item: T)}
|
|
242
371
|
<div class="flex justify-end items-center gap-1">
|
|
243
372
|
{#each rowActions as action (action.label)}
|
|
@@ -283,87 +412,115 @@
|
|
|
283
412
|
breadcrumbs={[{ label: labelMany, icon: entityIcon, link: { href: '#' }, prominent: true }]}
|
|
284
413
|
>
|
|
285
414
|
{#snippet buttons()}
|
|
286
|
-
{
|
|
287
|
-
|
|
288
|
-
|
|
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}
|
|
289
420
|
|
|
290
|
-
|
|
291
|
-
{@const ExportIcon = icons.download}
|
|
292
|
-
<Button
|
|
293
|
-
variant="ghost"
|
|
294
|
-
class="btn-square"
|
|
295
|
-
popovertarget="export-menu-{exportId}"
|
|
296
|
-
style="anchor-name:--export-anchor-{exportId}"
|
|
297
|
-
aria-label={strings.export}
|
|
298
|
-
title={strings.export}
|
|
299
|
-
>
|
|
300
|
-
<ExportIcon class="size-4" />
|
|
301
|
-
</Button>
|
|
302
|
-
<div
|
|
303
|
-
popover="auto"
|
|
304
|
-
id="export-menu-{exportId}"
|
|
305
|
-
style="position-anchor:--export-anchor-{exportId}"
|
|
306
|
-
class="dropdown dropdown-end w-40 rounded-box border border-base-content/10 bg-base-100 p-1 shadow-lg"
|
|
307
|
-
bind:this={exportPopoverEl}
|
|
308
|
-
>
|
|
309
|
-
<Button
|
|
310
|
-
variant="ghost"
|
|
311
|
-
class="btn-sm w-full justify-start"
|
|
312
|
-
onclick={() => handleExport('csv')}
|
|
313
|
-
>
|
|
314
|
-
{strings.exportCsv}
|
|
315
|
-
</Button>
|
|
316
|
-
{#if xlsx}
|
|
421
|
+
{#if hasCollapsibleActions}
|
|
317
422
|
<Button
|
|
318
423
|
variant="ghost"
|
|
319
|
-
class="btn-
|
|
320
|
-
|
|
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}
|
|
321
429
|
>
|
|
322
|
-
|
|
430
|
+
<span class="text-lg leading-none" aria-hidden="true">⋯</span>
|
|
323
431
|
</Button>
|
|
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}
|
|
463
|
+
|
|
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}
|
|
485
|
+
|
|
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>
|
|
324
500
|
{/if}
|
|
325
|
-
</div>
|
|
326
|
-
{/if}
|
|
327
501
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
onclick={() => handleBulkAction(bulkAction)}
|
|
336
|
-
>
|
|
337
|
-
<BulkIcon class="size-4" />
|
|
338
|
-
{bulkAction.label}{#if !bulkAction.view}
|
|
339
|
-
({selected.size})
|
|
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>
|
|
340
509
|
{/if}
|
|
341
510
|
</Button>
|
|
342
|
-
{/if}
|
|
343
|
-
{/each}
|
|
344
|
-
|
|
345
|
-
{#if allowDelete}
|
|
346
|
-
{@const DeleteIcon = icons.delete}
|
|
347
|
-
<Button
|
|
348
|
-
variant="error"
|
|
349
|
-
class="btn-outline"
|
|
350
|
-
disabled={selected.size === 0}
|
|
351
|
-
onclick={handleDelete}
|
|
352
|
-
>
|
|
353
|
-
<DeleteIcon class="size-4" />
|
|
354
|
-
{deleteLabel} ({selected.size})
|
|
355
|
-
</Button>
|
|
356
|
-
{/if}
|
|
357
|
-
|
|
358
|
-
{@const CreateIcon = icons.create}
|
|
359
|
-
<Button variant="primary" onclick={handleCreate}>
|
|
360
|
-
<CreateIcon class="size-5" />
|
|
361
|
-
{#if creation.label}
|
|
362
|
-
<span>{creation.label}</span>
|
|
363
511
|
{:else}
|
|
364
|
-
|
|
512
|
+
{@render toolbarButtons(false)}
|
|
365
513
|
{/if}
|
|
366
|
-
</
|
|
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>
|
|
367
524
|
{/snippet}
|
|
368
525
|
</Header>
|
|
369
526
|
|
|
@@ -400,8 +557,10 @@
|
|
|
400
557
|
{/if}
|
|
401
558
|
|
|
402
559
|
{#if pendingBulkAction !== null}
|
|
403
|
-
|
|
404
|
-
|
|
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>
|
|
405
564
|
<div class="flex justify-end gap-2 mt-4">
|
|
406
565
|
<Button variant="ghost" onclick={() => (pendingBulkAction = null)}>
|
|
407
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,7 +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:
|
|
39
|
+
onBulkAction?: (action: ViewBasedCustomBulkAction<T>, items: T[]) => void;
|
|
40
40
|
};
|
|
41
41
|
exports: {};
|
|
42
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,24 +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
|
-
/**
|
|
106
|
-
|
|
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;
|
|
107
113
|
variant?: string;
|
|
108
|
-
confirm?: boolean;
|
|
109
114
|
condition?: (items: T[]) => boolean;
|
|
115
|
+
}
|
|
116
|
+
export interface ViewBasedCustomBulkAction<T extends object = Record<string, unknown>> extends BaseCustomBulkAction<T> {
|
|
117
|
+
kind: 'view';
|
|
110
118
|
/** Renders as a modal-like panel when the action runs, instead of POSTing
|
|
111
|
-
* to
|
|
112
|
-
* `endpoint` — provide exactly one of the two, mirroring `CustomAction.view`.
|
|
119
|
+
* to an endpoint once per selected item, mirroring `CustomAction.view`.
|
|
113
120
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
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;
|
|
119
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>;
|
|
120
140
|
export interface SearchConfiguration {
|
|
121
141
|
param?: string;
|
|
122
142
|
placeholder?: string;
|