@warkypublic/svelix 0.1.46 → 0.2.1

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 (129) hide show
  1. package/README.md +93 -33
  2. package/dist/components/BetterMenu/BetterMenu.svelte +21 -37
  3. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte +21 -31
  4. package/dist/components/BetterMenu/BetterMenuPreview.svelte +27 -29
  5. package/dist/components/BetterMenu/MenuRenderer.svelte +40 -51
  6. package/dist/components/Boxer/Boxer.svelte +492 -602
  7. package/dist/components/Boxer/BoxerTarget.svelte +6 -46
  8. package/dist/components/Boxer/BoxerTarget.svelte.d.ts +1 -0
  9. package/dist/components/Boxer/types.d.ts +0 -1
  10. package/dist/components/Button.svelte +19 -43
  11. package/dist/components/ButtonPreview.svelte +2 -11
  12. package/dist/components/CardGrid/CardGrid.svelte +489 -614
  13. package/dist/components/CardGrid/CardGrid.svelte.d.ts +4 -0
  14. package/dist/components/CardGrid/CardGridFilterPanel.svelte +111 -90
  15. package/dist/components/CardGrid/DefaultCard.svelte +86 -97
  16. package/dist/components/CardGrid/DefaultCard.svelte.d.ts +1 -0
  17. package/dist/components/CardGrid/ImageCardStory.svelte +87 -36
  18. package/dist/components/CardGrid/cardGrid.d.ts +24 -0
  19. package/dist/components/CardGrid/cardGrid.js +20 -0
  20. package/dist/components/ContentEditor/CollaboraInsertTextDemo.svelte +5 -10
  21. package/dist/components/ContentEditor/ContentEditor.svelte +24 -50
  22. package/dist/components/ContentEditor/subcomponents/AudioPlayer.svelte +17 -17
  23. package/dist/components/ContentEditor/subcomponents/CollaboraEditor.svelte +244 -309
  24. package/dist/components/ContentEditor/subcomponents/EmailViewer.svelte +2 -3
  25. package/dist/components/ContentEditor/subcomponents/ImageViewer.svelte +14 -15
  26. package/dist/components/ContentEditor/subcomponents/MarkdownViewer.svelte +74 -90
  27. package/dist/components/ContentEditor/subcomponents/MonacoEditor.svelte +75 -103
  28. package/dist/components/ContentEditor/subcomponents/Office365Editor.svelte +234 -297
  29. package/dist/components/ContentEditor/subcomponents/PdfViewer.svelte +14 -20
  30. package/dist/components/ContentEditor/subcomponents/TextEditor.svelte +122 -150
  31. package/dist/components/ContentEditor/subcomponents/UnknownFile.svelte +105 -126
  32. package/dist/components/ContentEditor/subcomponents/VideoPlayer.svelte +17 -17
  33. package/dist/components/ContentEditor/subcomponents/ZipViewer.svelte +2 -3
  34. package/dist/components/ErrorBoundary/ErrorBoundary.svelte +41 -58
  35. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +8 -16
  36. package/dist/components/Former/Former.svelte +306 -424
  37. package/dist/components/Former/FormerButtonArea.svelte +17 -68
  38. package/dist/components/Former/FormerDrawer.svelte +16 -80
  39. package/dist/components/Former/FormerDrawerPreview.svelte +99 -89
  40. package/dist/components/Former/FormerModal.svelte +16 -74
  41. package/dist/components/Former/FormerModalPreview.svelte +99 -89
  42. package/dist/components/Former/FormerPreview.svelte +76 -78
  43. package/dist/components/Former/FormerRestApiPreview.svelte +55 -48
  44. package/dist/components/Former/formerOverlayHelpers.svelte.d.ts +11 -0
  45. package/dist/components/Former/formerOverlayHelpers.svelte.js +38 -0
  46. package/dist/components/Former/index.d.ts +1 -0
  47. package/dist/components/Former/index.js +1 -0
  48. package/dist/components/FormerControllers/ButtonCtrl.svelte +13 -49
  49. package/dist/components/FormerControllers/CtrlFieldShell.svelte +15 -0
  50. package/dist/components/FormerControllers/CtrlFieldShell.svelte.d.ts +12 -0
  51. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +285 -457
  52. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +95 -136
  53. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +3 -62
  54. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +143 -217
  55. package/dist/components/FormerControllers/IconButtonCtrl.svelte +13 -47
  56. package/dist/components/FormerControllers/InlineWrapper.svelte +12 -41
  57. package/dist/components/FormerControllers/InlineWrapperPreview.svelte +4 -26
  58. package/dist/components/FormerControllers/NativeSelectCtrl.svelte +7 -39
  59. package/dist/components/FormerControllers/NumberInputCtrl.svelte +21 -43
  60. package/dist/components/FormerControllers/PasswordInputCtrl.svelte +8 -33
  61. package/dist/components/FormerControllers/SwitchCtrl.svelte +4 -20
  62. package/dist/components/FormerControllers/TextAreaCtrl.svelte +7 -34
  63. package/dist/components/FormerControllers/TextInputCtrl.svelte +7 -34
  64. package/dist/components/FormerControllers/ctrlLoading.svelte.d.ts +8 -0
  65. package/dist/components/FormerControllers/ctrlLoading.svelte.js +43 -0
  66. package/dist/components/FormerControllers/index.d.ts +2 -0
  67. package/dist/components/FormerControllers/index.js +2 -0
  68. package/dist/components/FormerControllers/utils.d.ts +1 -0
  69. package/dist/components/FormerControllers/utils.js +3 -0
  70. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +73 -120
  71. package/dist/components/Gridler/components/Gridler.svelte +344 -442
  72. package/dist/components/Gridler/components/GridlerCanvas.svelte +720 -1115
  73. package/dist/components/Gridler/components/GridlerEditor.svelte +2 -15
  74. package/dist/components/Gridler/components/GridlerFull.svelte +521 -841
  75. package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +1 -1
  76. package/dist/components/Gridler/components/GridlerFullWithFormerPreview.svelte +194 -147
  77. package/dist/components/Gridler/components/GridlerSearch.svelte +7 -16
  78. package/dist/components/Gridler/components/GridlerSearchToggle.svelte +7 -19
  79. package/dist/components/Gridler/types.d.ts +1 -1
  80. package/dist/components/Gridler/utils/serverPaging.d.ts +1 -0
  81. package/dist/components/Gridler/utils/serverPaging.js +7 -0
  82. package/dist/components/Icons/IconAdd.svelte +1 -3
  83. package/dist/components/Icons/IconAlertCircle.svelte +1 -3
  84. package/dist/components/Icons/IconAlertTriangle.svelte +1 -3
  85. package/dist/components/Icons/IconCamera.svelte +1 -3
  86. package/dist/components/Icons/IconClose.svelte +1 -3
  87. package/dist/components/Icons/IconEdit.svelte +1 -3
  88. package/dist/components/Icons/IconFilter.svelte +1 -3
  89. package/dist/components/Icons/IconSearch.svelte +1 -3
  90. package/dist/components/Icons/IconSort.svelte +1 -3
  91. package/dist/components/Icons/IconTrash.svelte +1 -3
  92. package/dist/components/Portal/Portal.svelte +14 -38
  93. package/dist/components/Screenshot/Screenshot.svelte +7 -13
  94. package/dist/components/Svark/Svark.svelte +680 -852
  95. package/dist/components/Svark/SvarkContextMenu.svelte +44 -63
  96. package/dist/components/Svark/SvarkInfiniteLoader.svelte +2 -6
  97. package/dist/components/Svark/SvarkPager.svelte +2 -28
  98. package/dist/components/Svark/SvarkSelectionDemo.svelte +4 -8
  99. package/dist/components/Svark/SvarkTopBar.svelte +9 -74
  100. package/dist/components/SvarkGrid/SvarkGrid.svelte +877 -1123
  101. package/dist/components/SvarkGrid/components/SvarkColumnFilterForm.svelte +45 -58
  102. package/dist/components/SvarkGrid/components/SvarkContextMenu.svelte +44 -63
  103. package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte +41 -75
  104. package/dist/components/SvarkGrid/components/SvarkInfiniteLoader.svelte +2 -6
  105. package/dist/components/SvarkGrid/components/SvarkPager.svelte +2 -28
  106. package/dist/components/SvarkGrid/components/SvarkSearchPopover.svelte +8 -16
  107. package/dist/components/SvarkGrid/components/SvarkTopBar.svelte +6 -62
  108. package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +345 -507
  109. package/dist/components/VTree/VTree.svelte +454 -431
  110. package/dist/components/VTree/VTree.utils.d.ts +23 -0
  111. package/dist/components/VTree/VTree.utils.js +73 -0
  112. package/dist/components/VTree/VTreeContextMenu.svelte +21 -33
  113. package/dist/components/VTree/VTreeControlledSelectionDemo.svelte +20 -0
  114. package/dist/components/VTree/VTreeControlledSelectionDemo.svelte.d.ts +18 -0
  115. package/dist/components/VTree/VTreeCustomizationDemo.svelte +23 -0
  116. package/dist/components/VTree/VTreeCustomizationDemo.svelte.d.ts +18 -0
  117. package/dist/components/VTree/VTreeEventsDemo.svelte +48 -58
  118. package/dist/components/VTree/VTreeInteractionDemo.svelte +29 -0
  119. package/dist/components/VTree/VTreeInteractionDemo.svelte.d.ts +18 -0
  120. package/dist/components/VTree/VTreeMultiSelectDemo.svelte +27 -0
  121. package/dist/components/VTree/VTreeMultiSelectDemo.svelte.d.ts +18 -0
  122. package/dist/components/VTree/VTreeRow.svelte +77 -115
  123. package/dist/components/VTree/VTreeRow.svelte.d.ts +34 -18
  124. package/dist/components/VTree/VTreeSearch.svelte +6 -13
  125. package/dist/components/VTree/VTreeVirtualViewport.svelte +54 -143
  126. package/dist/components/VTree/VTreeVirtualViewport.svelte.d.ts +35 -18
  127. package/dist/components/VTree/types.d.ts +39 -1
  128. package/llm/COMPONENT_GUIDE.md +25 -0
  129. package/package.json +51 -50
@@ -1,427 +1,309 @@
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 {
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
- }
1
+ <script lang="ts">/* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { untrack } from "svelte";
3
+ import FormerButtonArea from "./FormerButtonArea.svelte";
4
+ import { getUUID } from "@warkypublic/artemis-kit/strings";
5
+ let { afterGet, afterSave, beforeGet, beforeSave, children, id = getUUID(), keepOpen = $bindable(false), layout, mounted = true, onAPICall, onChange, onClose, onConfirmDelete, onError, onOpen, opened = $bindable(false), primeData, request = $bindable("insert"), uniqueKeyField = "id", values = $bindable(undefined) } = $props();
6
+ // Internal state
7
+ let loading = $state(false);
8
+ let error = $state(undefined);
9
+ let dirty = $state(false);
10
+ let deleteConfirmed = $state(false);
11
+ // Track initial values for dirty detection
12
+ let initialValues = $state(undefined);
13
+ // Snapshot initial values once when the form opens.
14
+ // untrack() prevents values/primeData reads from being tracked — only the
15
+ // opened transition should trigger re-initialization.
16
+ $effect(() => {
17
+ if (!mounted) {
18
+ return;
19
+ }
20
+ if (opened) {
21
+ untrack(() => {
22
+ if (initialValues === undefined) {
23
+ if (values !== undefined) {
24
+ initialValues = JSON.parse(JSON.stringify(values));
25
+ } else if (primeData !== undefined) {
26
+ initialValues = JSON.parse(JSON.stringify(primeData));
27
+ }
28
+ }
29
+ });
30
+ } else {
31
+ // Reset when form closes so re-opens start fresh.
32
+ initialValues = undefined;
33
+ dirty = false;
34
+ deleteConfirmed = false;
35
+ error = undefined;
36
+ }
37
+ });
38
+ // Track dirty state
39
+ $effect(() => {
40
+ if (!mounted) {
41
+ return;
42
+ }
43
+ if (initialValues !== undefined && values !== undefined) {
44
+ dirty = JSON.stringify(values) !== JSON.stringify(initialValues);
45
+ }
46
+ });
47
+ function getState(key) {
48
+ const state = getAllState();
49
+ return state[key];
50
+ }
51
+ function getAllState() {
52
+ return {
53
+ afterGet,
54
+ afterSave,
55
+ beforeGet,
56
+ beforeSave,
57
+ deleteConfirmed,
58
+ dirty,
59
+ error,
60
+ getAllState,
61
+ getState,
62
+ keepOpen,
63
+ layout,
64
+ load,
65
+ loading,
66
+ onAPICall,
67
+ onChange,
68
+ onClose: handleClose,
69
+ onConfirmDelete,
70
+ onError,
71
+ onOpen,
72
+ opened,
73
+ primeData,
74
+ request,
75
+ reset,
76
+ save,
77
+ setRequest,
78
+ setState,
79
+ uniqueKeyField,
80
+ validate,
81
+ values
82
+ };
83
+ }
84
+ function setState(key, value) {
85
+ if (key === "opened") opened = value;
86
+ else if (key === "values") values = value;
87
+ else if (key === "request") request = value;
88
+ else if (key === "keepOpen") keepOpen = value;
89
+ else if (key === "loading") loading = value;
90
+ else if (key === "error") error = value;
91
+ else if (key === "dirty") dirty = value;
92
+ else if (key === "deleteConfirmed") deleteConfirmed = value;
93
+ }
94
+ function setRequest(r) {
95
+ request = r;
96
+ }
97
+ async function validate() {
98
+ if (!mounted) {
99
+ return true;
100
+ }
101
+ // With native HTML form validation, check validity
102
+ const form = document.getElementById(`former_f${id}`);
103
+ if (form) {
104
+ return form.reportValidity();
105
+ }
106
+ return true;
107
+ }
108
+ async function load(reset) {
109
+ if (!mounted) {
110
+ return;
111
+ }
112
+ try {
113
+ loading = true;
114
+ error = undefined;
115
+ // Base data for "load" comes from existing values or primeData.
116
+ // If `beforeGet` is provided, it can normalize/augment this data even
117
+ // when no API call is configured.
118
+ let inData = values !== undefined ? { ...values } : primeData !== undefined ? { ...primeData } : values;
119
+ if (beforeGet) {
120
+ const modified = await beforeGet(inData, getAllState());
121
+ if (modified !== undefined) inData = modified;
122
+ }
123
+ const keyValue = inData?.[uniqueKeyField] ?? primeData?.[uniqueKeyField];
124
+ if (onAPICall && keyValue !== undefined) {
125
+ let data = await onAPICall("read", request ?? "insert", inData, keyValue);
126
+ if (afterGet) {
127
+ const result = await afterGet(data, getAllState());
128
+ if (result !== undefined) data = result;
129
+ }
130
+ values = data;
131
+ onChange?.(data, getAllState());
132
+ } else if (beforeGet) {
133
+ // No API call: still apply `beforeGet` output to the form values.
134
+ values = inData;
135
+ onChange?.(inData, getAllState());
136
+ }
137
+ if (reset) {
138
+ initialValues = values ? JSON.parse(JSON.stringify(values)) : undefined;
139
+ dirty = false;
140
+ }
141
+ } catch (e) {
142
+ const msg = e?.message ?? String(e);
143
+ error = msg;
144
+ onError?.(msg, getAllState());
145
+ } finally {
146
+ loading = false;
147
+ }
148
+ }
149
+ async function save() {
150
+ if (!mounted) {
151
+ return undefined;
152
+ }
153
+ try {
154
+ loading = true;
155
+ error = undefined;
156
+ let data = values ? { ...values } : {};
157
+ if (beforeSave) {
158
+ const modified = await beforeSave(data, getAllState());
159
+ if (modified !== undefined) data = modified;
160
+ }
161
+ if (request === "delete" && !deleteConfirmed) {
162
+ const confirmed = onConfirmDelete ? await onConfirmDelete(data) : confirm("Are you sure you want to delete this item?");
163
+ if (!confirmed) {
164
+ loading = false;
165
+ return undefined;
166
+ }
167
+ deleteConfirmed = true;
168
+ }
169
+ if (onAPICall) {
170
+ const keyValue = values?.[uniqueKeyField] ?? primeData?.[uniqueKeyField];
171
+ const savedData = await onAPICall("mutate", request ?? "insert", data, keyValue);
172
+ const newData = {
173
+ ...data,
174
+ ...savedData
175
+ };
176
+ if (afterSave) {
177
+ await afterSave(newData, getAllState());
178
+ }
179
+ if (keepOpen) {
180
+ const clearedData = { ...newData };
181
+ delete clearedData[uniqueKeyField];
182
+ values = clearedData;
183
+ initialValues = JSON.parse(JSON.stringify(clearedData));
184
+ dirty = false;
185
+ deleteConfirmed = false;
186
+ onChange?.(clearedData, getAllState());
187
+ return newData;
188
+ }
189
+ values = newData;
190
+ initialValues = JSON.parse(JSON.stringify(newData));
191
+ dirty = false;
192
+ onChange?.(newData, getAllState());
193
+ handleClose(newData);
194
+ return newData;
195
+ }
196
+ if (keepOpen) {
197
+ const clearedData = { ...data };
198
+ delete clearedData[uniqueKeyField];
199
+ values = clearedData;
200
+ initialValues = JSON.parse(JSON.stringify(clearedData));
201
+ dirty = false;
202
+ onChange?.(clearedData, getAllState());
203
+ return data;
204
+ }
205
+ values = data;
206
+ initialValues = JSON.parse(JSON.stringify(data));
207
+ dirty = false;
208
+ onChange?.(data, getAllState());
209
+ handleClose(data);
210
+ return data;
211
+ } catch (e) {
212
+ const msg = e?.message ?? String(e);
213
+ error = msg;
214
+ onError?.(msg, getAllState());
215
+ } finally {
216
+ loading = false;
217
+ }
218
+ return undefined;
219
+ }
220
+ async function reset() {
221
+ if (request !== "insert") {
222
+ await load(true);
223
+ } else {
224
+ values = initialValues ? JSON.parse(JSON.stringify(initialValues)) : undefined;
225
+ dirty = false;
226
+ }
227
+ }
228
+ function handleClose(data) {
229
+ if (dirty) {
230
+ if (!confirm("You have unsaved changes. Are you sure you want to close?")) {
231
+ return;
232
+ }
233
+ }
234
+ if (onClose) {
235
+ onClose(data);
236
+ } else {
237
+ opened = false;
238
+ }
239
+ }
240
+ // Load data when opened changes (for non-insert modes).
241
+ // untrack() prevents reactive reads inside load() (values, onAPICall, etc.)
242
+ // from being tracked by this effect, which would cause a re-fetch every time
243
+ // load() writes back to values after a successful GET.
244
+ $effect(() => {
245
+ if (mounted && opened && request && request !== "insert") {
246
+ untrack(() => load(true));
247
+ }
248
+ });
249
+ const formerState = {
250
+ get deleteConfirmed() {
251
+ return deleteConfirmed;
252
+ },
253
+ get dirty() {
254
+ return dirty;
255
+ },
256
+ get error() {
257
+ return error;
258
+ },
259
+ getAllState,
260
+ getState,
261
+ load,
262
+ get loading() {
263
+ return loading;
264
+ },
265
+ get opened() {
266
+ return opened;
267
+ },
268
+ get primeData() {
269
+ return primeData;
270
+ },
271
+ get request() {
272
+ return request;
273
+ },
274
+ reset,
275
+ save,
276
+ setRequest,
277
+ setState,
278
+ validate,
279
+ get values() {
280
+ return values;
281
+ }
282
+ };
283
+ // Exposed methods for bind:this
284
+ export async function show() {
285
+ opened = true;
286
+ onOpen?.(values);
287
+ }
288
+ export async function close() {
289
+ handleClose(values);
290
+ }
291
+ export async function saveForm() {
292
+ return save();
293
+ }
294
+ export async function validateForm() {
295
+ return validate();
296
+ }
297
+ export function resetForm() {
298
+ reset();
299
+ }
300
+ export function getValue() {
301
+ return values;
302
+ }
303
+ export function setValue(val) {
304
+ values = val;
305
+ onChange?.(val, getAllState());
306
+ }
425
307
  </script>
426
308
 
427
309
  {#if mounted}