runeforge 0.0.51 → 0.0.53
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/crud/GenericCRUD.svelte +16 -1
- package/dist/components/crud/utils/embedded.d.ts +2 -0
- package/dist/components/crud/utils/embedded.js +27 -0
- package/dist/components/crud/utils/formatters.d.ts +0 -1
- package/dist/components/crud/utils/formatters.js +0 -6
- package/dist/components/crud/utils/resolution.d.ts +1 -0
- package/dist/components/crud/utils/resolution.js +11 -0
- package/dist/components/crud/views/Create.svelte +9 -2
- package/dist/components/crud/views/Create.svelte.d.ts +3 -0
- package/dist/components/crud/views/Update.svelte +25 -14
- package/dist/components/crud/views/Update.svelte.d.ts +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/types/attribute.d.ts +8 -0
- package/dist/types/crud.d.ts +18 -6
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { AUTO_EXCLUDED } from './utils/constants.js';
|
|
9
9
|
import {
|
|
10
10
|
resolveFormatter,
|
|
11
|
+
truncateFormatter,
|
|
11
12
|
inferType,
|
|
12
13
|
buildFieldDefinitions
|
|
13
14
|
} from './utils/resolution.js';
|
|
@@ -139,10 +140,18 @@
|
|
|
139
140
|
activeBulkAction = { action, items };
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
let duplicateSeed = $state<Record<string, unknown> | undefined>(undefined);
|
|
144
|
+
|
|
142
145
|
async function navList() {
|
|
146
|
+
duplicateSeed = undefined;
|
|
143
147
|
await goto(lastListState.search || '?');
|
|
144
148
|
}
|
|
145
149
|
async function navCreate() {
|
|
150
|
+
duplicateSeed = undefined;
|
|
151
|
+
await goto('?view=create');
|
|
152
|
+
}
|
|
153
|
+
async function navDuplicate(record: Record<string, unknown>) {
|
|
154
|
+
duplicateSeed = record;
|
|
146
155
|
await goto('?view=create');
|
|
147
156
|
}
|
|
148
157
|
async function navRead(item: T) {
|
|
@@ -190,7 +199,7 @@
|
|
|
190
199
|
// embedded column without an explicit formatter falls back to
|
|
191
200
|
// joining each item's label (itemLabel, or the same
|
|
192
201
|
// sub-field-joining summary the embedded form list uses).
|
|
193
|
-
const
|
|
202
|
+
const resolvedFormatter =
|
|
194
203
|
resolveFormatter(m, data) ??
|
|
195
204
|
(embeddedFields
|
|
196
205
|
? (value: unknown) =>
|
|
@@ -204,6 +213,10 @@
|
|
|
204
213
|
.join(', ')
|
|
205
214
|
: ''
|
|
206
215
|
: undefined);
|
|
216
|
+
const formatter =
|
|
217
|
+
m.truncateUpTo != null
|
|
218
|
+
? truncateFormatter(resolvedFormatter, m.truncateUpTo)
|
|
219
|
+
: resolvedFormatter;
|
|
207
220
|
return {
|
|
208
221
|
attribute: k as keyof T & string,
|
|
209
222
|
title: m.label ?? k,
|
|
@@ -337,6 +350,7 @@
|
|
|
337
350
|
fields={resolvedFields}
|
|
338
351
|
{creation}
|
|
339
352
|
{serverError}
|
|
353
|
+
seed={duplicateSeed}
|
|
340
354
|
onCancel={navList}
|
|
341
355
|
onSuccess={navList}
|
|
342
356
|
/>
|
|
@@ -364,6 +378,7 @@
|
|
|
364
378
|
onCancel={navList}
|
|
365
379
|
onSuccess={navList}
|
|
366
380
|
onContinue={navContinueEdit}
|
|
381
|
+
onDuplicate={navDuplicate}
|
|
367
382
|
/>
|
|
368
383
|
{:else}
|
|
369
384
|
<List
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { FieldDefinition } from '../../../types/crud.js';
|
|
2
2
|
export declare function seedField<T extends object = Record<string, unknown>>(f: FieldDefinition<T>, raw: unknown): unknown;
|
|
3
3
|
export declare function emptyRecord<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[]): Record<string, unknown>;
|
|
4
|
+
export declare function seedRecord<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[], source: Record<string, unknown>): Record<string, unknown>;
|
|
5
|
+
export declare function applyDuplicateOmit<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[], record: Record<string, unknown>, omit?: string[]): Record<string, unknown>;
|
|
4
6
|
export declare function formatEmbeddedFieldValue<T extends object = Record<string, unknown>>(f: FieldDefinition<T>, raw: unknown): string;
|
|
5
7
|
export declare function defaultItemLabel<T extends object = Record<string, unknown>>(fields: FieldDefinition<T>[], item: Record<string, unknown>): string;
|
|
@@ -26,6 +26,33 @@ export function seedField(f, raw) {
|
|
|
26
26
|
export function emptyRecord(fields) {
|
|
27
27
|
return Object.fromEntries(fields.map((f) => [f.attribute, seedField(f, f.default)]));
|
|
28
28
|
}
|
|
29
|
+
// Shared by Update.svelte (loading the instance being edited) and Create.svelte
|
|
30
|
+
// (pre-filling a new record from a duplicated instance): builds a draft record
|
|
31
|
+
// from an arbitrary source object, running each field's `seed` resolver
|
|
32
|
+
// (falling back to the source's raw value) through the same seedField
|
|
33
|
+
// normalization as emptyRecord.
|
|
34
|
+
export function seedRecord(fields, source) {
|
|
35
|
+
const seeded = { ...source };
|
|
36
|
+
for (const f of fields) {
|
|
37
|
+
const raw = f.seed ? f.seed(source) : source[f.attribute];
|
|
38
|
+
seeded[f.attribute] = seedField(f, raw);
|
|
39
|
+
}
|
|
40
|
+
return seeded;
|
|
41
|
+
}
|
|
42
|
+
// Shared by Update.svelte and Create.svelte's "Duplicate" handling: returns a
|
|
43
|
+
// copy of the just-saved record with `omit`-listed attributes reset to their
|
|
44
|
+
// field default, so the new draft doesn't inherit values that only made sense
|
|
45
|
+
// for the original instance (e.g. a publication date).
|
|
46
|
+
export function applyDuplicateOmit(fields, record, omit = []) {
|
|
47
|
+
if (!omit.length)
|
|
48
|
+
return record;
|
|
49
|
+
const result = { ...record };
|
|
50
|
+
for (const f of fields) {
|
|
51
|
+
if (omit.includes(f.attribute))
|
|
52
|
+
result[f.attribute] = seedField(f, f.default);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
29
56
|
// Shared by defaultItemLabel and the CSV/XLSX export column expansion: renders
|
|
30
57
|
// one sub-field's raw stored value as display text, resolving a select's
|
|
31
58
|
// option label instead of its stored value. Booleans are left to the caller
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export declare const formatBoolean: (trueLabel?: string, falseLabel?: string) => () => (value: boolean) => string;
|
|
2
2
|
export declare const formatDatetime: (format?: string, timeZone?: string) => (() => (value: Date) => string);
|
|
3
|
-
export declare function formatTruncateTextUpTo(maxLength: number): () => (value: string) => string;
|
|
4
3
|
export declare function formatInstance<T extends Record<string, unknown>>(attribute: keyof T & string, instances: T[], urlPath: string, idKey?: keyof T & string): (value: unknown) => string;
|
|
@@ -52,12 +52,6 @@ export const formatDatetime = (format = 'dd/mm/YYYY HH:MM', timeZone) => {
|
|
|
52
52
|
};
|
|
53
53
|
return () => fmt;
|
|
54
54
|
};
|
|
55
|
-
export function formatTruncateTextUpTo(maxLength) {
|
|
56
|
-
return () => (value) => {
|
|
57
|
-
const str = String(value ?? '');
|
|
58
|
-
return str.length > maxLength ? str.slice(0, maxLength) + '…' : str;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
55
|
function escapeHtml(str) {
|
|
62
56
|
return str
|
|
63
57
|
.replace(/&/g, '&')
|
|
@@ -3,5 +3,6 @@ import type { FieldDefinition } from '../../../types/crud.js';
|
|
|
3
3
|
export declare function resolveOptions(m: AttributeMetadata, d: unknown): SelectOption[] | undefined;
|
|
4
4
|
export declare function resolveDefault(m: AttributeMetadata, d: unknown): unknown;
|
|
5
5
|
export declare function resolveFormatter(m: AttributeMetadata, d: unknown): import("../../../index.ts").CellFormatter<any, any> | undefined;
|
|
6
|
+
export declare function truncateFormatter<T extends object = Record<string, unknown>, V = unknown>(formatter: ((value: V, row: T) => string) | undefined, maxLength: number): (value: V, row: T) => string;
|
|
6
7
|
export declare function inferType(key: string, value: unknown): AttributeType;
|
|
7
8
|
export declare function buildFieldDefinitions<T extends object = Record<string, unknown>>(meta: Partial<Record<string, AttributeMetadata>>, data: unknown, excludedFlag: 'excludedFromCreate' | 'excludedFromRead' | 'excludedFromUpdate' | 'excludedFromList', excluded: Set<string>): FieldDefinition<T>[];
|
|
@@ -9,6 +9,17 @@ export function resolveDefault(m, d) {
|
|
|
9
9
|
export function resolveFormatter(m, d) {
|
|
10
10
|
return m.formatter?.(d);
|
|
11
11
|
}
|
|
12
|
+
// List column only (GenericCRUD's resolvedColumns): wraps `formatter` (or,
|
|
13
|
+
// absent one, the same raw String(value) TableBody falls back to) so a column
|
|
14
|
+
// that needs to stay narrow can truncate its cell without that truncation
|
|
15
|
+
// leaking into the Read view or the Create/Update forms, which call
|
|
16
|
+
// `formatter` directly and never go through this wrapper.
|
|
17
|
+
export function truncateFormatter(formatter, maxLength) {
|
|
18
|
+
return (value, row) => {
|
|
19
|
+
const str = formatter ? formatter(value, row) : String(value ?? '');
|
|
20
|
+
return str.length > maxLength ? str.slice(0, maxLength) + '…' : str;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
12
23
|
export function inferType(key, value) {
|
|
13
24
|
if (typeof value === 'boolean')
|
|
14
25
|
return 'boolean';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { defaultIconSet } from '../../../icons/sets/default.js';
|
|
9
9
|
import { validateAll } from '../utils/validation.js';
|
|
10
10
|
import { groupFields } from '../utils/grouping.js';
|
|
11
|
-
import { emptyRecord } from '../utils/embedded.js';
|
|
11
|
+
import { applyDuplicateOmit, emptyRecord, seedRecord } from '../utils/embedded.js';
|
|
12
12
|
import type { ActionConfiguration, FieldDefinition } from '../../../types/crud.js';
|
|
13
13
|
import { getStrings } from '../../../i18n/context.js';
|
|
14
14
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
fields = [] as FieldDefinition<T>[],
|
|
22
22
|
creation = {} as ActionConfiguration<T>,
|
|
23
23
|
serverError = '',
|
|
24
|
+
seed = undefined as Record<string, unknown> | undefined,
|
|
24
25
|
onCancel,
|
|
25
26
|
onSuccess,
|
|
26
27
|
}: {
|
|
@@ -31,6 +32,9 @@
|
|
|
31
32
|
fields?: FieldDefinition<T>[];
|
|
32
33
|
creation?: ActionConfiguration<T>;
|
|
33
34
|
serverError?: string;
|
|
35
|
+
/** Pre-fills the form from another instance's values — used when the
|
|
36
|
+
* create view is reached via an Update form's "Duplicate" button. */
|
|
37
|
+
seed?: Record<string, unknown>;
|
|
34
38
|
onCancel?: () => void;
|
|
35
39
|
onSuccess?: () => void;
|
|
36
40
|
} = $props();
|
|
@@ -51,7 +55,9 @@
|
|
|
51
55
|
successTimeout = setTimeout(() => (successMessage = ''), 4000);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
let record = $state<Record<string, unknown>>(
|
|
58
|
+
let record = $state<Record<string, unknown>>(
|
|
59
|
+
untrack(() => (seed ? seedRecord(fields, seed) : emptyRecord(fields)))
|
|
60
|
+
);
|
|
55
61
|
|
|
56
62
|
const groups = $derived(groupFields(fields));
|
|
57
63
|
const hasFileField = $derived(fields.some((f) => f.type === 'file'));
|
|
@@ -130,6 +136,7 @@
|
|
|
130
136
|
fieldErrors = {};
|
|
131
137
|
internalError = '';
|
|
132
138
|
flashSuccess();
|
|
139
|
+
record = applyDuplicateOmit(fields, record, creation.duplication?.omit);
|
|
133
140
|
} else {
|
|
134
141
|
onSuccess?.();
|
|
135
142
|
}
|
|
@@ -7,6 +7,9 @@ declare function $$render<T extends object = Record<string, unknown>>(): {
|
|
|
7
7
|
fields?: FieldDefinition<T>[];
|
|
8
8
|
creation?: ActionConfiguration<T>;
|
|
9
9
|
serverError?: string;
|
|
10
|
+
/** Pre-fills the form from another instance's values — used when the
|
|
11
|
+
* create view is reached via an Update form's "Duplicate" button. */
|
|
12
|
+
seed?: Record<string, unknown>;
|
|
10
13
|
onCancel?: () => void;
|
|
11
14
|
onSuccess?: () => void;
|
|
12
15
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { defaultIconSet } from '../../../icons/sets/default.js';
|
|
9
9
|
import { validateAll } from '../utils/validation.js';
|
|
10
10
|
import { groupFields } from '../utils/grouping.js';
|
|
11
|
-
import {
|
|
11
|
+
import { applyDuplicateOmit, seedRecord } from '../utils/embedded.js';
|
|
12
12
|
import type { ActionConfiguration, FieldDefinition } from '../../../types/crud.js';
|
|
13
13
|
import { getStrings } from '../../../i18n/context.js';
|
|
14
14
|
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
onCancel,
|
|
27
27
|
onSuccess,
|
|
28
28
|
onContinue,
|
|
29
|
+
onDuplicate,
|
|
29
30
|
}: {
|
|
30
31
|
labelOne?: string;
|
|
31
32
|
labelMany?: string;
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
onCancel?: () => void;
|
|
40
41
|
onSuccess?: () => void;
|
|
41
42
|
onContinue?: () => void;
|
|
43
|
+
onDuplicate?: (record: Record<string, unknown>) => void;
|
|
42
44
|
} = $props();
|
|
43
45
|
|
|
44
46
|
const icons = $derived(getIconSet() ?? defaultIconSet);
|
|
@@ -47,24 +49,16 @@
|
|
|
47
49
|
let fieldErrors = $state<Record<string, string>>({});
|
|
48
50
|
let internalError = $state('');
|
|
49
51
|
let continuing = $state(false);
|
|
50
|
-
|
|
51
|
-
function seedFromInstance(inst: Record<string, unknown>): Record<string, unknown> {
|
|
52
|
-
const seeded: Record<string, unknown> = { ...inst };
|
|
53
|
-
for (const f of fields) {
|
|
54
|
-
const raw = f.seed ? f.seed(inst) : inst[f.attribute];
|
|
55
|
-
seeded[f.attribute] = seedField(f, raw);
|
|
56
|
-
}
|
|
57
|
-
return seeded;
|
|
58
|
-
}
|
|
52
|
+
let duplicating = $state(false);
|
|
59
53
|
|
|
60
54
|
let record = $state<Record<string, unknown>>(
|
|
61
|
-
untrack(() =>
|
|
55
|
+
untrack(() => seedRecord(fields, instance as Record<string, unknown>))
|
|
62
56
|
);
|
|
63
57
|
|
|
64
58
|
$effect(() => {
|
|
65
59
|
const id = (instance as Record<string, unknown>)[idKey];
|
|
66
60
|
if (!id) return;
|
|
67
|
-
untrack(() => { record =
|
|
61
|
+
untrack(() => { record = seedRecord(fields, instance as Record<string, unknown>); });
|
|
68
62
|
});
|
|
69
63
|
|
|
70
64
|
const groups = $derived(groupFields(fields));
|
|
@@ -74,6 +68,10 @@
|
|
|
74
68
|
const continueLabel = $derived(update.continue?.label ?? strings.saveAndContinue);
|
|
75
69
|
const continueClass = $derived(update.continue?.class ?? '');
|
|
76
70
|
|
|
71
|
+
const duplicationEnabled = $derived(update.duplication?.enabled ?? false);
|
|
72
|
+
const duplicationLabel = $derived(update.duplication?.label ?? strings.duplicate);
|
|
73
|
+
const duplicationClass = $derived(update.duplication?.class ?? '');
|
|
74
|
+
|
|
77
75
|
const errorEntries = $derived([
|
|
78
76
|
...((serverError || internalError) ? [['_global', internalError || serverError] as [string, string]] : []),
|
|
79
77
|
...Object.entries(fieldErrors),
|
|
@@ -123,6 +121,9 @@
|
|
|
123
121
|
if (continuing) {
|
|
124
122
|
continuing = false;
|
|
125
123
|
onContinue?.();
|
|
124
|
+
} else if (duplicating) {
|
|
125
|
+
duplicating = false;
|
|
126
|
+
onDuplicate?.(applyDuplicateOmit(fields, record, update.duplication?.omit));
|
|
126
127
|
} else {
|
|
127
128
|
onSuccess?.();
|
|
128
129
|
}
|
|
@@ -173,17 +174,27 @@
|
|
|
173
174
|
<Button variant="ghost" onclick={() => onCancel?.()}>
|
|
174
175
|
{strings.cancel}
|
|
175
176
|
</Button>
|
|
177
|
+
{#if duplicationEnabled}
|
|
178
|
+
<Button
|
|
179
|
+
type="submit"
|
|
180
|
+
variant="warning"
|
|
181
|
+
class={duplicationClass}
|
|
182
|
+
onclick={() => { continuing = false; duplicating = true; }}
|
|
183
|
+
>
|
|
184
|
+
{duplicationLabel}
|
|
185
|
+
</Button>
|
|
186
|
+
{/if}
|
|
176
187
|
{#if continueEnabled}
|
|
177
188
|
<Button
|
|
178
189
|
type="submit"
|
|
179
190
|
variant="secondary"
|
|
180
191
|
class={continueClass}
|
|
181
|
-
onclick={() => { continuing = true; }}
|
|
192
|
+
onclick={() => { continuing = true; duplicating = false; }}
|
|
182
193
|
>
|
|
183
194
|
{continueLabel}
|
|
184
195
|
</Button>
|
|
185
196
|
{/if}
|
|
186
|
-
<Button type="submit" variant="primary" onclick={() => { continuing = false; }}>
|
|
197
|
+
<Button type="submit" variant="primary" onclick={() => { continuing = false; duplicating = false; }}>
|
|
187
198
|
{strings.save}
|
|
188
199
|
</Button>
|
|
189
200
|
</div>
|
package/dist/index.d.ts
CHANGED
|
@@ -44,9 +44,9 @@ export { default as CRUDRead } from './components/crud/views/Read.svelte';
|
|
|
44
44
|
export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
|
|
45
45
|
export { default as SearchInput } from './components/crud/SearchInput.svelte';
|
|
46
46
|
export { AUTO_EXCLUDED } from './components/crud/utils/constants.js';
|
|
47
|
-
export { resolveOptions, resolveFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
|
|
47
|
+
export { resolveOptions, resolveFormatter, truncateFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
|
|
48
48
|
export { fieldLabel, initials } from './components/crud/utils/misc.js';
|
|
49
|
-
export { formatBoolean, formatDatetime,
|
|
49
|
+
export { formatBoolean, formatDatetime, formatInstance } from './components/crud/utils/formatters.js';
|
|
50
50
|
export { groupFields } from './components/crud/utils/grouping.js';
|
|
51
51
|
export type { FieldGroup } from './components/crud/utils/grouping.js';
|
|
52
52
|
export { validateAll } from './components/crud/utils/validation.js';
|
package/dist/index.js
CHANGED
|
@@ -42,9 +42,9 @@ export { default as CRUDUpdate } from './components/crud/views/Update.svelte';
|
|
|
42
42
|
export { default as SearchInput } from './components/crud/SearchInput.svelte';
|
|
43
43
|
// CRUD utilities
|
|
44
44
|
export { AUTO_EXCLUDED } from './components/crud/utils/constants.js';
|
|
45
|
-
export { resolveOptions, resolveFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
|
|
45
|
+
export { resolveOptions, resolveFormatter, truncateFormatter, inferType, buildFieldDefinitions } from './components/crud/utils/resolution.js';
|
|
46
46
|
export { fieldLabel, initials } from './components/crud/utils/misc.js';
|
|
47
|
-
export { formatBoolean, formatDatetime,
|
|
47
|
+
export { formatBoolean, formatDatetime, formatInstance } from './components/crud/utils/formatters.js';
|
|
48
48
|
export { groupFields } from './components/crud/utils/grouping.js';
|
|
49
49
|
export { validateAll } from './components/crud/utils/validation.js';
|
|
50
50
|
export { emptyRecord, defaultItemLabel } from './components/crud/utils/embedded.js';
|
|
@@ -51,7 +51,15 @@ export type AttributeMetadata = {
|
|
|
51
51
|
hidden?: boolean | HiddenResolver;
|
|
52
52
|
seed?: SeedResolver;
|
|
53
53
|
component?: CellComponent<any, any>;
|
|
54
|
+
/** Formats the raw value everywhere it's displayed read-only: the list
|
|
55
|
+
* column's cell, the Read view's field, and any other readonly Field. */
|
|
54
56
|
formatter?: FormatterResolver;
|
|
57
|
+
/** List column only: truncates the cell's text to this many characters,
|
|
58
|
+
* appending an ellipsis, applied to `formatter`'s output (or, absent one,
|
|
59
|
+
* the raw stringified value) — the Read view and Create/Update forms
|
|
60
|
+
* always show the untruncated value. Handy for a long free-text column
|
|
61
|
+
* that would otherwise blow out the table's width. */
|
|
62
|
+
truncateUpTo?: number;
|
|
55
63
|
/** Pass a function to require a field only in certain conditions, e.g. a
|
|
56
64
|
* quantity that only applies to some of a select's options. */
|
|
57
65
|
required?: boolean | RequiredResolver;
|
package/dist/types/crud.d.ts
CHANGED
|
@@ -65,6 +65,14 @@ export interface CreateFormButtonConfiguration {
|
|
|
65
65
|
label?: string;
|
|
66
66
|
class?: string;
|
|
67
67
|
}
|
|
68
|
+
/** The "Duplicate" button's configuration — a `CreateFormButtonConfiguration`
|
|
69
|
+
* plus which attributes to leave out of the duplicated draft. */
|
|
70
|
+
export interface DuplicationButtonConfiguration extends CreateFormButtonConfiguration {
|
|
71
|
+
/** Attribute names reset to the field's `default` instead of copying the
|
|
72
|
+
* source instance's value — e.g. a publication date that should depend on
|
|
73
|
+
* when the copy is itself published, not on when the original was. */
|
|
74
|
+
omit?: string[];
|
|
75
|
+
}
|
|
68
76
|
export interface ActionConfiguration<T extends object = Record<string, unknown>> {
|
|
69
77
|
enabled?: boolean;
|
|
70
78
|
label?: string;
|
|
@@ -81,12 +89,16 @@ export interface ActionConfiguration<T extends object = Record<string, unknown>>
|
|
|
81
89
|
* Falls back to reloading the current instance when there is no next
|
|
82
90
|
* one. Disabled by default — pass `{ enabled: true }` to show it. */
|
|
83
91
|
continue?: CreateFormButtonConfiguration;
|
|
84
|
-
/**
|
|
85
|
-
* submits to the same `endpoint`, but — unlike "Save and
|
|
86
|
-
* blanks the form — leaves the just-submitted values in
|
|
87
|
-
* can tweak a few fields and save again as a new
|
|
88
|
-
*
|
|
89
|
-
|
|
92
|
+
/** Shows a "Duplicate" button alongside Save/Cancel.
|
|
93
|
+
* - Create form: submits to the same `endpoint`, but — unlike "Save and
|
|
94
|
+
* continue", which blanks the form — leaves the just-submitted values in
|
|
95
|
+
* place so the user can tweak a few fields and save again as a new
|
|
96
|
+
* record.
|
|
97
|
+
* - Update form: submits the edit to the same `endpoint`, then opens the
|
|
98
|
+
* create form pre-filled with the just-saved instance's values, so a new
|
|
99
|
+
* record can be started from it without retyping everything.
|
|
100
|
+
* Disabled by default — pass `{ enabled: true }` to show it. */
|
|
101
|
+
duplication?: DuplicationButtonConfiguration;
|
|
90
102
|
}
|
|
91
103
|
export interface CustomAction<T extends object = Record<string, unknown>> {
|
|
92
104
|
label: string;
|