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
- onSuccess?.();
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
- <Button type="submit" variant="primary">
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>
@@ -11,6 +11,7 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
11
11
  serverError?: string;
12
12
  onCancel?: () => void;
13
13
  onSuccess?: () => void;
14
+ onContinue?: () => void;
14
15
  };
15
16
  exports: {};
16
17
  bindings: "";
@@ -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
- /** Create form only: the "Save and continue" button, which submits to the
75
- * same `endpoint` and then blanks the form so the user can create another
76
- * record from scratch. Enabled by default — pass `{ enabled: false }` to
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runeforge",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "description": "SvelteKit toolkit for building metadata-driven CRUD interfaces with tables, forms, and actions",
5
5
  "license": "MIT",
6
6
  "author": "Ezequiel Puerta",