@formisch/svelte 0.8.0 → 0.10.0
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/README.md +2 -1
- package/dist/core/index.svelte.d.ts +269 -166
- package/dist/core/index.svelte.js +371 -139
- package/dist/methods/index.svelte.d.ts +265 -8
- package/dist/methods/index.svelte.js +63 -29
- package/dist/runes/createForm/createForm.svelte.js +4 -0
- package/dist/runes/useField/useField.svelte.js +11 -1
- package/dist/runes/useFieldArray/useFieldArray.svelte.js +4 -0
- package/dist/types/field.d.ts +8 -0
- package/dist/types/form.d.ts +5 -1
- package/package.json +4 -3
|
@@ -68,11 +68,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
68
68
|
else {
|
|
69
69
|
internalFieldStore.schema = schema;
|
|
70
70
|
internalFieldStore.name = JSON.stringify(path);
|
|
71
|
+
internalFieldStore.path = path;
|
|
71
72
|
const initialElements = [];
|
|
72
73
|
internalFieldStore.initialElements = initialElements;
|
|
73
74
|
internalFieldStore.elements = initialElements;
|
|
74
75
|
internalFieldStore.errors = /* @__PURE__ */ createSignal(null);
|
|
75
76
|
internalFieldStore.isTouched = /* @__PURE__ */ createSignal(false);
|
|
77
|
+
internalFieldStore.isEdited = /* @__PURE__ */ createSignal(false);
|
|
76
78
|
internalFieldStore.isDirty = /* @__PURE__ */ createSignal(false);
|
|
77
79
|
if (schema.type === "array" || schema.type === "loose_tuple" || schema.type === "strict_tuple" || schema.type === "tuple") {
|
|
78
80
|
if (internalFieldStore.kind && internalFieldStore.kind !== "array") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "array"`);
|
|
@@ -82,16 +84,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
82
84
|
if (schema.type === "array") {
|
|
83
85
|
if (initialInput) for (let index = 0; index < initialInput.length; index++) {
|
|
84
86
|
internalFieldStore.children[index] = {};
|
|
85
|
-
|
|
86
|
-
initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], path);
|
|
87
|
-
path.pop();
|
|
87
|
+
initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], [...path, index]);
|
|
88
88
|
}
|
|
89
89
|
} else for (let index = 0; index < schema.items.length; index++) {
|
|
90
90
|
internalFieldStore.children[index] = {};
|
|
91
|
-
|
|
92
|
-
initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], path);
|
|
93
|
-
path.pop();
|
|
91
|
+
initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], [...path, index]);
|
|
94
92
|
}
|
|
93
|
+
internalFieldStore.isNullish = nullish;
|
|
95
94
|
const arrayInput = nullish && initialInput == null ? initialInput : true;
|
|
96
95
|
internalFieldStore.initialInput = /* @__PURE__ */ createSignal(arrayInput);
|
|
97
96
|
internalFieldStore.startInput = /* @__PURE__ */ createSignal(arrayInput);
|
|
@@ -108,10 +107,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
108
107
|
internalFieldStore.children ??= {};
|
|
109
108
|
for (const key in schema.entries) {
|
|
110
109
|
internalFieldStore.children[key] ??= {};
|
|
111
|
-
|
|
112
|
-
initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
|
|
113
|
-
path.pop();
|
|
110
|
+
initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], [...path, key]);
|
|
114
111
|
}
|
|
112
|
+
internalFieldStore.isNullish = nullish;
|
|
115
113
|
const objectInput = nullish && initialInput == null ? initialInput : true;
|
|
116
114
|
internalFieldStore.initialInput = /* @__PURE__ */ createSignal(objectInput);
|
|
117
115
|
internalFieldStore.startInput = /* @__PURE__ */ createSignal(objectInput);
|
|
@@ -134,8 +132,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
134
132
|
/**
|
|
135
133
|
* Copies the deeply nested state (signal values) from one field store to
|
|
136
134
|
* another. This includes the `elements`, `errors`, `startInput`, `input`,
|
|
137
|
-
* `isTouched`, `isDirty`, and for arrays `startItems` and `items`
|
|
138
|
-
* Recursively walks through the field stores and copies all signal
|
|
135
|
+
* `isTouched`, `isEdited`, `isDirty`, and for arrays `startItems` and `items`
|
|
136
|
+
* properties. Recursively walks through the field stores and copies all signal
|
|
137
|
+
* values.
|
|
139
138
|
*
|
|
140
139
|
* @param fromInternalFieldStore The source field store to copy from.
|
|
141
140
|
* @param toInternalFieldStore The destination field store to copy to.
|
|
@@ -148,19 +147,16 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
148
147
|
toInternalFieldStore.startInput.value = fromInternalFieldStore.startInput.value;
|
|
149
148
|
toInternalFieldStore.input.value = fromInternalFieldStore.input.value;
|
|
150
149
|
toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
|
|
150
|
+
toInternalFieldStore.isEdited.value = fromInternalFieldStore.isEdited.value;
|
|
151
151
|
toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
|
|
152
152
|
if (fromInternalFieldStore.kind === "array" && toInternalFieldStore.kind === "array") {
|
|
153
153
|
const fromItems = fromInternalFieldStore.items.value;
|
|
154
154
|
toInternalFieldStore.startItems.value = fromInternalFieldStore.startItems.value;
|
|
155
155
|
toInternalFieldStore.items.value = fromItems;
|
|
156
|
-
let path;
|
|
157
156
|
for (let index = 0; index < fromItems.length; index++) {
|
|
158
157
|
if (!toInternalFieldStore.children[index]) {
|
|
159
|
-
path ??= JSON.parse(toInternalFieldStore.name);
|
|
160
158
|
toInternalFieldStore.children[index] = {};
|
|
161
|
-
|
|
162
|
-
initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, path);
|
|
163
|
-
path.pop();
|
|
159
|
+
initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, [...toInternalFieldStore.path, index]);
|
|
164
160
|
}
|
|
165
161
|
copyItemState(fromInternalFieldStore.children[index], toInternalFieldStore.children[index]);
|
|
166
162
|
}
|
|
@@ -173,37 +169,53 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
173
169
|
//#region src/array/resetItemState/resetItemState.ts
|
|
174
170
|
/**
|
|
175
171
|
* Resets the state of a field store (signal values) deeply nested. Sets
|
|
176
|
-
* `elements` to empty array, `errors` to `null`, `isTouched
|
|
177
|
-
* `false`, and `startInput`, `input`, `startItems`, and `items` to
|
|
178
|
-
* input value. Keeps the `initialInput` and `initialItems` state
|
|
179
|
-
* form reset functionality.
|
|
172
|
+
* `elements` to empty array, `errors` to `null`, `isTouched`, `isEdited` and
|
|
173
|
+
* `isDirty` to `false`, and `startInput`, `input`, `startItems`, and `items` to
|
|
174
|
+
* the new input value. Keeps the `initialInput` and `initialItems` state
|
|
175
|
+
* unchanged for form reset functionality.
|
|
180
176
|
*
|
|
181
177
|
* @param internalFieldStore The field store to reset.
|
|
182
|
-
* @param
|
|
178
|
+
* @param input The new input value (can be any type including array or object).
|
|
179
|
+
* @param keepStart Whether to keep `startInput` and `startItems` as the dirty
|
|
180
|
+
* baseline instead of resetting them to the new input. Used when a field store
|
|
181
|
+
* is reused for an in-place edit so its dirty state is detected correctly.
|
|
183
182
|
*/
|
|
184
|
-
function resetItemState(internalFieldStore,
|
|
183
|
+
function resetItemState(internalFieldStore, input, keepStart = false) {
|
|
185
184
|
batch(() => {
|
|
186
|
-
|
|
185
|
+
const elements = [];
|
|
186
|
+
if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
|
|
187
|
+
internalFieldStore.elements = elements;
|
|
187
188
|
internalFieldStore.errors.value = null;
|
|
188
189
|
internalFieldStore.isTouched.value = false;
|
|
190
|
+
internalFieldStore.isEdited.value = false;
|
|
189
191
|
internalFieldStore.isDirty.value = false;
|
|
190
192
|
if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
|
|
191
|
-
const objectInput =
|
|
192
|
-
internalFieldStore.startInput.value = objectInput;
|
|
193
|
+
const objectInput = internalFieldStore.isNullish && input == null ? input : true;
|
|
194
|
+
if (!keepStart) internalFieldStore.startInput.value = objectInput;
|
|
193
195
|
internalFieldStore.input.value = objectInput;
|
|
194
|
-
if (internalFieldStore.kind === "array")
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
if (internalFieldStore.kind === "array") {
|
|
197
|
+
const isTuple = internalFieldStore.schema.type !== "array";
|
|
198
|
+
if (input || isTuple) {
|
|
199
|
+
const length = isTuple ? internalFieldStore.children.length : input.length;
|
|
200
|
+
const newItems = Array.from({ length }, createId);
|
|
201
|
+
if (!keepStart) internalFieldStore.startItems.value = newItems;
|
|
202
|
+
internalFieldStore.items.value = newItems;
|
|
203
|
+
for (let index = 0; index < length; index++) {
|
|
204
|
+
const itemInput = input?.[index];
|
|
205
|
+
if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], itemInput, keepStart);
|
|
206
|
+
else {
|
|
207
|
+
internalFieldStore.children[index] = {};
|
|
208
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, itemInput, [...internalFieldStore.path, index]);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
if (!keepStart) internalFieldStore.startItems.value = [];
|
|
213
|
+
internalFieldStore.items.value = [];
|
|
214
|
+
}
|
|
215
|
+
} else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
|
|
204
216
|
} else {
|
|
205
|
-
internalFieldStore.startInput.value =
|
|
206
|
-
internalFieldStore.input.value =
|
|
217
|
+
if (!keepStart) internalFieldStore.startInput.value = input;
|
|
218
|
+
internalFieldStore.input.value = input;
|
|
207
219
|
}
|
|
208
220
|
});
|
|
209
221
|
}
|
|
@@ -213,8 +225,8 @@ function resetItemState(internalFieldStore, initialInput) {
|
|
|
213
225
|
/**
|
|
214
226
|
* Swaps the deeply nested state (signal values) between two field stores. This
|
|
215
227
|
* includes the `elements`, `errors`, `startInput`, `input`, `isTouched`,
|
|
216
|
-
* `isDirty`, and for arrays `startItems` and `items` properties.
|
|
217
|
-
* walks through the field stores and swaps all signal values.
|
|
228
|
+
* `isEdited`, `isDirty`, and for arrays `startItems` and `items` properties.
|
|
229
|
+
* Recursively walks through the field stores and swaps all signal values.
|
|
218
230
|
*
|
|
219
231
|
* @param firstInternalFieldStore The first field store to swap.
|
|
220
232
|
* @param secondInternalFieldStore The second field store to swap.
|
|
@@ -237,6 +249,9 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
237
249
|
const tempIsTouched = firstInternalFieldStore.isTouched.value;
|
|
238
250
|
firstInternalFieldStore.isTouched.value = secondInternalFieldStore.isTouched.value;
|
|
239
251
|
secondInternalFieldStore.isTouched.value = tempIsTouched;
|
|
252
|
+
const tempIsEdited = firstInternalFieldStore.isEdited.value;
|
|
253
|
+
firstInternalFieldStore.isEdited.value = secondInternalFieldStore.isEdited.value;
|
|
254
|
+
secondInternalFieldStore.isEdited.value = tempIsEdited;
|
|
240
255
|
const tempIsDirty = firstInternalFieldStore.isDirty.value;
|
|
241
256
|
firstInternalFieldStore.isDirty.value = secondInternalFieldStore.isDirty.value;
|
|
242
257
|
secondInternalFieldStore.isDirty.value = tempIsDirty;
|
|
@@ -249,22 +264,14 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
249
264
|
firstInternalFieldStore.items.value = secondItems;
|
|
250
265
|
secondInternalFieldStore.items.value = firstItems;
|
|
251
266
|
const maxLength = Math.max(firstItems.length, secondItems.length);
|
|
252
|
-
let firstPath;
|
|
253
|
-
let secondPath;
|
|
254
267
|
for (let index = 0; index < maxLength; index++) {
|
|
255
268
|
if (!firstInternalFieldStore.children[index]) {
|
|
256
|
-
firstPath ??= JSON.parse(firstInternalFieldStore.name);
|
|
257
269
|
firstInternalFieldStore.children[index] = {};
|
|
258
|
-
|
|
259
|
-
initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, firstPath);
|
|
260
|
-
firstPath.pop();
|
|
270
|
+
initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, [...firstInternalFieldStore.path, index]);
|
|
261
271
|
}
|
|
262
272
|
if (!secondInternalFieldStore.children[index]) {
|
|
263
|
-
secondPath ??= JSON.parse(secondInternalFieldStore.name);
|
|
264
273
|
secondInternalFieldStore.children[index] = {};
|
|
265
|
-
|
|
266
|
-
initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, secondPath);
|
|
267
|
-
secondPath.pop();
|
|
274
|
+
initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, [...secondInternalFieldStore.path, index]);
|
|
268
275
|
}
|
|
269
276
|
swapItemState(firstInternalFieldStore.children[index], secondInternalFieldStore.children[index]);
|
|
270
277
|
}
|
|
@@ -273,6 +280,57 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
273
280
|
});
|
|
274
281
|
}
|
|
275
282
|
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/field/focusFieldElement/focusFieldElement.ts
|
|
285
|
+
/**
|
|
286
|
+
* Focuses the first focusable element of a field store. The elements are tried
|
|
287
|
+
* in order and the first one that actually receives focus wins, so detached,
|
|
288
|
+
* disabled or hidden elements are skipped. The browser decides focusability,
|
|
289
|
+
* which is read back via the element's root `activeElement` so elements in a
|
|
290
|
+
* shadow root or another document are handled correctly.
|
|
291
|
+
*
|
|
292
|
+
* Hint: A `display: none` or `hidden` element is correctly skipped in real
|
|
293
|
+
* browsers, but jsdom has no layout and focuses it anyway, so that case cannot
|
|
294
|
+
* be covered by unit tests.
|
|
295
|
+
*
|
|
296
|
+
* @param internalFieldStore The field store to focus.
|
|
297
|
+
*
|
|
298
|
+
* @returns Whether an element was focused.
|
|
299
|
+
*/
|
|
300
|
+
function focusFieldElement(internalFieldStore) {
|
|
301
|
+
for (const element of internalFieldStore.elements) {
|
|
302
|
+
element.focus();
|
|
303
|
+
if (element.getRootNode().activeElement === element) return true;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/field/walkFieldStore/walkFieldStore.ts
|
|
310
|
+
/**
|
|
311
|
+
* Walks through the field store and all nested children, calling the callback
|
|
312
|
+
* for each field store in depth-first order. The callback may return `true` to
|
|
313
|
+
* stop the walk early, in which case `walkFieldStore` returns `true` as well.
|
|
314
|
+
*
|
|
315
|
+
* The walk reads array `items` reactively, so a reactive caller subscribes to
|
|
316
|
+
* structural changes naturally. Imperative callers that must not subscribe
|
|
317
|
+
* (e.g. when invoked inside an effect) should wrap the call in `untrack`.
|
|
318
|
+
*
|
|
319
|
+
* @param internalFieldStore The field store to walk.
|
|
320
|
+
* @param callback The callback to invoke for each field store. Return `true` to stop the walk early.
|
|
321
|
+
*
|
|
322
|
+
* @returns Whether the walk was stopped early by the callback.
|
|
323
|
+
*/
|
|
324
|
+
function walkFieldStore(internalFieldStore, callback) {
|
|
325
|
+
if (callback(internalFieldStore)) return true;
|
|
326
|
+
if (internalFieldStore.kind === "array") {
|
|
327
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (walkFieldStore(internalFieldStore.children[index], callback)) return true;
|
|
328
|
+
} else if (internalFieldStore.kind === "object") {
|
|
329
|
+
for (const key in internalFieldStore.children) if (walkFieldStore(internalFieldStore.children[key], callback)) return true;
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
276
334
|
//#endregion
|
|
277
335
|
//#region src/field/getFieldBool/getFieldBool.ts
|
|
278
336
|
/**
|
|
@@ -286,16 +344,7 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
286
344
|
*/
|
|
287
345
|
/* @__NO_SIDE_EFFECTS__ */
|
|
288
346
|
function getFieldBool(internalFieldStore, type) {
|
|
289
|
-
|
|
290
|
-
if (internalFieldStore.kind === "array") {
|
|
291
|
-
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
if (internalFieldStore.kind == "object") {
|
|
295
|
-
for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
347
|
+
return walkFieldStore(internalFieldStore, (internalFieldStore$1) => Boolean(internalFieldStore$1[type].value));
|
|
299
348
|
}
|
|
300
349
|
|
|
301
350
|
//#endregion
|
|
@@ -428,11 +477,11 @@ function getFieldStore(internalFormStore, path) {
|
|
|
428
477
|
*/
|
|
429
478
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
430
479
|
batch(() => {
|
|
431
|
-
|
|
432
|
-
internalFieldStore
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
480
|
+
untrack(() => {
|
|
481
|
+
walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
|
|
482
|
+
internalFieldStore$1[type].value = bool;
|
|
483
|
+
});
|
|
484
|
+
});
|
|
436
485
|
});
|
|
437
486
|
}
|
|
438
487
|
|
|
@@ -447,23 +496,21 @@ function setFieldBool(internalFieldStore, type, bool) {
|
|
|
447
496
|
*/
|
|
448
497
|
function setNestedInput(internalFieldStore, input) {
|
|
449
498
|
internalFieldStore.isTouched.value = true;
|
|
499
|
+
internalFieldStore.isEdited.value = true;
|
|
450
500
|
if (internalFieldStore.kind === "array") {
|
|
451
501
|
const arrayInput = input ?? [];
|
|
452
502
|
const items = internalFieldStore.items.value;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
|
|
461
|
-
path.pop();
|
|
462
|
-
}
|
|
503
|
+
const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
|
|
504
|
+
if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
|
|
505
|
+
else if (length > items.length) {
|
|
506
|
+
for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
|
|
507
|
+
else {
|
|
508
|
+
internalFieldStore.children[index] = {};
|
|
509
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], [...internalFieldStore.path, index]);
|
|
463
510
|
}
|
|
464
|
-
internalFieldStore.items.value = [...items, ...
|
|
511
|
+
internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
|
|
465
512
|
}
|
|
466
|
-
for (let index = 0; index <
|
|
513
|
+
for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
467
514
|
internalFieldStore.input.value = input == null ? input : true;
|
|
468
515
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
|
|
469
516
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -510,41 +557,22 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
510
557
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
511
558
|
batch(() => {
|
|
512
559
|
if (internalFieldStore.kind === "array") {
|
|
513
|
-
internalFieldStore.
|
|
560
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
514
561
|
const initialArrayInput = initialInput ?? [];
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
path.push(index);
|
|
520
|
-
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
|
|
521
|
-
path.pop();
|
|
522
|
-
}
|
|
562
|
+
const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
|
|
563
|
+
if (length > internalFieldStore.children.length) for (let index = internalFieldStore.children.length; index < length; index++) {
|
|
564
|
+
internalFieldStore.children[index] = {};
|
|
565
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], [...internalFieldStore.path, index]);
|
|
523
566
|
}
|
|
524
|
-
internalFieldStore.initialItems.value =
|
|
567
|
+
internalFieldStore.initialItems.value = Array.from({ length }, createId);
|
|
525
568
|
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
|
|
526
569
|
} else if (internalFieldStore.kind === "object") {
|
|
527
|
-
internalFieldStore.
|
|
570
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
528
571
|
for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
529
572
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
530
573
|
});
|
|
531
574
|
}
|
|
532
575
|
|
|
533
|
-
//#endregion
|
|
534
|
-
//#region src/field/walkFieldStore/walkFieldStore.ts
|
|
535
|
-
/**
|
|
536
|
-
* Walks through the field store and all nested children, calling the callback
|
|
537
|
-
* for each field store in depth-first order.
|
|
538
|
-
*
|
|
539
|
-
* @param internalFieldStore The field store to walk.
|
|
540
|
-
* @param callback The callback to invoke for each field store.
|
|
541
|
-
*/
|
|
542
|
-
function walkFieldStore(internalFieldStore, callback) {
|
|
543
|
-
callback(internalFieldStore);
|
|
544
|
-
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
|
|
545
|
-
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
576
|
//#endregion
|
|
549
577
|
//#region src/form/createFormStore/createFormStore.ts
|
|
550
578
|
/**
|
|
@@ -570,6 +598,203 @@ function createFormStore(config, parse) {
|
|
|
570
598
|
return store;
|
|
571
599
|
}
|
|
572
600
|
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/form/decodeFormData/decodeFormData.ts
|
|
603
|
+
const NUMBER_REGEX = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/u;
|
|
604
|
+
const ISO_DATE_TIME_REGEX = /^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])T(?:0\d|1\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?$/u;
|
|
605
|
+
const MAX_ARRAY_LENGTH = 5e3;
|
|
606
|
+
/**
|
|
607
|
+
* Unwraps wrapper and lazy schemas until a concrete schema is reached.
|
|
608
|
+
*
|
|
609
|
+
* @param schema The schema to unwrap.
|
|
610
|
+
*
|
|
611
|
+
* @returns The unwrapped schema.
|
|
612
|
+
*/
|
|
613
|
+
function unwrapSchema(schema) {
|
|
614
|
+
switch (schema.type) {
|
|
615
|
+
case "exact_optional":
|
|
616
|
+
case "nullable":
|
|
617
|
+
case "nullish":
|
|
618
|
+
case "optional":
|
|
619
|
+
case "undefinedable":
|
|
620
|
+
case "non_nullable":
|
|
621
|
+
case "non_nullish":
|
|
622
|
+
case "non_optional": return unwrapSchema(schema.wrapped);
|
|
623
|
+
case "lazy": return unwrapSchema(schema.getter(void 0));
|
|
624
|
+
default: return schema;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Returns the child schema for the given key by traversing objects, arrays,
|
|
629
|
+
* tuples and schema options. Returns `undefined` if no child schema is found.
|
|
630
|
+
*
|
|
631
|
+
* @param schema The parent schema.
|
|
632
|
+
* @param key The path key.
|
|
633
|
+
*
|
|
634
|
+
* @returns The child schema or `undefined`.
|
|
635
|
+
*/
|
|
636
|
+
function getChildSchema(schema, key) {
|
|
637
|
+
if (schema) {
|
|
638
|
+
const unwrapped = unwrapSchema(schema);
|
|
639
|
+
if (unwrapped.type === "object" || unwrapped.type === "loose_object" || unwrapped.type === "strict_object") return unwrapped.entries[key];
|
|
640
|
+
if (unwrapped.type === "array") return unwrapped.item;
|
|
641
|
+
if (unwrapped.type === "tuple" || unwrapped.type === "loose_tuple" || unwrapped.type === "strict_tuple") return unwrapped.items[key];
|
|
642
|
+
if (unwrapped.type === "union" || unwrapped.type === "intersect" || unwrapped.type === "variant") for (const option of unwrapped.options) {
|
|
643
|
+
const childSchema = getChildSchema(option, key);
|
|
644
|
+
if (childSchema !== void 0) return childSchema;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Decodes a stringified date based on its format. Empty strings become `null`.
|
|
650
|
+
*
|
|
651
|
+
* @param value The stringified value.
|
|
652
|
+
*
|
|
653
|
+
* @returns The decoded date.
|
|
654
|
+
*/
|
|
655
|
+
function decodeDate(value) {
|
|
656
|
+
if (!value || value === "null") return null;
|
|
657
|
+
if (value === "undefined") return;
|
|
658
|
+
if (ISO_DATE_TIME_REGEX.test(value)) return /* @__PURE__ */ new Date(`${value}Z`);
|
|
659
|
+
return new Date(value);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Decodes a stringified boolean. Empty strings become `null`.
|
|
663
|
+
*
|
|
664
|
+
* @param value The stringified value.
|
|
665
|
+
*
|
|
666
|
+
* @returns The decoded boolean.
|
|
667
|
+
*/
|
|
668
|
+
function decodeBoolean(value) {
|
|
669
|
+
if (!value || value === "null") return null;
|
|
670
|
+
if (value === "undefined") return;
|
|
671
|
+
return !(value === "false" || value === "off" || value === "0");
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Decodes a stringified number. Empty strings become `null` and non-numeric
|
|
675
|
+
* values become `NaN`.
|
|
676
|
+
*
|
|
677
|
+
* @param value The stringified value.
|
|
678
|
+
*
|
|
679
|
+
* @returns The decoded number.
|
|
680
|
+
*/
|
|
681
|
+
function decodeNumber(value) {
|
|
682
|
+
if (!value || value === "null") return null;
|
|
683
|
+
if (value === "undefined") return;
|
|
684
|
+
if (NUMBER_REGEX.test(value)) return Number(value);
|
|
685
|
+
return NaN;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Decodes a stringified bigint. Empty strings become `null` and invalid values
|
|
689
|
+
* are returned unchanged.
|
|
690
|
+
*
|
|
691
|
+
* @param value The stringified value.
|
|
692
|
+
*
|
|
693
|
+
* @returns The decoded bigint.
|
|
694
|
+
*/
|
|
695
|
+
function decodeBigint(value) {
|
|
696
|
+
if (!value || value === "null") return null;
|
|
697
|
+
if (value === "undefined") return;
|
|
698
|
+
try {
|
|
699
|
+
return BigInt(value);
|
|
700
|
+
} catch {
|
|
701
|
+
return value;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Decodes a single form data value based on the concrete schema type. Files
|
|
706
|
+
* and unknown types are returned unchanged.
|
|
707
|
+
*
|
|
708
|
+
* @param value The form data value.
|
|
709
|
+
* @param schema The schema of the value.
|
|
710
|
+
*
|
|
711
|
+
* @returns The decoded value.
|
|
712
|
+
*/
|
|
713
|
+
function decodeValue(value, schema) {
|
|
714
|
+
if (typeof value !== "string" || !schema) return value;
|
|
715
|
+
switch (unwrapSchema(schema).type) {
|
|
716
|
+
case "number": return decodeNumber(value);
|
|
717
|
+
case "boolean": return decodeBoolean(value);
|
|
718
|
+
case "date": return decodeDate(value);
|
|
719
|
+
case "bigint": return decodeBigint(value);
|
|
720
|
+
default: return value;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Fills in default values that are lost during the form data transfer. Booleans
|
|
725
|
+
* of unchecked checkboxes become `false` and absent arrays become empty. Only
|
|
726
|
+
* containers that are present in the decoded data are completed.
|
|
727
|
+
*
|
|
728
|
+
* @param schema The schema of the value.
|
|
729
|
+
* @param parent The parent object or array holding the value.
|
|
730
|
+
* @param key The key of the value within its parent.
|
|
731
|
+
*/
|
|
732
|
+
function fillDefaults(schema, parent, key) {
|
|
733
|
+
const unwrappedSchema = unwrapSchema(schema);
|
|
734
|
+
if (unwrappedSchema.type === "boolean") {
|
|
735
|
+
if (parent[key] === void 0) parent[key] = false;
|
|
736
|
+
} else if (unwrappedSchema.type === "array") if (Array.isArray(parent[key])) for (let index = 0; index < parent[key].length; index++) fillDefaults(unwrappedSchema.item, parent[key], index);
|
|
737
|
+
else parent[key] = [];
|
|
738
|
+
else if (unwrappedSchema.type === "tuple" || unwrappedSchema.type === "loose_tuple" || unwrappedSchema.type === "strict_tuple") {
|
|
739
|
+
if (Array.isArray(parent[key])) for (let index = 0; index < unwrappedSchema.items.length; index++) fillDefaults(unwrappedSchema.items[index], parent[key], index);
|
|
740
|
+
} else if (unwrappedSchema.type === "object" || unwrappedSchema.type === "loose_object" || unwrappedSchema.type === "strict_object") {
|
|
741
|
+
if (parent[key] && typeof parent[key] === "object") for (const entryKey in unwrappedSchema.entries) fillDefaults(unwrappedSchema.entries[entryKey], parent[key], entryKey);
|
|
742
|
+
} else if (unwrappedSchema.type === "union" || unwrappedSchema.type === "intersect" || unwrappedSchema.type === "variant") for (const option of unwrappedSchema.options) fillDefaults(option, parent, key);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Decodes the entries of a form data object into nested form values using the
|
|
746
|
+
* Valibot schema as the source of truth. Information that is lost during the
|
|
747
|
+
* transfer via HTTP, like numbers, booleans, dates and unchecked checkboxes,
|
|
748
|
+
* is restored based on the schema.
|
|
749
|
+
*
|
|
750
|
+
* The keys of the form data are expected to be the stringified field paths that
|
|
751
|
+
* Formisch assigns to its field elements (for example `["todos",0,"label"]`).
|
|
752
|
+
*
|
|
753
|
+
* @param schema The form schema.
|
|
754
|
+
* @param formData The form data object.
|
|
755
|
+
*
|
|
756
|
+
* @returns The decoded form values.
|
|
757
|
+
*/
|
|
758
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
759
|
+
function decodeFormData(schema, formData) {
|
|
760
|
+
const values = {};
|
|
761
|
+
formData.forEach((value, key) => {
|
|
762
|
+
let path = null;
|
|
763
|
+
try {
|
|
764
|
+
path = JSON.parse(key);
|
|
765
|
+
} catch {}
|
|
766
|
+
if (Array.isArray(path) && path.length > 0 && (typeof value === "string" || value.size > 0 || value.name !== "")) {
|
|
767
|
+
let parentValue = values;
|
|
768
|
+
let parentSchema = schema;
|
|
769
|
+
for (let index = 0; index < path.length; index++) {
|
|
770
|
+
const segment = path[index];
|
|
771
|
+
if (typeof segment !== "string" && typeof segment !== "number" || segment === "" || segment === "__proto__" || segment === "prototype" || segment === "constructor") break;
|
|
772
|
+
if (Array.isArray(parentValue)) {
|
|
773
|
+
if (typeof segment === "string") break;
|
|
774
|
+
if (segment >= MAX_ARRAY_LENGTH) throw new Error(`Array exceeds the maximum length of ${MAX_ARRAY_LENGTH}`);
|
|
775
|
+
}
|
|
776
|
+
const childSchema = getChildSchema(parentSchema, segment);
|
|
777
|
+
if (index === path.length - 1) {
|
|
778
|
+
const unwrappedSchema = childSchema && unwrapSchema(childSchema);
|
|
779
|
+
if (unwrappedSchema && unwrappedSchema.type === "array") {
|
|
780
|
+
parentValue[segment] ??= [];
|
|
781
|
+
parentValue[segment].push(decodeValue(value, unwrappedSchema.item));
|
|
782
|
+
} else parentValue[segment] = decodeValue(value, childSchema);
|
|
783
|
+
} else {
|
|
784
|
+
if (parentValue[segment] == null) {
|
|
785
|
+
const schemaType = childSchema && unwrapSchema(childSchema).type;
|
|
786
|
+
parentValue[segment] = schemaType === "array" || schemaType === "tuple" || schemaType === "loose_tuple" || schemaType === "strict_tuple" ? [] : {};
|
|
787
|
+
} else if (typeof parentValue[segment] !== "object") break;
|
|
788
|
+
parentValue = parentValue[segment];
|
|
789
|
+
parentSchema = childSchema;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
fillDefaults(schema, { values }, "values");
|
|
795
|
+
return values;
|
|
796
|
+
}
|
|
797
|
+
|
|
573
798
|
//#endregion
|
|
574
799
|
//#region src/form/validateFormInput/validateFormInput.ts
|
|
575
800
|
/**
|
|
@@ -585,44 +810,51 @@ function createFormStore(config, parse) {
|
|
|
585
810
|
async function validateFormInput(internalFormStore, config) {
|
|
586
811
|
internalFormStore.validators++;
|
|
587
812
|
internalFormStore.isValidating.value = true;
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const path
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const name = JSON.stringify(path);
|
|
603
|
-
const fieldErrors = nestedErrors[name];
|
|
604
|
-
if (fieldErrors) fieldErrors.push(issue.message);
|
|
605
|
-
else nestedErrors[name] = [issue.message];
|
|
606
|
-
} else if (rootErrors) rootErrors.push(issue.message);
|
|
607
|
-
else rootErrors = [issue.message];
|
|
608
|
-
}
|
|
609
|
-
let shouldFocus = config?.shouldFocus ?? false;
|
|
610
|
-
batch(() => {
|
|
611
|
-
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
612
|
-
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
613
|
-
else {
|
|
614
|
-
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
615
|
-
internalFieldStore.errors.value = fieldErrors;
|
|
616
|
-
if (shouldFocus && fieldErrors) {
|
|
617
|
-
internalFieldStore.elements[0]?.focus();
|
|
618
|
-
shouldFocus = false;
|
|
813
|
+
try {
|
|
814
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
815
|
+
let rootErrors;
|
|
816
|
+
let nestedErrors;
|
|
817
|
+
if (result.issues) {
|
|
818
|
+
nestedErrors = {};
|
|
819
|
+
for (const issue of result.issues) if (issue.path) {
|
|
820
|
+
const path = [];
|
|
821
|
+
for (const pathItem of issue.path) {
|
|
822
|
+
const key = pathItem.key;
|
|
823
|
+
const keyType = typeof key;
|
|
824
|
+
const itemType = pathItem.type;
|
|
825
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
826
|
+
path.push(key);
|
|
619
827
|
}
|
|
620
|
-
|
|
828
|
+
const name = JSON.stringify(path);
|
|
829
|
+
const fieldErrors = nestedErrors[name];
|
|
830
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
831
|
+
else nestedErrors[name] = [issue.message];
|
|
832
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
833
|
+
else rootErrors = [issue.message];
|
|
834
|
+
}
|
|
835
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
836
|
+
batch(() => {
|
|
837
|
+
untrack(() => {
|
|
838
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
839
|
+
if (internalFieldStore.path.length === 0) internalFieldStore.errors.value = rootErrors ?? null;
|
|
840
|
+
else {
|
|
841
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
842
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
843
|
+
if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
internalFormStore.validators--;
|
|
848
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
621
849
|
});
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
850
|
+
return result;
|
|
851
|
+
} catch (error) {
|
|
852
|
+
batch(() => {
|
|
853
|
+
internalFormStore.validators--;
|
|
854
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
855
|
+
});
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
626
858
|
}
|
|
627
859
|
|
|
628
860
|
//#endregion
|
|
@@ -648,4 +880,4 @@ function validateIfRequired(internalFormStore, internalFieldStore, validationMod
|
|
|
648
880
|
const INTERNAL = "~internal";
|
|
649
881
|
|
|
650
882
|
//#endregion
|
|
651
|
-
export { INTERNAL, batch, copyItemState, createFormStore, createId, createSignal, framework, getDirtyFieldInput, getElementInput, getFieldBool, getFieldInput, getFieldStore, initializeFieldStore, resetItemState, setFieldBool, setFieldInput, setInitialFieldInput, swapItemState, untrack, validateFormInput, validateIfRequired, walkFieldStore };
|
|
883
|
+
export { INTERNAL, batch, copyItemState, createFormStore, createId, createSignal, decodeFormData, focusFieldElement, framework, getDirtyFieldInput, getElementInput, getFieldBool, getFieldInput, getFieldStore, initializeFieldStore, resetItemState, setFieldBool, setFieldInput, setInitialFieldInput, swapItemState, untrack, validateFormInput, validateIfRequired, walkFieldStore };
|