@formisch/svelte 0.7.6 → 0.9.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 +8 -1
- package/dist/components/Field/Field.svelte +7 -3
- package/dist/components/Field/Field.svelte.d.ts +7 -7
- package/dist/components/FieldArray/FieldArray.svelte +3 -3
- package/dist/components/FieldArray/FieldArray.svelte.d.ts +7 -7
- package/dist/components/Form/Form.svelte +3 -3
- package/dist/components/Form/Form.svelte.d.ts +7 -7
- package/dist/core/index.svelte.d.ts +122 -13
- package/dist/core/index.svelte.js +375 -97
- package/dist/index.d.ts +1 -1
- package/dist/methods/index.svelte.d.ts +145 -33
- package/dist/methods/index.svelte.js +66 -4
- package/dist/runes/createForm/createForm.svelte.d.ts +2 -2
- package/dist/runes/useField/useField.svelte.d.ts +3 -3
- package/dist/runes/useField/useField.svelte.js +7 -1
- package/dist/runes/useFieldArray/useFieldArray.svelte.d.ts +3 -3
- package/dist/types/field.d.ts +3 -3
- package/dist/types/form.d.ts +2 -2
- package/package.json +6 -5
|
@@ -179,31 +179,45 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
179
179
|
* form reset functionality.
|
|
180
180
|
*
|
|
181
181
|
* @param internalFieldStore The field store to reset.
|
|
182
|
-
* @param
|
|
182
|
+
* @param input The new input value (can be any type including array or object).
|
|
183
|
+
* @param keepStart Whether to keep `startInput` and `startItems` as the dirty
|
|
184
|
+
* baseline instead of resetting them to the new input. Used when a field store
|
|
185
|
+
* is reused for an in-place edit so its dirty state is detected correctly.
|
|
183
186
|
*/
|
|
184
|
-
function resetItemState(internalFieldStore,
|
|
187
|
+
function resetItemState(internalFieldStore, input, keepStart = false) {
|
|
185
188
|
batch(() => {
|
|
186
|
-
|
|
189
|
+
const elements = [];
|
|
190
|
+
if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
|
|
191
|
+
internalFieldStore.elements = elements;
|
|
187
192
|
internalFieldStore.errors.value = null;
|
|
188
193
|
internalFieldStore.isTouched.value = false;
|
|
189
194
|
internalFieldStore.isDirty.value = false;
|
|
190
195
|
if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
|
|
191
|
-
const objectInput =
|
|
192
|
-
internalFieldStore.startInput.value = objectInput;
|
|
196
|
+
const objectInput = input == null ? input : true;
|
|
197
|
+
if (!keepStart) internalFieldStore.startInput.value = objectInput;
|
|
193
198
|
internalFieldStore.input.value = objectInput;
|
|
194
|
-
if (internalFieldStore.kind === "array") if (
|
|
195
|
-
const
|
|
196
|
-
|
|
199
|
+
if (internalFieldStore.kind === "array") if (input) {
|
|
200
|
+
const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
|
|
201
|
+
const newItems = Array.from({ length }, createId);
|
|
202
|
+
if (!keepStart) internalFieldStore.startItems.value = newItems;
|
|
197
203
|
internalFieldStore.items.value = newItems;
|
|
198
|
-
|
|
204
|
+
let path;
|
|
205
|
+
for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
|
|
206
|
+
else {
|
|
207
|
+
path ??= JSON.parse(internalFieldStore.name);
|
|
208
|
+
internalFieldStore.children[index] = {};
|
|
209
|
+
path.push(index);
|
|
210
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
|
|
211
|
+
path.pop();
|
|
212
|
+
}
|
|
199
213
|
} else {
|
|
200
|
-
internalFieldStore.startItems.value = [];
|
|
214
|
+
if (!keepStart) internalFieldStore.startItems.value = [];
|
|
201
215
|
internalFieldStore.items.value = [];
|
|
202
216
|
}
|
|
203
|
-
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key],
|
|
217
|
+
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
|
|
204
218
|
} else {
|
|
205
|
-
internalFieldStore.startInput.value =
|
|
206
|
-
internalFieldStore.input.value =
|
|
219
|
+
if (!keepStart) internalFieldStore.startInput.value = input;
|
|
220
|
+
internalFieldStore.input.value = input;
|
|
207
221
|
}
|
|
208
222
|
});
|
|
209
223
|
}
|
|
@@ -273,6 +287,94 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
273
287
|
});
|
|
274
288
|
}
|
|
275
289
|
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/field/focusFieldElement/focusFieldElement.ts
|
|
292
|
+
/**
|
|
293
|
+
* Focuses the first focusable element of a field store. The elements are tried
|
|
294
|
+
* in order and the first one that actually receives focus wins, so detached,
|
|
295
|
+
* disabled or hidden elements are skipped. The browser decides focusability,
|
|
296
|
+
* which is read back via the element's root `activeElement` so elements in a
|
|
297
|
+
* shadow root or another document are handled correctly.
|
|
298
|
+
*
|
|
299
|
+
* Hint: A `display: none` or `hidden` element is correctly skipped in real
|
|
300
|
+
* browsers, but jsdom has no layout and focuses it anyway, so that case cannot
|
|
301
|
+
* be covered by unit tests.
|
|
302
|
+
*
|
|
303
|
+
* @param internalFieldStore The field store to focus.
|
|
304
|
+
*
|
|
305
|
+
* @returns Whether an element was focused.
|
|
306
|
+
*/
|
|
307
|
+
function focusFieldElement(internalFieldStore) {
|
|
308
|
+
for (const element of internalFieldStore.elements) {
|
|
309
|
+
element.focus();
|
|
310
|
+
if (element.getRootNode().activeElement === element) return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/field/getFieldBool/getFieldBool.ts
|
|
317
|
+
/**
|
|
318
|
+
* Returns whether the specified boolean property is true for the field store
|
|
319
|
+
* or any of its nested children. Recursively checks arrays and objects.
|
|
320
|
+
*
|
|
321
|
+
* @param internalFieldStore The field store to check.
|
|
322
|
+
* @param type The boolean property type to check.
|
|
323
|
+
*
|
|
324
|
+
* @returns Whether the property is true.
|
|
325
|
+
*/
|
|
326
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
327
|
+
function getFieldBool(internalFieldStore, type) {
|
|
328
|
+
if (internalFieldStore[type].value) return true;
|
|
329
|
+
if (internalFieldStore.kind === "array") {
|
|
330
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
if (internalFieldStore.kind == "object") {
|
|
334
|
+
for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/field/getDirtyFieldInput/getDirtyFieldInput.ts
|
|
342
|
+
/**
|
|
343
|
+
* Returns only the dirty input of the field store. Arrays are treated as
|
|
344
|
+
* atomic and returned in full if any item is dirty, while object keys without
|
|
345
|
+
* a dirty descendant are omitted. Returns `undefined` if no descendant is
|
|
346
|
+
* dirty.
|
|
347
|
+
*
|
|
348
|
+
* @param internalFieldStore The field store to get dirty input from.
|
|
349
|
+
* @param dirtyOnly Whether to only include dirty fields. Defaults to `true`.
|
|
350
|
+
*
|
|
351
|
+
* @returns The dirty input, or `undefined` if no descendant is dirty.
|
|
352
|
+
*/
|
|
353
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
354
|
+
function getDirtyFieldInput(internalFieldStore, dirtyOnly = true) {
|
|
355
|
+
if (dirtyOnly && !/* @__PURE__ */ getFieldBool(internalFieldStore, "isDirty")) return;
|
|
356
|
+
if (internalFieldStore.kind === "array") {
|
|
357
|
+
if (internalFieldStore.input.value) {
|
|
358
|
+
const value = [];
|
|
359
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) value[index] = /* @__PURE__ */ getDirtyFieldInput(internalFieldStore.children[index], false);
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
return internalFieldStore.input.value;
|
|
363
|
+
}
|
|
364
|
+
if (internalFieldStore.kind === "object") {
|
|
365
|
+
if (internalFieldStore.input.value) {
|
|
366
|
+
const value = {};
|
|
367
|
+
for (const key in internalFieldStore.children) {
|
|
368
|
+
const child = internalFieldStore.children[key];
|
|
369
|
+
if (!dirtyOnly || /* @__PURE__ */ getFieldBool(child, "isDirty")) value[key] = /* @__PURE__ */ getDirtyFieldInput(child, dirtyOnly);
|
|
370
|
+
}
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
return internalFieldStore.input.value;
|
|
374
|
+
}
|
|
375
|
+
return internalFieldStore.input.value;
|
|
376
|
+
}
|
|
377
|
+
|
|
276
378
|
//#endregion
|
|
277
379
|
//#region src/field/getFieldInput/getFieldInput.ts
|
|
278
380
|
/**
|
|
@@ -335,31 +437,6 @@ function getElementInput(element, internalFieldStore) {
|
|
|
335
437
|
return element.value;
|
|
336
438
|
}
|
|
337
439
|
|
|
338
|
-
//#endregion
|
|
339
|
-
//#region src/field/getFieldBool/getFieldBool.ts
|
|
340
|
-
/**
|
|
341
|
-
* Returns whether the specified boolean property is true for the field store
|
|
342
|
-
* or any of its nested children. Recursively checks arrays and objects.
|
|
343
|
-
*
|
|
344
|
-
* @param internalFieldStore The field store to check.
|
|
345
|
-
* @param type The boolean property type to check.
|
|
346
|
-
*
|
|
347
|
-
* @returns Whether the property is true.
|
|
348
|
-
*/
|
|
349
|
-
/* @__NO_SIDE_EFFECTS__ */
|
|
350
|
-
function getFieldBool(internalFieldStore, type) {
|
|
351
|
-
if (internalFieldStore[type].value) return true;
|
|
352
|
-
if (internalFieldStore.kind === "array") {
|
|
353
|
-
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
if (internalFieldStore.kind == "object") {
|
|
357
|
-
for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
440
|
//#endregion
|
|
364
441
|
//#region src/field/getFieldStore/getFieldStore.ts
|
|
365
442
|
/**
|
|
@@ -390,11 +467,9 @@ function getFieldStore(internalFormStore, path) {
|
|
|
390
467
|
*/
|
|
391
468
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
392
469
|
batch(() => {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
} else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
397
|
-
else internalFieldStore[type].value = bool;
|
|
470
|
+
internalFieldStore[type].value = bool;
|
|
471
|
+
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
|
|
472
|
+
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
398
473
|
});
|
|
399
474
|
}
|
|
400
475
|
|
|
@@ -412,20 +487,20 @@ function setNestedInput(internalFieldStore, input) {
|
|
|
412
487
|
if (internalFieldStore.kind === "array") {
|
|
413
488
|
const arrayInput = input ?? [];
|
|
414
489
|
const items = internalFieldStore.items.value;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
490
|
+
const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
|
|
491
|
+
if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
|
|
492
|
+
else if (length > items.length) {
|
|
493
|
+
const path = JSON.parse(internalFieldStore.name);
|
|
494
|
+
for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
|
|
495
|
+
else {
|
|
496
|
+
internalFieldStore.children[index] = {};
|
|
497
|
+
path.push(index);
|
|
498
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
|
|
499
|
+
path.pop();
|
|
425
500
|
}
|
|
426
|
-
internalFieldStore.items.value = [...items, ...
|
|
501
|
+
internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
|
|
427
502
|
}
|
|
428
|
-
for (let index = 0; index <
|
|
503
|
+
for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
429
504
|
internalFieldStore.input.value = input == null ? input : true;
|
|
430
505
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
|
|
431
506
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -472,21 +547,22 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
472
547
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
473
548
|
batch(() => {
|
|
474
549
|
if (internalFieldStore.kind === "array") {
|
|
475
|
-
internalFieldStore.
|
|
550
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
476
551
|
const initialArrayInput = initialInput ?? [];
|
|
477
|
-
|
|
552
|
+
const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
|
|
553
|
+
if (length > internalFieldStore.children.length) {
|
|
478
554
|
const path = JSON.parse(internalFieldStore.name);
|
|
479
|
-
for (let index = internalFieldStore.children.length; index <
|
|
555
|
+
for (let index = internalFieldStore.children.length; index < length; index++) {
|
|
480
556
|
internalFieldStore.children[index] = {};
|
|
481
557
|
path.push(index);
|
|
482
558
|
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
|
|
483
559
|
path.pop();
|
|
484
560
|
}
|
|
485
561
|
}
|
|
486
|
-
internalFieldStore.initialItems.value =
|
|
562
|
+
internalFieldStore.initialItems.value = Array.from({ length }, createId);
|
|
487
563
|
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
|
|
488
564
|
} else if (internalFieldStore.kind === "object") {
|
|
489
|
-
internalFieldStore.
|
|
565
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
490
566
|
for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
491
567
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
492
568
|
});
|
|
@@ -532,6 +608,203 @@ function createFormStore(config, parse) {
|
|
|
532
608
|
return store;
|
|
533
609
|
}
|
|
534
610
|
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/form/decodeFormData/decodeFormData.ts
|
|
613
|
+
const NUMBER_REGEX = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/u;
|
|
614
|
+
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;
|
|
615
|
+
const MAX_ARRAY_LENGTH = 5e3;
|
|
616
|
+
/**
|
|
617
|
+
* Unwraps wrapper and lazy schemas until a concrete schema is reached.
|
|
618
|
+
*
|
|
619
|
+
* @param schema The schema to unwrap.
|
|
620
|
+
*
|
|
621
|
+
* @returns The unwrapped schema.
|
|
622
|
+
*/
|
|
623
|
+
function unwrapSchema(schema) {
|
|
624
|
+
switch (schema.type) {
|
|
625
|
+
case "exact_optional":
|
|
626
|
+
case "nullable":
|
|
627
|
+
case "nullish":
|
|
628
|
+
case "optional":
|
|
629
|
+
case "undefinedable":
|
|
630
|
+
case "non_nullable":
|
|
631
|
+
case "non_nullish":
|
|
632
|
+
case "non_optional": return unwrapSchema(schema.wrapped);
|
|
633
|
+
case "lazy": return unwrapSchema(schema.getter(void 0));
|
|
634
|
+
default: return schema;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Returns the child schema for the given key by traversing objects, arrays,
|
|
639
|
+
* tuples and schema options. Returns `undefined` if no child schema is found.
|
|
640
|
+
*
|
|
641
|
+
* @param schema The parent schema.
|
|
642
|
+
* @param key The path key.
|
|
643
|
+
*
|
|
644
|
+
* @returns The child schema or `undefined`.
|
|
645
|
+
*/
|
|
646
|
+
function getChildSchema(schema, key) {
|
|
647
|
+
if (schema) {
|
|
648
|
+
const unwrapped = unwrapSchema(schema);
|
|
649
|
+
if (unwrapped.type === "object" || unwrapped.type === "loose_object" || unwrapped.type === "strict_object") return unwrapped.entries[key];
|
|
650
|
+
if (unwrapped.type === "array") return unwrapped.item;
|
|
651
|
+
if (unwrapped.type === "tuple" || unwrapped.type === "loose_tuple" || unwrapped.type === "strict_tuple") return unwrapped.items[key];
|
|
652
|
+
if (unwrapped.type === "union" || unwrapped.type === "intersect" || unwrapped.type === "variant") for (const option of unwrapped.options) {
|
|
653
|
+
const childSchema = getChildSchema(option, key);
|
|
654
|
+
if (childSchema !== void 0) return childSchema;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Decodes a stringified date based on its format. Empty strings become `null`.
|
|
660
|
+
*
|
|
661
|
+
* @param value The stringified value.
|
|
662
|
+
*
|
|
663
|
+
* @returns The decoded date.
|
|
664
|
+
*/
|
|
665
|
+
function decodeDate(value) {
|
|
666
|
+
if (!value || value === "null") return null;
|
|
667
|
+
if (value === "undefined") return;
|
|
668
|
+
if (ISO_DATE_TIME_REGEX.test(value)) return /* @__PURE__ */ new Date(`${value}Z`);
|
|
669
|
+
return new Date(value);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Decodes a stringified boolean. Empty strings become `null`.
|
|
673
|
+
*
|
|
674
|
+
* @param value The stringified value.
|
|
675
|
+
*
|
|
676
|
+
* @returns The decoded boolean.
|
|
677
|
+
*/
|
|
678
|
+
function decodeBoolean(value) {
|
|
679
|
+
if (!value || value === "null") return null;
|
|
680
|
+
if (value === "undefined") return;
|
|
681
|
+
return !(value === "false" || value === "off" || value === "0");
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Decodes a stringified number. Empty strings become `null` and non-numeric
|
|
685
|
+
* values become `NaN`.
|
|
686
|
+
*
|
|
687
|
+
* @param value The stringified value.
|
|
688
|
+
*
|
|
689
|
+
* @returns The decoded number.
|
|
690
|
+
*/
|
|
691
|
+
function decodeNumber(value) {
|
|
692
|
+
if (!value || value === "null") return null;
|
|
693
|
+
if (value === "undefined") return;
|
|
694
|
+
if (NUMBER_REGEX.test(value)) return Number(value);
|
|
695
|
+
return NaN;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Decodes a stringified bigint. Empty strings become `null` and invalid values
|
|
699
|
+
* are returned unchanged.
|
|
700
|
+
*
|
|
701
|
+
* @param value The stringified value.
|
|
702
|
+
*
|
|
703
|
+
* @returns The decoded bigint.
|
|
704
|
+
*/
|
|
705
|
+
function decodeBigint(value) {
|
|
706
|
+
if (!value || value === "null") return null;
|
|
707
|
+
if (value === "undefined") return;
|
|
708
|
+
try {
|
|
709
|
+
return BigInt(value);
|
|
710
|
+
} catch {
|
|
711
|
+
return value;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Decodes a single form data value based on the concrete schema type. Files
|
|
716
|
+
* and unknown types are returned unchanged.
|
|
717
|
+
*
|
|
718
|
+
* @param value The form data value.
|
|
719
|
+
* @param schema The schema of the value.
|
|
720
|
+
*
|
|
721
|
+
* @returns The decoded value.
|
|
722
|
+
*/
|
|
723
|
+
function decodeValue(value, schema) {
|
|
724
|
+
if (typeof value !== "string" || !schema) return value;
|
|
725
|
+
switch (unwrapSchema(schema).type) {
|
|
726
|
+
case "number": return decodeNumber(value);
|
|
727
|
+
case "boolean": return decodeBoolean(value);
|
|
728
|
+
case "date": return decodeDate(value);
|
|
729
|
+
case "bigint": return decodeBigint(value);
|
|
730
|
+
default: return value;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Fills in default values that are lost during the form data transfer. Booleans
|
|
735
|
+
* of unchecked checkboxes become `false` and absent arrays become empty. Only
|
|
736
|
+
* containers that are present in the decoded data are completed.
|
|
737
|
+
*
|
|
738
|
+
* @param schema The schema of the value.
|
|
739
|
+
* @param parent The parent object or array holding the value.
|
|
740
|
+
* @param key The key of the value within its parent.
|
|
741
|
+
*/
|
|
742
|
+
function fillDefaults(schema, parent, key) {
|
|
743
|
+
const unwrappedSchema = unwrapSchema(schema);
|
|
744
|
+
if (unwrappedSchema.type === "boolean") {
|
|
745
|
+
if (parent[key] === void 0) parent[key] = false;
|
|
746
|
+
} 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);
|
|
747
|
+
else parent[key] = [];
|
|
748
|
+
else if (unwrappedSchema.type === "tuple" || unwrappedSchema.type === "loose_tuple" || unwrappedSchema.type === "strict_tuple") {
|
|
749
|
+
if (Array.isArray(parent[key])) for (let index = 0; index < unwrappedSchema.items.length; index++) fillDefaults(unwrappedSchema.items[index], parent[key], index);
|
|
750
|
+
} else if (unwrappedSchema.type === "object" || unwrappedSchema.type === "loose_object" || unwrappedSchema.type === "strict_object") {
|
|
751
|
+
if (parent[key] && typeof parent[key] === "object") for (const entryKey in unwrappedSchema.entries) fillDefaults(unwrappedSchema.entries[entryKey], parent[key], entryKey);
|
|
752
|
+
} else if (unwrappedSchema.type === "union" || unwrappedSchema.type === "intersect" || unwrappedSchema.type === "variant") for (const option of unwrappedSchema.options) fillDefaults(option, parent, key);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Decodes the entries of a form data object into nested form values using the
|
|
756
|
+
* Valibot schema as the source of truth. Information that is lost during the
|
|
757
|
+
* transfer via HTTP, like numbers, booleans, dates and unchecked checkboxes,
|
|
758
|
+
* is restored based on the schema.
|
|
759
|
+
*
|
|
760
|
+
* The keys of the form data are expected to be the stringified field paths that
|
|
761
|
+
* Formisch assigns to its field elements (for example `["todos",0,"label"]`).
|
|
762
|
+
*
|
|
763
|
+
* @param schema The form schema.
|
|
764
|
+
* @param formData The form data object.
|
|
765
|
+
*
|
|
766
|
+
* @returns The decoded form values.
|
|
767
|
+
*/
|
|
768
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
769
|
+
function decodeFormData(schema, formData) {
|
|
770
|
+
const values = {};
|
|
771
|
+
formData.forEach((value, key) => {
|
|
772
|
+
let path = null;
|
|
773
|
+
try {
|
|
774
|
+
path = JSON.parse(key);
|
|
775
|
+
} catch {}
|
|
776
|
+
if (Array.isArray(path) && path.length > 0 && (typeof value === "string" || value.size > 0 || value.name !== "")) {
|
|
777
|
+
let parentValue = values;
|
|
778
|
+
let parentSchema = schema;
|
|
779
|
+
for (let index = 0; index < path.length; index++) {
|
|
780
|
+
const segment = path[index];
|
|
781
|
+
if (typeof segment !== "string" && typeof segment !== "number" || segment === "" || segment === "__proto__" || segment === "prototype" || segment === "constructor") break;
|
|
782
|
+
if (Array.isArray(parentValue)) {
|
|
783
|
+
if (typeof segment === "string") break;
|
|
784
|
+
if (segment >= MAX_ARRAY_LENGTH) throw new Error(`Array exceeds the maximum length of ${MAX_ARRAY_LENGTH}`);
|
|
785
|
+
}
|
|
786
|
+
const childSchema = getChildSchema(parentSchema, segment);
|
|
787
|
+
if (index === path.length - 1) {
|
|
788
|
+
const unwrappedSchema = childSchema && unwrapSchema(childSchema);
|
|
789
|
+
if (unwrappedSchema && unwrappedSchema.type === "array") {
|
|
790
|
+
parentValue[segment] ??= [];
|
|
791
|
+
parentValue[segment].push(decodeValue(value, unwrappedSchema.item));
|
|
792
|
+
} else parentValue[segment] = decodeValue(value, childSchema);
|
|
793
|
+
} else {
|
|
794
|
+
if (parentValue[segment] == null) {
|
|
795
|
+
const schemaType = childSchema && unwrapSchema(childSchema).type;
|
|
796
|
+
parentValue[segment] = schemaType === "array" || schemaType === "tuple" || schemaType === "loose_tuple" || schemaType === "strict_tuple" ? [] : {};
|
|
797
|
+
} else if (typeof parentValue[segment] !== "object") break;
|
|
798
|
+
parentValue = parentValue[segment];
|
|
799
|
+
parentSchema = childSchema;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
fillDefaults(schema, { values }, "values");
|
|
805
|
+
return values;
|
|
806
|
+
}
|
|
807
|
+
|
|
535
808
|
//#endregion
|
|
536
809
|
//#region src/form/validateFormInput/validateFormInput.ts
|
|
537
810
|
/**
|
|
@@ -547,44 +820,49 @@ function createFormStore(config, parse) {
|
|
|
547
820
|
async function validateFormInput(internalFormStore, config) {
|
|
548
821
|
internalFormStore.validators++;
|
|
549
822
|
internalFormStore.isValidating.value = true;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const path
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const name = JSON.stringify(path);
|
|
565
|
-
const fieldErrors = nestedErrors[name];
|
|
566
|
-
if (fieldErrors) fieldErrors.push(issue.message);
|
|
567
|
-
else nestedErrors[name] = [issue.message];
|
|
568
|
-
} else if (rootErrors) rootErrors.push(issue.message);
|
|
569
|
-
else rootErrors = [issue.message];
|
|
570
|
-
}
|
|
571
|
-
let shouldFocus = config?.shouldFocus ?? false;
|
|
572
|
-
batch(() => {
|
|
573
|
-
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
574
|
-
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
575
|
-
else {
|
|
576
|
-
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
577
|
-
internalFieldStore.errors.value = fieldErrors;
|
|
578
|
-
if (shouldFocus && fieldErrors) {
|
|
579
|
-
internalFieldStore.elements[0]?.focus();
|
|
580
|
-
shouldFocus = false;
|
|
823
|
+
try {
|
|
824
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
825
|
+
let rootErrors;
|
|
826
|
+
let nestedErrors;
|
|
827
|
+
if (result.issues) {
|
|
828
|
+
nestedErrors = {};
|
|
829
|
+
for (const issue of result.issues) if (issue.path) {
|
|
830
|
+
const path = [];
|
|
831
|
+
for (const pathItem of issue.path) {
|
|
832
|
+
const key = pathItem.key;
|
|
833
|
+
const keyType = typeof key;
|
|
834
|
+
const itemType = pathItem.type;
|
|
835
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
836
|
+
path.push(key);
|
|
581
837
|
}
|
|
582
|
-
|
|
838
|
+
const name = JSON.stringify(path);
|
|
839
|
+
const fieldErrors = nestedErrors[name];
|
|
840
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
841
|
+
else nestedErrors[name] = [issue.message];
|
|
842
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
843
|
+
else rootErrors = [issue.message];
|
|
844
|
+
}
|
|
845
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
846
|
+
batch(() => {
|
|
847
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
848
|
+
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
849
|
+
else {
|
|
850
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
851
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
852
|
+
if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
internalFormStore.validators--;
|
|
856
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
583
857
|
});
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
858
|
+
return result;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
batch(() => {
|
|
861
|
+
internalFormStore.validators--;
|
|
862
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
863
|
+
});
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
588
866
|
}
|
|
589
867
|
|
|
590
868
|
//#endregion
|
|
@@ -610,4 +888,4 @@ function validateIfRequired(internalFormStore, internalFieldStore, validationMod
|
|
|
610
888
|
const INTERNAL = "~internal";
|
|
611
889
|
|
|
612
890
|
//#endregion
|
|
613
|
-
export { INTERNAL, batch, copyItemState, createFormStore, createId, createSignal, framework, getElementInput, getFieldBool, getFieldInput, getFieldStore, initializeFieldStore, resetItemState, setFieldBool, setFieldInput, setInitialFieldInput, swapItemState, untrack, validateFormInput, validateIfRequired, walkFieldStore };
|
|
891
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { DeepPartial, FieldElement, FormConfig, PartialValues, PathValue, RequiredPath, Schema, SubmitEventHandler, SubmitHandler, ValidArrayPath, ValidationMode, ValidPath, } from './core/index.svelte';
|
|
1
|
+
export type { DeepPartial, FieldElement, FormConfig, PartialValues, PathValue, RequiredPath, FormSchema, Schema, SubmitEventHandler, SubmitHandler, ValidArrayPath, ValidationMode, ValidPath, } from './core/index.svelte';
|
|
2
2
|
export * from './methods/index.svelte';
|
|
3
3
|
export * from './components/index';
|
|
4
4
|
export * from './runes/index';
|