runeforge 0.0.48 → 0.0.50
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.
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
async function navList() {
|
|
143
|
-
await goto('?');
|
|
143
|
+
await goto(lastListState.search || '?');
|
|
144
144
|
}
|
|
145
145
|
async function navCreate() {
|
|
146
146
|
await goto('?view=create');
|
|
@@ -154,6 +154,28 @@
|
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
let lastListState = $state<{ data: T[]; search: string }>({ data: [], search: '' });
|
|
158
|
+
$effect(() => {
|
|
159
|
+
if (!creating && !reading && !editing) {
|
|
160
|
+
lastListState = { data: entityData, search: page.url.search };
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// "Save and continue" on the Update form: jumps to editing the record that
|
|
165
|
+
// follows the current one in the last loaded list, falling back to
|
|
166
|
+
// re-editing the same instance when it's the last one (or isn't found).
|
|
167
|
+
function nextInstance(current: T): T {
|
|
168
|
+
const currentId = (current as Record<string, unknown>)[idKey];
|
|
169
|
+
const idx = lastListState.data.findIndex(
|
|
170
|
+
(item) => (item as Record<string, unknown>)[idKey] === currentId
|
|
171
|
+
);
|
|
172
|
+
return (idx !== -1 ? lastListState.data[idx + 1] : undefined) ?? current;
|
|
173
|
+
}
|
|
174
|
+
async function navContinueEdit() {
|
|
175
|
+
if (!singleInstance) return;
|
|
176
|
+
await navEdit(nextInstance(singleInstance));
|
|
177
|
+
}
|
|
178
|
+
|
|
157
179
|
const resolvedColumns: ColumnDefinition<T>[] = $derived(
|
|
158
180
|
columns ??
|
|
159
181
|
(meta
|
|
@@ -341,6 +363,7 @@
|
|
|
341
363
|
{serverError}
|
|
342
364
|
onCancel={navList}
|
|
343
365
|
onSuccess={navList}
|
|
366
|
+
onContinue={navContinueEdit}
|
|
344
367
|
/>
|
|
345
368
|
{:else}
|
|
346
369
|
<List
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
serverError = '',
|
|
26
26
|
onCancel,
|
|
27
27
|
onSuccess,
|
|
28
|
+
onContinue,
|
|
28
29
|
}: {
|
|
29
30
|
labelOne?: string;
|
|
30
31
|
labelMany?: string;
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
serverError?: string;
|
|
38
39
|
onCancel?: () => void;
|
|
39
40
|
onSuccess?: () => void;
|
|
41
|
+
onContinue?: () => void;
|
|
40
42
|
} = $props();
|
|
41
43
|
|
|
42
44
|
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
|
|
45
47
|
let fieldErrors = $state<Record<string, string>>({});
|
|
46
48
|
let internalError = $state('');
|
|
49
|
+
let continuing = $state(false);
|
|
47
50
|
|
|
48
51
|
function seedFromInstance(inst: Record<string, unknown>): Record<string, unknown> {
|
|
49
52
|
const seeded: Record<string, unknown> = { ...inst };
|
|
@@ -67,6 +70,10 @@
|
|
|
67
70
|
const groups = $derived(groupFields(fields));
|
|
68
71
|
const hasFileField = $derived(fields.some((f) => f.type === 'file'));
|
|
69
72
|
|
|
73
|
+
const continueEnabled = $derived(update.continue?.enabled ?? false);
|
|
74
|
+
const continueLabel = $derived(update.continue?.label ?? strings.saveAndContinue);
|
|
75
|
+
const continueClass = $derived(update.continue?.class ?? '');
|
|
76
|
+
|
|
70
77
|
const errorEntries = $derived([
|
|
71
78
|
...((serverError || internalError) ? [['_global', internalError || serverError] as [string, string]] : []),
|
|
72
79
|
...Object.entries(fieldErrors),
|
|
@@ -113,7 +120,12 @@
|
|
|
113
120
|
return async ({ result, update: updateForm }) => {
|
|
114
121
|
if (result.type === 'success' || result.type === 'redirect') {
|
|
115
122
|
await updateForm({ reset: false });
|
|
116
|
-
|
|
123
|
+
if (continuing) {
|
|
124
|
+
continuing = false;
|
|
125
|
+
onContinue?.();
|
|
126
|
+
} else {
|
|
127
|
+
onSuccess?.();
|
|
128
|
+
}
|
|
117
129
|
} else if (result.type === 'error') {
|
|
118
130
|
internalError = result.error?.message ?? strings.serverError;
|
|
119
131
|
} else {
|
|
@@ -161,7 +173,17 @@
|
|
|
161
173
|
<Button variant="ghost" onclick={() => onCancel?.()}>
|
|
162
174
|
{strings.cancel}
|
|
163
175
|
</Button>
|
|
164
|
-
|
|
176
|
+
{#if continueEnabled}
|
|
177
|
+
<Button
|
|
178
|
+
type="submit"
|
|
179
|
+
variant="secondary"
|
|
180
|
+
class={continueClass}
|
|
181
|
+
onclick={() => { continuing = true; }}
|
|
182
|
+
>
|
|
183
|
+
{continueLabel}
|
|
184
|
+
</Button>
|
|
185
|
+
{/if}
|
|
186
|
+
<Button type="submit" variant="primary" onclick={() => { continuing = false; }}>
|
|
165
187
|
{strings.save}
|
|
166
188
|
</Button>
|
|
167
189
|
</div>
|
package/dist/types/crud.d.ts
CHANGED
|
@@ -71,10 +71,15 @@ export interface ActionConfiguration<T extends object = Record<string, unknown>>
|
|
|
71
71
|
endpoint?: string;
|
|
72
72
|
confirm?: boolean;
|
|
73
73
|
callback?: (items: T[]) => void | Promise<void>;
|
|
74
|
-
/**
|
|
75
|
-
* same `endpoint` and then blanks the form
|
|
76
|
-
* record from scratch. Enabled by
|
|
77
|
-
* hide it.
|
|
74
|
+
/** The "Save and continue" button, shown alongside Save/Cancel.
|
|
75
|
+
* - Create form: submits to the same `endpoint` and then blanks the form
|
|
76
|
+
* so the user can create another record from scratch. Enabled by
|
|
77
|
+
* default — pass `{ enabled: false }` to hide it.
|
|
78
|
+
* - Update form: submits to the same `endpoint` and then loads the record
|
|
79
|
+
* that follows the current one in the list into the same form, so
|
|
80
|
+
* several records can be edited in a row — handy after a bulk import.
|
|
81
|
+
* Falls back to reloading the current instance when there is no next
|
|
82
|
+
* one. Disabled by default — pass `{ enabled: true }` to show it. */
|
|
78
83
|
continue?: CreateFormButtonConfiguration;
|
|
79
84
|
/** Create form only: shows a "Duplicate" button alongside Save/Cancel that
|
|
80
85
|
* submits to the same `endpoint`, but — unlike "Save and continue", which
|