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