@warkypublic/svelix 0.1.24 → 0.1.26
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/Former/Former.svelte +521 -460
- package/dist/components/Former/Former.svelte.d.ts +2 -2
- package/dist/components/Gridler/components/Gridler.svelte +32 -18
- package/dist/components/Gridler/components/Gridler.svelte.d.ts +6 -0
- package/dist/components/Gridler/components/GridlerCanvas.svelte +7 -7
- package/dist/components/Gridler/components/GridlerCanvas.svelte.d.ts +1 -1
- package/dist/components/Gridler/components/GridlerFull.svelte +35 -190
- package/dist/components/Gridler/utils/cellContent.d.ts +3 -0
- package/dist/components/Gridler/utils/cellContent.js +18 -0
- package/dist/components/Gridler/utils/columns.d.ts +1 -0
- package/dist/components/Gridler/utils/columns.js +10 -0
- package/dist/components/Gridler/utils/filters.d.ts +5 -0
- package/dist/components/Gridler/utils/filters.js +73 -0
- package/dist/components/Gridler/utils/helpers.d.ts +1 -0
- package/dist/components/Gridler/utils/helpers.js +10 -0
- package/dist/components/Gridler/utils/sort.d.ts +5 -0
- package/dist/components/Gridler/utils/sort.js +36 -0
- package/dist/components/SvarkGrid/SvarkGrid.svelte +253 -30
- package/dist/components/SvarkGrid/SvarkGrid.svelte.d.ts +1 -0
- package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte +6 -2
- package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte.d.ts +4 -1
- package/dist/components/SvarkGrid/index.d.ts +1 -0
- package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +107 -6
- package/dist/components/SvarkGrid/internal/SvarkGridView.svelte.d.ts +6 -1
- package/dist/components/SvarkGrid/utils/svarGridMapping.d.ts +4 -1
- package/dist/components/Types/generic_grid.d.ts +20 -11
- package/llm/COMPONENT_GUIDE.md +248 -0
- package/package.json +2 -1
|
@@ -1,465 +1,526 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { untrack } from "svelte";
|
|
4
|
+
import type { Snippet } from "svelte";
|
|
5
|
+
import type {
|
|
6
|
+
FormRequestType,
|
|
7
|
+
FormerAPICallType,
|
|
8
|
+
FormerLayout,
|
|
9
|
+
FormerState,
|
|
10
|
+
FormerStateAndProps,
|
|
11
|
+
} from "./types";
|
|
12
|
+
import FormerButtonArea from "./FormerButtonArea.svelte";
|
|
13
|
+
import { getUUID } from "@warkypublic/artemis-kit/strings";
|
|
14
|
+
export interface Props<T = any> {
|
|
15
|
+
// Called before the GET request is made; can transform the outgoing payload.
|
|
16
|
+
beforeGet?: (data: T, state: Partial<FormerState<T>>) => Promise<T> | T;
|
|
17
|
+
afterGet?: (data: T, state: Partial<FormerState<T>>) => Promise<T> | void;
|
|
18
|
+
afterSave?: (
|
|
19
|
+
data: T,
|
|
20
|
+
state: Partial<FormerState<T>>,
|
|
21
|
+
) => Promise<void> | void;
|
|
22
|
+
beforeSave?: (data: T, state: Partial<FormerState<T>>) => Promise<T> | T;
|
|
23
|
+
children?: Snippet<[FormerState<T>]>;
|
|
24
|
+
id?: string;
|
|
25
|
+
keepOpen?: boolean;
|
|
26
|
+
layout?: FormerLayout<T>;
|
|
27
|
+
mounted?: boolean;
|
|
28
|
+
onAPICall?: FormerAPICallType<T>;
|
|
29
|
+
onChange?: (value: T, state: Partial<FormerState<T>>) => void;
|
|
30
|
+
onClose?: (data?: T) => void;
|
|
31
|
+
onConfirmDelete?: (values?: T) => Promise<boolean>;
|
|
32
|
+
onError?: (error: Error | string, state: Partial<FormerState<T>>) => void;
|
|
33
|
+
onOpen?: (data?: T) => void;
|
|
34
|
+
opened?: boolean;
|
|
35
|
+
primeData?: T;
|
|
36
|
+
request?: FormRequestType;
|
|
37
|
+
uniqueKeyField?: string;
|
|
38
|
+
values?: T;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
afterGet,
|
|
43
|
+
afterSave,
|
|
44
|
+
beforeGet,
|
|
45
|
+
beforeSave,
|
|
46
|
+
children,
|
|
47
|
+
id = getUUID(),
|
|
48
|
+
keepOpen = $bindable(false),
|
|
49
|
+
layout,
|
|
50
|
+
mounted = true,
|
|
51
|
+
onAPICall,
|
|
52
|
+
onChange,
|
|
53
|
+
onClose,
|
|
54
|
+
onConfirmDelete,
|
|
55
|
+
onError,
|
|
56
|
+
onOpen,
|
|
57
|
+
opened = $bindable(false),
|
|
58
|
+
primeData,
|
|
59
|
+
request = $bindable<FormRequestType>("insert"),
|
|
60
|
+
uniqueKeyField = "id",
|
|
61
|
+
values = $bindable<any>(undefined),
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
// Internal state
|
|
65
|
+
let loading = $state(false);
|
|
66
|
+
let error = $state<string | undefined>(undefined);
|
|
67
|
+
let dirty = $state(false);
|
|
68
|
+
let deleteConfirmed = $state(false);
|
|
69
|
+
|
|
70
|
+
// Track initial values for dirty detection
|
|
71
|
+
let initialValues = $state<any>(undefined);
|
|
72
|
+
|
|
73
|
+
// Snapshot initial values once when the form opens.
|
|
74
|
+
// untrack() prevents values/primeData reads from being tracked — only the
|
|
75
|
+
// opened transition should trigger re-initialization.
|
|
76
|
+
$effect(() => {
|
|
77
|
+
if (!mounted) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (opened) {
|
|
81
|
+
untrack(() => {
|
|
82
|
+
if (initialValues === undefined) {
|
|
83
|
+
if (values !== undefined) {
|
|
84
|
+
initialValues = JSON.parse(JSON.stringify(values));
|
|
85
|
+
} else if (primeData !== undefined) {
|
|
86
|
+
initialValues = JSON.parse(JSON.stringify(primeData));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
// Reset when form closes so re-opens start fresh.
|
|
92
|
+
initialValues = undefined;
|
|
93
|
+
dirty = false;
|
|
94
|
+
deleteConfirmed = false;
|
|
95
|
+
error = undefined;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Track dirty state
|
|
100
|
+
$effect(() => {
|
|
101
|
+
if (!mounted) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (initialValues !== undefined && values !== undefined) {
|
|
105
|
+
dirty = JSON.stringify(values) !== JSON.stringify(initialValues);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function getState<K extends keyof FormerStateAndProps>(
|
|
110
|
+
key: K,
|
|
111
|
+
): FormerStateAndProps[K] {
|
|
112
|
+
const state = getAllState();
|
|
113
|
+
return state[key];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getAllState(): FormerStateAndProps {
|
|
117
|
+
return {
|
|
118
|
+
afterGet,
|
|
119
|
+
afterSave,
|
|
120
|
+
beforeGet,
|
|
121
|
+
beforeSave,
|
|
122
|
+
deleteConfirmed,
|
|
123
|
+
dirty,
|
|
124
|
+
error,
|
|
125
|
+
getAllState,
|
|
126
|
+
getState,
|
|
127
|
+
keepOpen,
|
|
128
|
+
layout,
|
|
129
|
+
load,
|
|
130
|
+
loading,
|
|
131
|
+
onAPICall,
|
|
132
|
+
onChange,
|
|
133
|
+
onClose: handleClose,
|
|
134
|
+
onConfirmDelete,
|
|
135
|
+
onError,
|
|
136
|
+
onOpen,
|
|
137
|
+
opened,
|
|
138
|
+
primeData,
|
|
139
|
+
request,
|
|
140
|
+
reset,
|
|
141
|
+
save,
|
|
142
|
+
setRequest,
|
|
143
|
+
setState,
|
|
144
|
+
uniqueKeyField,
|
|
145
|
+
validate,
|
|
146
|
+
values,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function setState<K extends keyof FormerStateAndProps>(
|
|
151
|
+
key: K,
|
|
152
|
+
value: FormerStateAndProps[K],
|
|
153
|
+
) {
|
|
154
|
+
if (key === "opened") opened = value as boolean;
|
|
155
|
+
else if (key === "values") values = value as any;
|
|
156
|
+
else if (key === "request") request = value as FormRequestType;
|
|
157
|
+
else if (key === "keepOpen") keepOpen = value as boolean;
|
|
158
|
+
else if (key === "loading") loading = value as boolean;
|
|
159
|
+
else if (key === "error") error = value as string | undefined;
|
|
160
|
+
else if (key === "dirty") dirty = value as boolean;
|
|
161
|
+
else if (key === "deleteConfirmed") deleteConfirmed = value as boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function setRequest(r: FormRequestType) {
|
|
165
|
+
request = r;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function validate(): Promise<boolean> {
|
|
169
|
+
if (!mounted) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
// With native HTML form validation, check validity
|
|
173
|
+
const form = document.getElementById(
|
|
174
|
+
`former_f${id}`,
|
|
175
|
+
) as HTMLFormElement | null;
|
|
176
|
+
if (form) {
|
|
177
|
+
return form.reportValidity();
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function load(reset?: boolean): Promise<void> {
|
|
183
|
+
if (!mounted) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
loading = true;
|
|
188
|
+
error = undefined;
|
|
189
|
+
|
|
190
|
+
// Base data for "load" comes from existing values or primeData.
|
|
191
|
+
// If `beforeGet` is provided, it can normalize/augment this data even
|
|
192
|
+
// when no API call is configured.
|
|
193
|
+
let inData =
|
|
194
|
+
values !== undefined
|
|
195
|
+
? { ...values }
|
|
196
|
+
: primeData !== undefined
|
|
197
|
+
? { ...primeData }
|
|
198
|
+
: values;
|
|
199
|
+
if (beforeGet) {
|
|
200
|
+
const modified = await beforeGet(inData, getAllState());
|
|
201
|
+
if (modified !== undefined) inData = modified;
|
|
202
|
+
}
|
|
203
|
+
const keyValue =
|
|
204
|
+
(inData as any)?.[uniqueKeyField] ??
|
|
205
|
+
(primeData as any)?.[uniqueKeyField];
|
|
206
|
+
if (onAPICall && keyValue !== undefined) {
|
|
207
|
+
let data = await onAPICall(
|
|
208
|
+
"read",
|
|
209
|
+
request ?? "insert",
|
|
210
|
+
inData,
|
|
211
|
+
keyValue,
|
|
212
|
+
);
|
|
213
|
+
if (afterGet) {
|
|
214
|
+
const result = await afterGet(data, getAllState());
|
|
215
|
+
if (result !== undefined) data = result;
|
|
216
|
+
}
|
|
217
|
+
values = data;
|
|
218
|
+
onChange?.(data, getAllState());
|
|
219
|
+
} else if (beforeGet) {
|
|
220
|
+
// No API call: still apply `beforeGet` output to the form values.
|
|
221
|
+
values = inData;
|
|
222
|
+
onChange?.(inData, getAllState());
|
|
223
|
+
}
|
|
224
|
+
if (reset) {
|
|
225
|
+
initialValues = values ? JSON.parse(JSON.stringify(values)) : undefined;
|
|
226
|
+
dirty = false;
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
const msg = (e as Error)?.message ?? String(e);
|
|
230
|
+
error = msg;
|
|
231
|
+
onError?.(msg, getAllState());
|
|
232
|
+
} finally {
|
|
233
|
+
loading = false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function save(): Promise<any> {
|
|
238
|
+
if (!mounted) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
loading = true;
|
|
243
|
+
error = undefined;
|
|
244
|
+
|
|
245
|
+
let data = values ? { ...values } : {};
|
|
246
|
+
|
|
247
|
+
if (beforeSave) {
|
|
248
|
+
const modified = await beforeSave(data, getAllState());
|
|
249
|
+
if (modified !== undefined) data = modified;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (request === "delete" && !deleteConfirmed) {
|
|
253
|
+
const confirmed = onConfirmDelete
|
|
254
|
+
? await onConfirmDelete(data)
|
|
255
|
+
: confirm("Are you sure you want to delete this item?");
|
|
256
|
+
if (!confirmed) {
|
|
257
|
+
loading = false;
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
deleteConfirmed = true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (onAPICall) {
|
|
264
|
+
const keyValue =
|
|
265
|
+
(values as any)?.[uniqueKeyField] ??
|
|
266
|
+
(primeData as any)?.[uniqueKeyField];
|
|
267
|
+
const savedData = await onAPICall(
|
|
268
|
+
"mutate",
|
|
269
|
+
request ?? "insert",
|
|
270
|
+
data,
|
|
271
|
+
keyValue,
|
|
272
|
+
);
|
|
273
|
+
const newData = { ...data, ...savedData };
|
|
274
|
+
|
|
275
|
+
if (afterSave) {
|
|
276
|
+
await afterSave(newData, getAllState());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (keepOpen) {
|
|
280
|
+
const clearedData = { ...newData };
|
|
281
|
+
delete (clearedData as any)[uniqueKeyField];
|
|
282
|
+
values = clearedData;
|
|
283
|
+
initialValues = JSON.parse(JSON.stringify(clearedData));
|
|
284
|
+
dirty = false;
|
|
285
|
+
deleteConfirmed = false;
|
|
286
|
+
onChange?.(clearedData, getAllState());
|
|
287
|
+
return newData;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
values = newData;
|
|
291
|
+
initialValues = JSON.parse(JSON.stringify(newData));
|
|
292
|
+
dirty = false;
|
|
293
|
+
onChange?.(newData, getAllState());
|
|
294
|
+
handleClose(newData);
|
|
295
|
+
return newData;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (keepOpen) {
|
|
299
|
+
const clearedData = { ...data };
|
|
300
|
+
delete (clearedData as any)[uniqueKeyField];
|
|
301
|
+
values = clearedData;
|
|
302
|
+
initialValues = JSON.parse(JSON.stringify(clearedData));
|
|
303
|
+
dirty = false;
|
|
304
|
+
onChange?.(clearedData, getAllState());
|
|
305
|
+
return data;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
values = data;
|
|
309
|
+
initialValues = JSON.parse(JSON.stringify(data));
|
|
310
|
+
dirty = false;
|
|
311
|
+
onChange?.(data, getAllState());
|
|
312
|
+
handleClose(data);
|
|
313
|
+
return data;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
const msg = (e as Error)?.message ?? String(e);
|
|
316
|
+
error = msg;
|
|
317
|
+
onError?.(msg, getAllState());
|
|
318
|
+
} finally {
|
|
319
|
+
loading = false;
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function reset(): Promise<void> {
|
|
325
|
+
if (request !== "insert") {
|
|
326
|
+
await load(true);
|
|
327
|
+
} else {
|
|
328
|
+
values = initialValues
|
|
329
|
+
? JSON.parse(JSON.stringify(initialValues))
|
|
330
|
+
: undefined;
|
|
331
|
+
dirty = false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function handleClose(data?: any) {
|
|
336
|
+
if (dirty) {
|
|
337
|
+
if (
|
|
338
|
+
!confirm("You have unsaved changes. Are you sure you want to close?")
|
|
339
|
+
) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (onClose) {
|
|
344
|
+
onClose(data);
|
|
345
|
+
} else {
|
|
346
|
+
opened = false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Load data when opened changes (for non-insert modes).
|
|
351
|
+
// untrack() prevents reactive reads inside load() (values, onAPICall, etc.)
|
|
352
|
+
// from being tracked by this effect, which would cause a re-fetch every time
|
|
353
|
+
// load() writes back to values after a successful GET.
|
|
354
|
+
$effect(() => {
|
|
355
|
+
if (mounted && opened && request && request !== "insert") {
|
|
356
|
+
untrack(() => load(true));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const formerState: FormerState = {
|
|
361
|
+
get deleteConfirmed() {
|
|
362
|
+
return deleteConfirmed;
|
|
363
|
+
},
|
|
364
|
+
get dirty() {
|
|
365
|
+
return dirty;
|
|
366
|
+
},
|
|
367
|
+
get error() {
|
|
368
|
+
return error;
|
|
369
|
+
},
|
|
370
|
+
getAllState,
|
|
371
|
+
getState,
|
|
372
|
+
load,
|
|
373
|
+
get loading() {
|
|
374
|
+
return loading;
|
|
375
|
+
},
|
|
376
|
+
get opened() {
|
|
377
|
+
return opened;
|
|
378
|
+
},
|
|
379
|
+
get primeData() {
|
|
380
|
+
return primeData;
|
|
381
|
+
},
|
|
382
|
+
get request() {
|
|
383
|
+
return request;
|
|
384
|
+
},
|
|
385
|
+
reset,
|
|
386
|
+
save,
|
|
387
|
+
setRequest,
|
|
388
|
+
setState,
|
|
389
|
+
validate,
|
|
390
|
+
get values() {
|
|
391
|
+
return values;
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Exposed methods for bind:this
|
|
396
|
+
export async function show(): Promise<void> {
|
|
397
|
+
opened = true;
|
|
398
|
+
onOpen?.(values);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export async function close(): Promise<void> {
|
|
402
|
+
handleClose(values);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export async function saveForm(): Promise<any> {
|
|
406
|
+
return save();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export async function validateForm(): Promise<boolean> {
|
|
410
|
+
return validate();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function resetForm(): void {
|
|
414
|
+
reset();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function getValue(): any {
|
|
418
|
+
return values;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function setValue(val: any): void {
|
|
422
|
+
values = val;
|
|
423
|
+
onChange?.(val, getAllState());
|
|
424
|
+
}
|
|
370
425
|
</script>
|
|
371
426
|
|
|
372
427
|
{#if mounted}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
428
|
+
<div class="flex flex-col h-full relative" id={`former_${id}`}>
|
|
429
|
+
<!-- Top button area -->
|
|
430
|
+
{#if layout?.buttonArea === "top"}
|
|
431
|
+
{#if layout.renderTop}
|
|
432
|
+
{@render layout.renderTop(formerState)}
|
|
433
|
+
{:else}
|
|
434
|
+
<FormerButtonArea
|
|
435
|
+
closeButtonTitle={layout?.closeButtonTitle}
|
|
436
|
+
{deleteConfirmed}
|
|
437
|
+
{dirty}
|
|
438
|
+
{keepOpen}
|
|
439
|
+
{request}
|
|
440
|
+
saveButtonTitle={layout?.saveButtonTitle}
|
|
441
|
+
showKeepOpenSwitch={layout?.showKeepOpenSwitch}
|
|
442
|
+
onClose={() => handleClose(values)}
|
|
443
|
+
onSave={save}
|
|
444
|
+
onToggleKeepOpen={(c) => (keepOpen = c)}
|
|
445
|
+
/>
|
|
446
|
+
{/if}
|
|
447
|
+
{/if}
|
|
448
|
+
|
|
449
|
+
<!-- Scrollable content area -->
|
|
450
|
+
<div class="flex-1 overflow-auto p-1 relative">
|
|
451
|
+
<!-- Loading overlay -->
|
|
452
|
+
{#if loading}
|
|
453
|
+
<div
|
|
454
|
+
class="absolute inset-0 bg-surface-50/50 dark:bg-surface-900/50 flex items-center justify-center z-10"
|
|
455
|
+
>
|
|
456
|
+
<span class="animate-spin text-2xl">⟳</span>
|
|
457
|
+
</div>
|
|
458
|
+
{/if}
|
|
459
|
+
|
|
460
|
+
<!-- Error message -->
|
|
461
|
+
{#if error}
|
|
462
|
+
<div class="alert preset-outlined-error-token mb-2 p-3 text-sm">
|
|
463
|
+
⚠ {error}
|
|
464
|
+
<button class="ml-2 text-xs" onclick={() => (error = undefined)}
|
|
465
|
+
>✕</button
|
|
466
|
+
>
|
|
467
|
+
</div>
|
|
468
|
+
{/if}
|
|
469
|
+
|
|
470
|
+
<form
|
|
471
|
+
id={`former_f${id}`}
|
|
472
|
+
data-request={request}
|
|
473
|
+
onreset={() => reset()}
|
|
474
|
+
onsubmit={(e) => {
|
|
475
|
+
e.preventDefault();
|
|
476
|
+
save();
|
|
477
|
+
}}
|
|
478
|
+
>
|
|
479
|
+
<fieldset disabled={request === "delete"} class="contents">
|
|
480
|
+
{#if children}
|
|
481
|
+
{@render children(formerState)}
|
|
482
|
+
{/if}
|
|
483
|
+
</fieldset>
|
|
484
|
+
</form>
|
|
485
|
+
|
|
486
|
+
{#if request === "delete"}
|
|
487
|
+
<div
|
|
488
|
+
class="mt-3 p-3 rounded border border-error-500 bg-error-50 dark:bg-error-950 text-sm space-y-2"
|
|
489
|
+
>
|
|
490
|
+
<p class="font-semibold text-error-700 dark:text-error-300">
|
|
491
|
+
⚠ This action cannot be undone
|
|
492
|
+
</p>
|
|
493
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
494
|
+
<input
|
|
495
|
+
type="checkbox"
|
|
496
|
+
class="checkbox"
|
|
497
|
+
checked={deleteConfirmed}
|
|
498
|
+
onchange={(e) => (deleteConfirmed = e.currentTarget.checked)}
|
|
499
|
+
/>
|
|
500
|
+
<span>I confirm I want to permanently delete this record</span>
|
|
501
|
+
</label>
|
|
502
|
+
</div>
|
|
503
|
+
{/if}
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<!-- Bottom button area -->
|
|
507
|
+
{#if !layout?.buttonArea || layout?.buttonArea === "bottom"}
|
|
508
|
+
{#if layout?.renderBottom}
|
|
509
|
+
{@render layout.renderBottom(formerState)}
|
|
510
|
+
{:else if layout?.buttonArea !== "none"}
|
|
511
|
+
<FormerButtonArea
|
|
512
|
+
closeButtonTitle={layout?.closeButtonTitle}
|
|
513
|
+
{deleteConfirmed}
|
|
514
|
+
{dirty}
|
|
515
|
+
{keepOpen}
|
|
516
|
+
{request}
|
|
517
|
+
saveButtonTitle={layout?.saveButtonTitle}
|
|
518
|
+
showKeepOpenSwitch={layout?.showKeepOpenSwitch}
|
|
519
|
+
onClose={() => handleClose(values)}
|
|
520
|
+
onSave={save}
|
|
521
|
+
onToggleKeepOpen={(c) => (keepOpen = c)}
|
|
522
|
+
/>
|
|
523
|
+
{/if}
|
|
524
|
+
{/if}
|
|
525
|
+
</div>
|
|
465
526
|
{/if}
|