runeforge 0.0.35 → 0.0.37
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.
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
const datePopId = $props.id();
|
|
34
34
|
const dateAnchorName = `--date-anchor-${datePopId}`;
|
|
35
35
|
let datePopoverEl: HTMLElement | undefined = $state();
|
|
36
|
+
let calendarDateEl: (HTMLElement & { value: string }) | undefined = $state();
|
|
36
37
|
|
|
37
38
|
const name = $derived(readonly ? undefined : field.attribute);
|
|
38
39
|
const labelText = $derived(fieldLabel(field));
|
|
@@ -72,6 +73,22 @@
|
|
|
72
73
|
);
|
|
73
74
|
const isMultiValued = $derived(field.type === 'multiselect' || field.type === 'tree');
|
|
74
75
|
|
|
76
|
+
// cally's `change` event doesn't bubble, so the usual `onchange={...}` prop
|
|
77
|
+
// never fires — Svelte 5 delegates events like `change` to a listener on
|
|
78
|
+
// a shared ancestor, which only ever sees events that bubble up to it.
|
|
79
|
+
// Attaching directly on the element (same fix ColumnFilter's date-range
|
|
80
|
+
// picker already uses) sidesteps delegation entirely.
|
|
81
|
+
$effect(() => {
|
|
82
|
+
if (!calendarDateEl) return;
|
|
83
|
+
function handler() {
|
|
84
|
+
if (fieldDisabled) return;
|
|
85
|
+
record[field.attribute] = calendarDateEl!.value;
|
|
86
|
+
datePopoverEl?.hidePopover();
|
|
87
|
+
}
|
|
88
|
+
calendarDateEl.addEventListener('change', handler);
|
|
89
|
+
return () => calendarDateEl?.removeEventListener('change', handler);
|
|
90
|
+
});
|
|
91
|
+
|
|
75
92
|
$effect(() => {
|
|
76
93
|
if (!field.dependentOptions || isMultiValued) return;
|
|
77
94
|
const current = record[field.attribute];
|
|
@@ -193,12 +210,8 @@
|
|
|
193
210
|
>
|
|
194
211
|
<calendar-date
|
|
195
212
|
class="cally"
|
|
213
|
+
bind:this={calendarDateEl}
|
|
196
214
|
value={String(record[field.attribute] ?? '')}
|
|
197
|
-
onchange={(e: Event) => {
|
|
198
|
-
if (fieldDisabled) return;
|
|
199
|
-
record[field.attribute] = (e.currentTarget as HTMLElement & { value: string }).value;
|
|
200
|
-
datePopoverEl?.hidePopover();
|
|
201
|
-
}}
|
|
202
215
|
>
|
|
203
216
|
<svg
|
|
204
217
|
aria-label={strings.previous}
|
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
);
|
|
122
122
|
|
|
123
123
|
let activeAction = $state<{ action: CustomAction<T>; item: T } | null>(null);
|
|
124
|
+
let activeBulkAction = $state<{ action: CustomBulkAction<T>; items: T[] } | null>(null);
|
|
124
125
|
|
|
125
126
|
async function runAction(action: CustomAction<T>, item: T) {
|
|
126
127
|
if (!(action.condition?.(item) ?? true)) return;
|
|
@@ -131,6 +132,10 @@
|
|
|
131
132
|
activeAction = { action, item };
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
function runBulkAction(action: CustomBulkAction<T>, items: T[]) {
|
|
136
|
+
activeBulkAction = { action, items };
|
|
137
|
+
}
|
|
138
|
+
|
|
134
139
|
async function navList() {
|
|
135
140
|
await goto('?');
|
|
136
141
|
}
|
|
@@ -293,7 +298,9 @@
|
|
|
293
298
|
);
|
|
294
299
|
|
|
295
300
|
const serverError = $derived(
|
|
296
|
-
creating || reading || editing || activeAction !== null
|
|
301
|
+
creating || reading || editing || activeAction !== null || activeBulkAction !== null
|
|
302
|
+
? (form?.error ?? '')
|
|
303
|
+
: ''
|
|
297
304
|
);
|
|
298
305
|
</script>
|
|
299
306
|
|
|
@@ -359,6 +366,7 @@
|
|
|
359
366
|
onEdit={navEdit}
|
|
360
367
|
onView={navRead}
|
|
361
368
|
onAction={runAction}
|
|
369
|
+
onBulkAction={runBulkAction}
|
|
362
370
|
/>
|
|
363
371
|
{/if}
|
|
364
372
|
|
|
@@ -373,3 +381,15 @@
|
|
|
373
381
|
onSuccess={() => (activeAction = null)}
|
|
374
382
|
/>
|
|
375
383
|
{/if}
|
|
384
|
+
|
|
385
|
+
{#if activeBulkAction !== null}
|
|
386
|
+
{@const BulkActionView = activeBulkAction.action.view}
|
|
387
|
+
<BulkActionView
|
|
388
|
+
items={activeBulkAction.items}
|
|
389
|
+
label={activeBulkAction.action.label}
|
|
390
|
+
endpoint={activeBulkAction.action.endpoint}
|
|
391
|
+
{serverError}
|
|
392
|
+
onCancel={() => (activeBulkAction = null)}
|
|
393
|
+
onSuccess={() => (activeBulkAction = null)}
|
|
394
|
+
/>
|
|
395
|
+
{/if}
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
onCreate,
|
|
53
53
|
onEdit,
|
|
54
54
|
onView,
|
|
55
|
-
onAction
|
|
55
|
+
onAction,
|
|
56
|
+
onBulkAction
|
|
56
57
|
}: {
|
|
57
58
|
data?: T[];
|
|
58
59
|
labelOne?: string;
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
onEdit?: (item: T) => void;
|
|
86
87
|
onView?: (item: T) => void;
|
|
87
88
|
onAction?: (action: CustomAction<T>, item: T) => void;
|
|
89
|
+
onBulkAction?: (action: CustomBulkAction<T>, items: T[]) => void;
|
|
88
90
|
} = $props();
|
|
89
91
|
|
|
90
92
|
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
@@ -97,7 +99,7 @@
|
|
|
97
99
|
const updateLabel = $derived(update.label ?? strings.edit);
|
|
98
100
|
const readLabel = $derived(read.label ?? strings.view);
|
|
99
101
|
const showRowActions = $derived(allowRead || allowUpdate || allowDelete || actions.length > 0);
|
|
100
|
-
const allowSelection = $derived(allowDelete || customBulkActions.
|
|
102
|
+
const allowSelection = $derived(allowDelete || customBulkActions.some((action) => !action.view));
|
|
101
103
|
|
|
102
104
|
let selected = new SvelteSet<number>();
|
|
103
105
|
let pendingDeletion = $state<T[] | null>(null);
|
|
@@ -179,6 +181,7 @@
|
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
async function runBulkAction(action: CustomBulkAction<T>, items: T[]) {
|
|
184
|
+
if (!action.endpoint) return;
|
|
182
185
|
await runEndpointAction(action.endpoint, items);
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -192,6 +195,10 @@
|
|
|
192
195
|
|
|
193
196
|
function handleBulkAction(action: CustomBulkAction<T>) {
|
|
194
197
|
const items = selectedItems;
|
|
198
|
+
if (action.view) {
|
|
199
|
+
onBulkAction?.(action, items);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
195
202
|
selected.clear();
|
|
196
203
|
requestBulkAction(action, items);
|
|
197
204
|
}
|
|
@@ -324,11 +331,13 @@
|
|
|
324
331
|
<Button
|
|
325
332
|
variant={bulkAction.variant ?? 'ghost'}
|
|
326
333
|
class="btn-outline"
|
|
327
|
-
disabled={selected.size === 0}
|
|
334
|
+
disabled={!bulkAction.view && selected.size === 0}
|
|
328
335
|
onclick={() => handleBulkAction(bulkAction)}
|
|
329
336
|
>
|
|
330
337
|
<BulkIcon class="size-4" />
|
|
331
|
-
{bulkAction.label}
|
|
338
|
+
{bulkAction.label}{#if !bulkAction.view}
|
|
339
|
+
({selected.size})
|
|
340
|
+
{/if}
|
|
332
341
|
</Button>
|
|
333
342
|
{/if}
|
|
334
343
|
{/each}
|
|
@@ -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: CustomBulkAction<T>, items: T[]) => void;
|
|
39
40
|
};
|
|
40
41
|
exports: {};
|
|
41
42
|
bindings: "";
|
package/dist/types/crud.d.ts
CHANGED
|
@@ -102,10 +102,20 @@ export interface RowAction<T extends object = Record<string, unknown>> {
|
|
|
102
102
|
export interface CustomBulkAction<T extends object = Record<string, unknown>> {
|
|
103
103
|
label: string;
|
|
104
104
|
icon: any;
|
|
105
|
-
|
|
105
|
+
/** Required unless `view` is set. */
|
|
106
|
+
endpoint?: string;
|
|
106
107
|
variant?: string;
|
|
107
108
|
confirm?: boolean;
|
|
108
109
|
condition?: (items: T[]) => boolean;
|
|
110
|
+
/** Renders as a modal-like panel when the action runs, instead of POSTing
|
|
111
|
+
* to `endpoint` once per selected item. Mutually exclusive with
|
|
112
|
+
* `endpoint` — provide exactly one of the two, mirroring `CustomAction.view`.
|
|
113
|
+
*
|
|
114
|
+
* Unlike the endpoint-loop behavior, a `view` action never requires a
|
|
115
|
+
* pre-existing selection: the button is enabled even with nothing
|
|
116
|
+
* selected, and `confirm` is ignored (the view owns its own flow). The
|
|
117
|
+
* component receives the current selection as `items` (possibly empty). */
|
|
118
|
+
view?: Component<any>;
|
|
109
119
|
}
|
|
110
120
|
export interface SearchConfiguration {
|
|
111
121
|
param?: string;
|