formstand 0.25.0 → 0.26.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.
- package/CHANGELOG.md +205 -0
- package/dist/chunk-XJ6W5CKB.js +22 -0
- package/dist/chunk-XJ6W5CKB.js.map +1 -0
- package/dist/{createForm-CMVALnc3.d.cts → createForm-BXDd9Tg4.d.cts} +18 -7
- package/dist/{createForm-CMVALnc3.d.ts → createForm-BXDd9Tg4.d.ts} +18 -7
- package/dist/devtools/index.cjs +40 -56
- package/dist/devtools/index.cjs.map +1 -1
- package/dist/devtools/index.js +41 -23
- package/dist/devtools/index.js.map +1 -1
- package/dist/index.cjs +535 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -11
- package/dist/index.d.ts +30 -11
- package/dist/index.js +682 -158
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +7 -11
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +4 -4
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.js +7 -11
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EQIS5IVH.js +0 -172
- package/dist/chunk-EQIS5IVH.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -212,10 +212,12 @@ var hasOwnSlot = (obj, path) => parsePath(path).reduce(
|
|
|
212
212
|
).ok;
|
|
213
213
|
var writeIntoArray = (arr, index, rest, value, kindAt, prefix) => {
|
|
214
214
|
const length = Math.max(arr.length, index + 1);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
);
|
|
215
|
+
const child = writeSegments(arr[index], rest, value, kindAt, [
|
|
216
|
+
...prefix,
|
|
217
|
+
index
|
|
218
|
+
]);
|
|
219
|
+
if (index < arr.length && child === arr[index]) return arr;
|
|
220
|
+
return Array.from({ length }, (_, i) => i === index ? child : arr[i]);
|
|
219
221
|
};
|
|
220
222
|
var refusesHugeIndex = (index) => {
|
|
221
223
|
if (index > MAX_ARRAY_INDEX) {
|
|
@@ -247,10 +249,12 @@ var writeSegments = (obj, segments, value, kindAt, prefix) => {
|
|
|
247
249
|
return obj;
|
|
248
250
|
}
|
|
249
251
|
const key2 = String(head);
|
|
250
|
-
|
|
251
|
-
...
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
const child = writeSegments(obj[key2], rest, value, kindAt, [
|
|
253
|
+
...prefix,
|
|
254
|
+
key2
|
|
255
|
+
]);
|
|
256
|
+
if (child === obj[key2] && Object.hasOwn(obj, key2)) return obj;
|
|
257
|
+
return { ...obj, [key2]: child };
|
|
254
258
|
}
|
|
255
259
|
const kind = kindAt?.(prefix.map(String).join(".")) ?? (typeof head === "number" ? "array" : "record");
|
|
256
260
|
if (kind === "array") {
|
|
@@ -298,6 +302,12 @@ var validateAsync = async (schema, values) => {
|
|
|
298
302
|
return result.success ? { kind: "valid", data: result.data } : { kind: "invalid", errors: flattenIssues(result.error.issues) };
|
|
299
303
|
};
|
|
300
304
|
var isAsyncRequiredError = (e) => e instanceof zod.z.core.$ZodAsyncError || e instanceof Error && /Encountered Promise during synchronous parse/i.test(e.message);
|
|
305
|
+
var reportBackgroundFailure = (error) => {
|
|
306
|
+
console.error(
|
|
307
|
+
"[formstand] a background validation pass threw (an async refinement rejected). Its in-flight flags were released. The error:",
|
|
308
|
+
error
|
|
309
|
+
);
|
|
310
|
+
};
|
|
301
311
|
var isPathOrChild = (key, path) => key === path || key.startsWith(`${path}.`);
|
|
302
312
|
var prefixErrorKeys = (errors, path) => path === "" ? errors : Object.fromEntries(
|
|
303
313
|
Object.entries(errors).map(([k, v]) => [
|
|
@@ -323,7 +333,11 @@ var unwrapForWalk = (s) => {
|
|
|
323
333
|
if (s instanceof zod.z.ZodDefault || s instanceof zod.z.ZodPrefault || s instanceof zod.z.ZodCatch || s instanceof zod.z.ZodReadonly || s instanceof zod.z.ZodNonOptional) {
|
|
324
334
|
return s.def.innerType;
|
|
325
335
|
}
|
|
326
|
-
if (s instanceof zod.z.ZodPipe)
|
|
336
|
+
if (s instanceof zod.z.ZodPipe) {
|
|
337
|
+
const input = s.def.in;
|
|
338
|
+
return input instanceof zod.z.ZodTransform ? s.def.out : input;
|
|
339
|
+
}
|
|
340
|
+
if (s instanceof zod.z.ZodLazy) return s.def.getter();
|
|
327
341
|
return null;
|
|
328
342
|
};
|
|
329
343
|
var isLeafSchema = (s) => s instanceof zod.z.ZodString || s instanceof zod.z.ZodNumber || s instanceof zod.z.ZodBoolean || s instanceof zod.z.ZodBigInt || s instanceof zod.z.ZodDate || s instanceof zod.z.ZodEnum || s instanceof zod.z.ZodLiteral || s instanceof zod.z.ZodNull || s instanceof zod.z.ZodUndefined || s instanceof zod.z.ZodVoid || s instanceof zod.z.ZodNaN || s instanceof zod.z.ZodSymbol;
|
|
@@ -349,14 +363,71 @@ var schemaHasPath = (schema, path) => {
|
|
|
349
363
|
};
|
|
350
364
|
return walk(schema, parsePath(path), SCHEMA_WALK_BUDGET);
|
|
351
365
|
};
|
|
366
|
+
var schemaAllowsKindChangeAt = (schema, path) => {
|
|
367
|
+
const flexible = (s) => s instanceof zod.z.ZodUnion || s instanceof zod.z.ZodDiscriminatedUnion || s instanceof zod.z.ZodPipe || s instanceof zod.z.ZodAny || s instanceof zod.z.ZodUnknown || s instanceof zod.z.ZodLazy || s instanceof zod.z.ZodCatch || s.def.coerce === true;
|
|
368
|
+
const strip = (s) => {
|
|
369
|
+
if (s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodNullable) {
|
|
370
|
+
return strip(s.unwrap());
|
|
371
|
+
}
|
|
372
|
+
if (s instanceof zod.z.ZodDefault || s instanceof zod.z.ZodPrefault || s instanceof zod.z.ZodReadonly || s instanceof zod.z.ZodNonOptional) {
|
|
373
|
+
return strip(s.def.innerType);
|
|
374
|
+
}
|
|
375
|
+
return s;
|
|
376
|
+
};
|
|
377
|
+
const walk = (s, segments, budget) => {
|
|
378
|
+
if (budget <= 0) return true;
|
|
379
|
+
const node = strip(s);
|
|
380
|
+
if (flexible(node)) return true;
|
|
381
|
+
const [head, ...rest] = segments;
|
|
382
|
+
if (head === void 0) return false;
|
|
383
|
+
if (node instanceof zod.z.ZodObject) {
|
|
384
|
+
const shape = node.shape;
|
|
385
|
+
const child = Object.hasOwn(shape, String(head)) ? shape[String(head)] : void 0;
|
|
386
|
+
return child === void 0 ? true : walk(child, rest, budget - 1);
|
|
387
|
+
}
|
|
388
|
+
if (node instanceof zod.z.ZodArray) {
|
|
389
|
+
return typeof head === "number" ? walk(node.element, rest, budget - 1) : true;
|
|
390
|
+
}
|
|
391
|
+
if (node instanceof zod.z.ZodTuple && typeof head === "number") {
|
|
392
|
+
const item = node.def.items[head];
|
|
393
|
+
return item === void 0 ? true : walk(item, rest, budget - 1);
|
|
394
|
+
}
|
|
395
|
+
if (node instanceof zod.z.ZodRecord) {
|
|
396
|
+
return walk(node.valueType, rest, budget - 1);
|
|
397
|
+
}
|
|
398
|
+
return true;
|
|
399
|
+
};
|
|
400
|
+
return walk(schema, parsePath(path), SCHEMA_WALK_BUDGET);
|
|
401
|
+
};
|
|
352
402
|
var hasChecks = (s) => {
|
|
353
403
|
const checks = s.def.checks;
|
|
354
404
|
return checks !== void 0 && checks.length > 0;
|
|
355
405
|
};
|
|
356
|
-
var
|
|
357
|
-
const
|
|
406
|
+
var optionClaims = (option, discriminator, tag) => {
|
|
407
|
+
const strip = (s) => {
|
|
358
408
|
const inner = unwrapForWalk(s);
|
|
359
|
-
|
|
409
|
+
return inner === null ? s : strip(inner);
|
|
410
|
+
};
|
|
411
|
+
const node = strip(option);
|
|
412
|
+
if (!(node instanceof zod.z.ZodObject)) return false;
|
|
413
|
+
const shape = node.shape;
|
|
414
|
+
const at = Object.hasOwn(shape, discriminator) ? shape[discriminator] : void 0;
|
|
415
|
+
if (at === void 0) return false;
|
|
416
|
+
const leaf = strip(at);
|
|
417
|
+
if (leaf instanceof zod.z.ZodLiteral) {
|
|
418
|
+
return leaf.def.values.includes(tag);
|
|
419
|
+
}
|
|
420
|
+
if (leaf instanceof zod.z.ZodEnum) {
|
|
421
|
+
return Object.values(leaf.def.entries).includes(
|
|
422
|
+
tag
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
};
|
|
427
|
+
var fieldSchemaForIntrospection = (schema, path, values) => {
|
|
428
|
+
const childOf = (s, segment, holder) => {
|
|
429
|
+
const inner = unwrapForWalk(s);
|
|
430
|
+
if (inner !== null) return childOf(inner, segment, holder);
|
|
360
431
|
if (s instanceof zod.z.ZodObject && typeof segment === "string") {
|
|
361
432
|
const shape = s.shape;
|
|
362
433
|
return Object.hasOwn(shape, segment) ? shape[segment] ?? null : null;
|
|
@@ -370,19 +441,45 @@ var fieldSchemaForIntrospection = (schema, path) => {
|
|
|
370
441
|
if (s instanceof zod.z.ZodRecord) {
|
|
371
442
|
return s.valueType;
|
|
372
443
|
}
|
|
444
|
+
if (s instanceof zod.z.ZodIntersection) {
|
|
445
|
+
return childOf(s.def.left, segment, holder) ?? childOf(s.def.right, segment, holder);
|
|
446
|
+
}
|
|
373
447
|
if (s instanceof zod.z.ZodDiscriminatedUnion || s instanceof zod.z.ZodUnion) {
|
|
374
448
|
const options = s.def.options;
|
|
375
|
-
|
|
376
|
-
|
|
449
|
+
const discriminator = s instanceof zod.z.ZodDiscriminatedUnion ? s.def.discriminator : null;
|
|
450
|
+
const tag = discriminator !== null && typeof holder === "object" && holder !== null ? holder[discriminator] : void 0;
|
|
451
|
+
const live = discriminator === null || tag === void 0 ? void 0 : options.find((option) => optionClaims(option, discriminator, tag));
|
|
452
|
+
const candidates = live === void 0 ? options : [live];
|
|
453
|
+
return candidates.reduce(
|
|
454
|
+
(found, option) => found ?? childOf(option, segment, holder),
|
|
377
455
|
null
|
|
378
456
|
);
|
|
379
457
|
}
|
|
380
458
|
return null;
|
|
381
459
|
};
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
460
|
+
const segments = parsePath(path);
|
|
461
|
+
return segments.reduce(
|
|
462
|
+
(acc, segment) => acc.s === null ? acc : {
|
|
463
|
+
s: childOf(
|
|
464
|
+
acc.s,
|
|
465
|
+
segment,
|
|
466
|
+
values === void 0 ? void 0 : getAtPath(values, acc.prefix)
|
|
467
|
+
),
|
|
468
|
+
prefix: acc.prefix === "" ? String(segment) : `${acc.prefix}.${String(segment)}`
|
|
469
|
+
},
|
|
470
|
+
{ s: schema, prefix: "" }
|
|
471
|
+
).s;
|
|
472
|
+
};
|
|
473
|
+
var schemaAcceptsUndefined = (schema) => schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodDefault || schema instanceof zod.z.ZodPrefault || schema instanceof zod.z.ZodNullable && schemaAcceptsUndefined(schema.unwrap());
|
|
474
|
+
var isObjectSchemaAt = (schema, path) => {
|
|
475
|
+
const sub = fieldSchemaForIntrospection(schema, path);
|
|
476
|
+
if (sub === null) return true;
|
|
477
|
+
const strip = (s) => {
|
|
478
|
+
const inner = unwrapForWalk(s);
|
|
479
|
+
return inner === null ? s : strip(inner);
|
|
480
|
+
};
|
|
481
|
+
const node = strip(sub);
|
|
482
|
+
return !(node instanceof zod.z.ZodUnion || node instanceof zod.z.ZodDiscriminatedUnion || node instanceof zod.z.ZodRecord || node instanceof zod.z.ZodMap || node instanceof zod.z.ZodSet || node instanceof zod.z.ZodArray || node instanceof zod.z.ZodTuple || isLeafSchema(node));
|
|
386
483
|
};
|
|
387
484
|
var containerKindAt = (schema) => (prefix) => {
|
|
388
485
|
const unwrapAll = (s) => {
|
|
@@ -433,7 +530,7 @@ var fieldSchemaAtPath = (schema, path) => parsePath(path).reduce((current, segme
|
|
|
433
530
|
}, schema);
|
|
434
531
|
|
|
435
532
|
// src/core/createForm.ts
|
|
436
|
-
var shallowFieldEqual = (a, b) => Object.is(a.value, b.value) && a.error === b.error && a.touched === b.touched && a.dirty === b.dirty && a.isValidating === b.isValidating;
|
|
533
|
+
var shallowFieldEqual = (a, b) => Object.is(a.value, b.value) && Object.is(a.initialValue, b.initialValue) && a.error === b.error && a.touched === b.touched && a.dirty === b.dirty && a.isValidating === b.isValidating;
|
|
437
534
|
var emptyErrors = {};
|
|
438
535
|
var emptyBools = {};
|
|
439
536
|
var identityMapper = (n) => n;
|
|
@@ -491,7 +588,27 @@ var reuseErrorRefs = (next, prev) => {
|
|
|
491
588
|
const sameShape = entries.length === Object.keys(prev).length && entries.every(([k, v]) => entryAt(prev, k) === v);
|
|
492
589
|
return sameShape ? prev : Object.fromEntries(entries);
|
|
493
590
|
};
|
|
494
|
-
var
|
|
591
|
+
var noScopeErrors = { scoped: emptyErrors, full: null };
|
|
592
|
+
var scopeParse = (result, path, viaSubschema) => {
|
|
593
|
+
const errors = result.kind === "invalid" ? result.errors : emptyErrors;
|
|
594
|
+
return viaSubschema ? { scoped: prefixErrorKeys(errors, path), full: null } : {
|
|
595
|
+
scoped: path === "" ? errors : pickScope(errors, [path]),
|
|
596
|
+
full: errors
|
|
597
|
+
};
|
|
598
|
+
};
|
|
599
|
+
var pruneUnreported = (prev, full) => {
|
|
600
|
+
const entries = Object.entries(prev);
|
|
601
|
+
const kept = entries.filter(([k]) => Object.hasOwn(full, k));
|
|
602
|
+
const unchanged = kept.length === entries.length && kept.every(([k, list]) => full[k] === list);
|
|
603
|
+
return unchanged ? prev : Object.fromEntries(kept.map(([k, list]) => [k, full[k] ?? list]));
|
|
604
|
+
};
|
|
605
|
+
var dropEmptyLists = (map) => {
|
|
606
|
+
const entries = Object.entries(map);
|
|
607
|
+
const kept = entries.filter(
|
|
608
|
+
([, list]) => list !== void 0 && list.length > 0
|
|
609
|
+
);
|
|
610
|
+
return kept.length === entries.length ? map : Object.fromEntries(kept);
|
|
611
|
+
};
|
|
495
612
|
var mergeErrorChannels = (schema, server) => {
|
|
496
613
|
const extra = Object.entries(server).filter(
|
|
497
614
|
([k]) => !Object.hasOwn(schema, k)
|
|
@@ -601,17 +718,17 @@ var createForm = (schema, options) => {
|
|
|
601
718
|
const merged = scopes.reduce(
|
|
602
719
|
(acc, { path, scope }) => scope.kind === "skip" ? acc : {
|
|
603
720
|
...acc,
|
|
604
|
-
...
|
|
721
|
+
...scopeParse(
|
|
605
722
|
validateSyncLatched(path, scope.schema, scope.value),
|
|
606
723
|
path,
|
|
607
724
|
true
|
|
608
|
-
)
|
|
725
|
+
).scoped
|
|
609
726
|
},
|
|
610
727
|
{}
|
|
611
728
|
);
|
|
612
729
|
store.setState((state) => ({
|
|
613
730
|
...state,
|
|
614
|
-
...commitScopedErrors(state, merged, paths)
|
|
731
|
+
...commitScopedErrors(state, merged, paths, null)
|
|
615
732
|
}));
|
|
616
733
|
return settledFields(merged);
|
|
617
734
|
}
|
|
@@ -620,7 +737,7 @@ var createForm = (schema, options) => {
|
|
|
620
737
|
const requestedErrors = pickScope(fullErrors, paths);
|
|
621
738
|
store.setState((state) => ({
|
|
622
739
|
...state,
|
|
623
|
-
...commitScopedErrors(state, requestedErrors, paths)
|
|
740
|
+
...commitScopedErrors(state, requestedErrors, paths, fullErrors)
|
|
624
741
|
}));
|
|
625
742
|
return settledFields(requestedErrors);
|
|
626
743
|
} catch (e) {
|
|
@@ -665,7 +782,7 @@ var createForm = (schema, options) => {
|
|
|
665
782
|
const slot = slotAtPath(values, path);
|
|
666
783
|
return slot.exists ? { kind: "parse", schema: sub, value: slot.value, viaSubschema: true } : { kind: "skip" };
|
|
667
784
|
};
|
|
668
|
-
const parseScopeAsync = async (path, scope) => scope.kind === "skip" ?
|
|
785
|
+
const parseScopeAsync = async (path, scope) => scope.kind === "skip" ? noScopeErrors : scopeParse(
|
|
669
786
|
await validateAsync(scope.schema, scope.value),
|
|
670
787
|
path,
|
|
671
788
|
scope.viaSubschema
|
|
@@ -685,14 +802,18 @@ var createForm = (schema, options) => {
|
|
|
685
802
|
};
|
|
686
803
|
const settledFields = (scoped) => Object.keys(scoped).length === 0 ? { kind: "valid" } : { kind: "invalid", errors: scoped };
|
|
687
804
|
const settledFormResult = (result) => settledFields(result.kind === "invalid" ? result.errors : emptyErrors);
|
|
688
|
-
const commitScopedErrors = (state, scoped, paths) => errorChannels(state, {
|
|
805
|
+
const commitScopedErrors = (state, scoped, paths, full) => errorChannels(state, {
|
|
689
806
|
schemaErrors: reuseErrorRefs(
|
|
690
|
-
mergeScopedErrors(
|
|
807
|
+
mergeScopedErrors(
|
|
808
|
+
full === null ? state.schemaErrors : pruneUnreported(state.schemaErrors, full),
|
|
809
|
+
scoped,
|
|
810
|
+
paths
|
|
811
|
+
),
|
|
691
812
|
state.schemaErrors
|
|
692
813
|
),
|
|
693
814
|
serverErrors: omitScope(state.serverErrors, paths)
|
|
694
815
|
});
|
|
695
|
-
const commitFieldErrors = (state,
|
|
816
|
+
const commitFieldErrors = (state, parse, path) => path === "" ? commitFullPass(state, parse.scoped) : commitScopedErrors(state, parse.scoped, [path], parse.full);
|
|
696
817
|
const validateField = (path) => {
|
|
697
818
|
warnMissingSchemaPath("validateField", path);
|
|
698
819
|
const scope = fieldScopeFor(path, store.getState().values);
|
|
@@ -700,7 +821,7 @@ var createForm = (schema, options) => {
|
|
|
700
821
|
return { kind: "pending", promise: validateFieldAsync(path) };
|
|
701
822
|
}
|
|
702
823
|
try {
|
|
703
|
-
const
|
|
824
|
+
const parse = scope.kind === "skip" ? noScopeErrors : scopeParse(
|
|
704
825
|
validateSyncLatched(
|
|
705
826
|
scope.viaSubschema ? path : "",
|
|
706
827
|
scope.schema,
|
|
@@ -711,9 +832,9 @@ var createForm = (schema, options) => {
|
|
|
711
832
|
);
|
|
712
833
|
store.setState((state) => ({
|
|
713
834
|
...state,
|
|
714
|
-
...commitFieldErrors(state,
|
|
835
|
+
...commitFieldErrors(state, parse, path)
|
|
715
836
|
}));
|
|
716
|
-
return fieldResult(scoped);
|
|
837
|
+
return fieldResult(parse.scoped);
|
|
717
838
|
} catch (e) {
|
|
718
839
|
if (isAsyncRequiredError(e)) {
|
|
719
840
|
return { kind: "pending", promise: validateFieldAsync(path) };
|
|
@@ -723,11 +844,15 @@ var createForm = (schema, options) => {
|
|
|
723
844
|
};
|
|
724
845
|
const snapshotField = (state, path) => {
|
|
725
846
|
const value = getAtPath(state.values, path);
|
|
847
|
+
const initialValue = getAtPath(state.initialValues, path);
|
|
848
|
+
const error = entryAt(state.errors, path);
|
|
726
849
|
return {
|
|
727
850
|
value,
|
|
728
|
-
|
|
851
|
+
initialValue,
|
|
852
|
+
error,
|
|
853
|
+
firstError: error?.[0],
|
|
729
854
|
touched: flagAt(state.touched, path),
|
|
730
|
-
dirty: isFieldDirty(value,
|
|
855
|
+
dirty: isFieldDirty(value, initialValue),
|
|
731
856
|
isValidating: flagAt(state.isValidating, path)
|
|
732
857
|
};
|
|
733
858
|
};
|
|
@@ -741,7 +866,14 @@ var createForm = (schema, options) => {
|
|
|
741
866
|
const pass = claimPass(FORM_SEQ_KEY);
|
|
742
867
|
const valuesAtStart = store.getState().values;
|
|
743
868
|
store.setState((state) => ({ ...state, isValidatingForm: true }));
|
|
744
|
-
const result = await validateAsync(schema, valuesAtStart)
|
|
869
|
+
const result = await validateAsync(schema, valuesAtStart).catch(
|
|
870
|
+
(error) => {
|
|
871
|
+
if (sequences.get(FORM_SEQ_KEY) === pass) {
|
|
872
|
+
store.setState((state) => ({ ...state, isValidatingForm: false }));
|
|
873
|
+
}
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
);
|
|
745
877
|
const live = store.getState();
|
|
746
878
|
const stillOwns = sequences.get(FORM_SEQ_KEY) === pass;
|
|
747
879
|
if (!stillOwns || live.values !== valuesAtStart) {
|
|
@@ -781,12 +913,15 @@ var createForm = (schema, options) => {
|
|
|
781
913
|
...Object.fromEntries(paths.map((p) => [p, true]))
|
|
782
914
|
}
|
|
783
915
|
}));
|
|
784
|
-
const
|
|
916
|
+
const parseBatch = async () => allViaSubschema ? {
|
|
785
917
|
kind: "slots",
|
|
786
918
|
byPath: new Map(
|
|
787
919
|
await Promise.all(
|
|
788
920
|
scopes.map(
|
|
789
|
-
async ({ path, scope }) => [
|
|
921
|
+
async ({ path, scope }) => [
|
|
922
|
+
path,
|
|
923
|
+
(await parseScopeAsync(path, scope)).scoped
|
|
924
|
+
]
|
|
790
925
|
)
|
|
791
926
|
)
|
|
792
927
|
)
|
|
@@ -796,6 +931,18 @@ var createForm = (schema, options) => {
|
|
|
796
931
|
(result) => result.kind === "invalid" ? result.errors : emptyErrors
|
|
797
932
|
)
|
|
798
933
|
};
|
|
934
|
+
const parsed = await parseBatch().catch((error) => {
|
|
935
|
+
const stillOwned = new Set(
|
|
936
|
+
paths.filter((p) => sequences.get(p) === passes.get(p))
|
|
937
|
+
);
|
|
938
|
+
if (stillOwned.size > 0) {
|
|
939
|
+
store.setState((state) => ({
|
|
940
|
+
...state,
|
|
941
|
+
isValidating: omitKeys(state.isValidating, stillOwned)
|
|
942
|
+
}));
|
|
943
|
+
}
|
|
944
|
+
throw error;
|
|
945
|
+
});
|
|
799
946
|
const errorsFor = (path) => {
|
|
800
947
|
switch (parsed.kind) {
|
|
801
948
|
case "slots":
|
|
@@ -824,7 +971,15 @@ var createForm = (schema, options) => {
|
|
|
824
971
|
Object.fromEntries(
|
|
825
972
|
fresh.flatMap((path) => Object.entries(errorsFor(path)))
|
|
826
973
|
),
|
|
827
|
-
fresh
|
|
974
|
+
fresh,
|
|
975
|
+
// The prune map describes the values the batch STARTED with.
|
|
976
|
+
// A skip scope (an index into an empty array) still reads as
|
|
977
|
+
// fresh after an unrelated change, so a batch can commit under
|
|
978
|
+
// changed values; pruning with the stale map then dropped
|
|
979
|
+
// errors committed elsewhere in flight (an email error landing
|
|
980
|
+
// while a wizard step with such a path validated). The prune
|
|
981
|
+
// rides only while the whole values reference held.
|
|
982
|
+
parsed.kind === "full" && live.values === valuesAtStart ? parsed.errors : null
|
|
828
983
|
),
|
|
829
984
|
isValidating: omitKeys(state.isValidating, ownedKeys)
|
|
830
985
|
}));
|
|
@@ -845,24 +1000,32 @@ var createForm = (schema, options) => {
|
|
|
845
1000
|
...state,
|
|
846
1001
|
isValidating: { ...state.isValidating, [path]: true }
|
|
847
1002
|
}));
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
const stillOwns = sequences.get(path) === pass;
|
|
851
|
-
if (!stillOwns || scopeStale(scope, path, valuesAtStart, live.values)) {
|
|
852
|
-
if (stillOwns) {
|
|
1003
|
+
const releaseOwnFlag = () => {
|
|
1004
|
+
if (sequences.get(path) === pass) {
|
|
853
1005
|
store.setState((state) => ({
|
|
854
1006
|
...state,
|
|
855
1007
|
isValidating: omitKey(state.isValidating, path)
|
|
856
1008
|
}));
|
|
857
1009
|
}
|
|
858
|
-
|
|
1010
|
+
};
|
|
1011
|
+
const parse = await parseScopeAsync(path, scope).catch(
|
|
1012
|
+
(error) => {
|
|
1013
|
+
releaseOwnFlag();
|
|
1014
|
+
throw error;
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
const live = store.getState();
|
|
1018
|
+
const stillOwns = sequences.get(path) === pass;
|
|
1019
|
+
if (!stillOwns || scopeStale(scope, path, valuesAtStart, live.values)) {
|
|
1020
|
+
releaseOwnFlag();
|
|
1021
|
+
return fieldResult(parse.scoped);
|
|
859
1022
|
}
|
|
860
1023
|
store.setState((state) => ({
|
|
861
1024
|
...state,
|
|
862
|
-
...commitFieldErrors(state,
|
|
1025
|
+
...commitFieldErrors(state, parse, path),
|
|
863
1026
|
isValidating: omitKey(state.isValidating, path)
|
|
864
1027
|
}));
|
|
865
|
-
return fieldResult(scoped);
|
|
1028
|
+
return fieldResult(parse.scoped);
|
|
866
1029
|
};
|
|
867
1030
|
const applyArrayOp = (path, nextArray, mapper, indexInBounds) => {
|
|
868
1031
|
const current = getAtPath(store.getState().values, path);
|
|
@@ -921,7 +1084,9 @@ var createForm = (schema, options) => {
|
|
|
921
1084
|
count: 0,
|
|
922
1085
|
baseline: false
|
|
923
1086
|
};
|
|
924
|
-
const submit = async (onValid,
|
|
1087
|
+
const submit = async (onValid, onInvalidOrOptions, positionalOptions) => {
|
|
1088
|
+
const submitOptions = typeof onInvalidOrOptions === "object" ? onInvalidOrOptions : positionalOptions;
|
|
1089
|
+
const onInvalid = typeof onInvalidOrOptions === "function" ? onInvalidOrOptions : submitOptions?.onInvalid;
|
|
925
1090
|
if (inFlight.count > 0 && submitOptions?.force !== true) {
|
|
926
1091
|
return { kind: "skipped" };
|
|
927
1092
|
}
|
|
@@ -962,6 +1127,7 @@ var createForm = (schema, options) => {
|
|
|
962
1127
|
try {
|
|
963
1128
|
onInvalid?.(merged);
|
|
964
1129
|
} catch (error) {
|
|
1130
|
+
submitOptions?.onError?.(error);
|
|
965
1131
|
return { kind: "error", error };
|
|
966
1132
|
}
|
|
967
1133
|
return { kind: "invalid", errors: merged };
|
|
@@ -987,9 +1153,14 @@ var createForm = (schema, options) => {
|
|
|
987
1153
|
}
|
|
988
1154
|
}
|
|
989
1155
|
};
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1156
|
+
const judgeBaseline = () => {
|
|
1157
|
+
if (options.validateOnMount !== true) return;
|
|
1158
|
+
const result = validate();
|
|
1159
|
+
if (result.kind === "pending") {
|
|
1160
|
+
void result.promise.catch(reportBackgroundFailure);
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
judgeBaseline();
|
|
993
1164
|
return Object.freeze({
|
|
994
1165
|
schema,
|
|
995
1166
|
store,
|
|
@@ -1004,8 +1175,12 @@ var createForm = (schema, options) => {
|
|
|
1004
1175
|
return store.subscribe((state) => {
|
|
1005
1176
|
const next = snapshotField(state, path);
|
|
1006
1177
|
if (!shallowFieldEqual(ref.current, next)) {
|
|
1178
|
+
const prev = ref.current;
|
|
1007
1179
|
ref.current = next;
|
|
1008
|
-
listener(
|
|
1180
|
+
listener(
|
|
1181
|
+
next,
|
|
1182
|
+
prev
|
|
1183
|
+
);
|
|
1009
1184
|
}
|
|
1010
1185
|
});
|
|
1011
1186
|
},
|
|
@@ -1040,10 +1215,12 @@ var createForm = (schema, options) => {
|
|
|
1040
1215
|
if (Object.is(getAtPath(currentValues, path), value) && (value !== void 0 || hasOwnSlot(currentValues, path))) {
|
|
1041
1216
|
return;
|
|
1042
1217
|
}
|
|
1218
|
+
const next = setAtPath(currentValues, path, value, kindAt);
|
|
1219
|
+
if (next === currentValues) return;
|
|
1043
1220
|
clearArrayOpsUnder(store, path);
|
|
1044
1221
|
store.setState((state) => ({
|
|
1045
1222
|
...state,
|
|
1046
|
-
values:
|
|
1223
|
+
values: next,
|
|
1047
1224
|
...errorChannels(state, {
|
|
1048
1225
|
serverErrors: releaseSpine(state.serverErrors, path)
|
|
1049
1226
|
})
|
|
@@ -1081,7 +1258,7 @@ var createForm = (schema, options) => {
|
|
|
1081
1258
|
},
|
|
1082
1259
|
setErrors: (errors) => store.setState((state) => ({
|
|
1083
1260
|
...state,
|
|
1084
|
-
...errorChannels(state, { serverErrors: errors })
|
|
1261
|
+
...errorChannels(state, { serverErrors: dropEmptyLists(errors) })
|
|
1085
1262
|
})),
|
|
1086
1263
|
addErrors: (errors) => store.setState((state) => {
|
|
1087
1264
|
const entries = Object.entries(errors);
|
|
@@ -1124,11 +1301,11 @@ var createForm = (schema, options) => {
|
|
|
1124
1301
|
if (next.values !== state.values) {
|
|
1125
1302
|
clearArrayOpsUnder(store, "");
|
|
1126
1303
|
}
|
|
1127
|
-
const serverErrors = patch.serverErrors !== void 0 ? patch.serverErrors : next.values !== state.values ? releaseChangedSlices(state.serverErrors, state.values, next.values) : state.serverErrors;
|
|
1304
|
+
const serverErrors = patch.serverErrors !== void 0 ? dropEmptyLists(patch.serverErrors) : next.values !== state.values ? releaseChangedSlices(state.serverErrors, state.values, next.values) : state.serverErrors;
|
|
1128
1305
|
store.setState(() => ({
|
|
1129
1306
|
...next,
|
|
1130
1307
|
...errorChannels(state, {
|
|
1131
|
-
schemaErrors:
|
|
1308
|
+
schemaErrors: patch.schemaErrors === void 0 ? state.schemaErrors : dropEmptyLists(patch.schemaErrors),
|
|
1132
1309
|
serverErrors
|
|
1133
1310
|
})
|
|
1134
1311
|
}));
|
|
@@ -1150,6 +1327,7 @@ var createForm = (schema, options) => {
|
|
|
1150
1327
|
isValidating: emptyBools,
|
|
1151
1328
|
isValidatingForm: false
|
|
1152
1329
|
}));
|
|
1330
|
+
judgeBaseline();
|
|
1153
1331
|
},
|
|
1154
1332
|
reset: (nextInitial, resetOptions) => {
|
|
1155
1333
|
sequences.clear();
|
|
@@ -1174,6 +1352,7 @@ var createForm = (schema, options) => {
|
|
|
1174
1352
|
isValidatingForm: false
|
|
1175
1353
|
};
|
|
1176
1354
|
});
|
|
1355
|
+
if (resetOptions?.keepErrors !== true) judgeBaseline();
|
|
1177
1356
|
},
|
|
1178
1357
|
resetField: (path) => {
|
|
1179
1358
|
const initialSlot = slotAtPath(
|
|
@@ -1237,9 +1416,14 @@ var createForm = (schema, options) => {
|
|
|
1237
1416
|
swapIndices(a, b),
|
|
1238
1417
|
(len) => Number.isInteger(a) && Number.isInteger(b) && a >= 0 && a < len && b >= 0 && b < len
|
|
1239
1418
|
),
|
|
1240
|
-
handleSubmit: (onValid,
|
|
1419
|
+
handleSubmit: (onValid, onInvalidOrOptions, positionalOptions) => async (event) => {
|
|
1241
1420
|
event?.preventDefault();
|
|
1242
|
-
const
|
|
1421
|
+
const opts = typeof onInvalidOrOptions === "object" ? onInvalidOrOptions : positionalOptions;
|
|
1422
|
+
const result = await submit(
|
|
1423
|
+
onValid,
|
|
1424
|
+
onInvalidOrOptions,
|
|
1425
|
+
positionalOptions
|
|
1426
|
+
);
|
|
1243
1427
|
if (result.kind === "error" && opts?.onError === void 0 && isDevBuild()) {
|
|
1244
1428
|
console.warn(
|
|
1245
1429
|
"[formstand] submit handler threw and nothing observed it \u2014 the form shows no feedback. Pass { onError } to handleSubmit (or setError inside your handler):",
|
|
@@ -1263,10 +1447,11 @@ var createForm = (schema, options) => {
|
|
|
1263
1447
|
},
|
|
1264
1448
|
snapshot: () => store.getState(),
|
|
1265
1449
|
restore: (snap) => {
|
|
1450
|
+
sequences.clear();
|
|
1266
1451
|
clearArrayOpsUnder(store, "");
|
|
1267
1452
|
store.setState(() => {
|
|
1268
|
-
const schemaErrors = snap.schemaErrors ?? emptyErrors;
|
|
1269
|
-
const serverErrors = snap.serverErrors ?? emptyErrors;
|
|
1453
|
+
const schemaErrors = dropEmptyLists(snap.schemaErrors ?? emptyErrors);
|
|
1454
|
+
const serverErrors = dropEmptyLists(snap.serverErrors ?? emptyErrors);
|
|
1270
1455
|
return {
|
|
1271
1456
|
...snap,
|
|
1272
1457
|
schemaErrors,
|
|
@@ -1313,11 +1498,13 @@ var createDebouncer = (ms) => {
|
|
|
1313
1498
|
const timer = {
|
|
1314
1499
|
current: null
|
|
1315
1500
|
};
|
|
1501
|
+
const scheduled = { current: null };
|
|
1316
1502
|
const cancel = () => {
|
|
1317
1503
|
if (timer.current !== null) {
|
|
1318
1504
|
clearTimeout(timer.current);
|
|
1319
1505
|
timer.current = null;
|
|
1320
1506
|
}
|
|
1507
|
+
scheduled.current = null;
|
|
1321
1508
|
};
|
|
1322
1509
|
const schedule = (run) => {
|
|
1323
1510
|
cancel();
|
|
@@ -1325,37 +1512,50 @@ var createDebouncer = (ms) => {
|
|
|
1325
1512
|
run();
|
|
1326
1513
|
return;
|
|
1327
1514
|
}
|
|
1515
|
+
scheduled.current = run;
|
|
1328
1516
|
timer.current = setTimeout(() => {
|
|
1329
1517
|
timer.current = null;
|
|
1518
|
+
scheduled.current = null;
|
|
1330
1519
|
run();
|
|
1331
1520
|
}, ms);
|
|
1332
1521
|
};
|
|
1522
|
+
const flush = () => {
|
|
1523
|
+
const run = scheduled.current;
|
|
1524
|
+
if (run === null) return;
|
|
1525
|
+
cancel();
|
|
1526
|
+
run();
|
|
1527
|
+
};
|
|
1333
1528
|
return Object.freeze({
|
|
1334
1529
|
schedule,
|
|
1335
1530
|
cancel,
|
|
1531
|
+
flush,
|
|
1336
1532
|
pending: () => timer.current !== null
|
|
1337
1533
|
});
|
|
1338
1534
|
};
|
|
1339
1535
|
|
|
1340
1536
|
// src/core/persist.ts
|
|
1341
1537
|
var kindOf = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
1342
|
-
var
|
|
1343
|
-
|
|
1344
|
-
if (
|
|
1538
|
+
var joinPath = (base, key) => base === "" ? String(key) : `${base}.${String(key)}`;
|
|
1539
|
+
var conflictPath = (draft, reference, path, flexibleAt) => {
|
|
1540
|
+
if (draft === void 0 || draft === null) return null;
|
|
1541
|
+
if (reference === void 0 || reference === null) return null;
|
|
1345
1542
|
const draftKind = kindOf(draft);
|
|
1346
1543
|
const referenceKind = kindOf(reference);
|
|
1347
|
-
if (draftKind !== referenceKind) return
|
|
1544
|
+
if (draftKind !== referenceKind) return flexibleAt(path) ? null : path;
|
|
1348
1545
|
if (draftKind === "array") {
|
|
1349
1546
|
const [d] = draft;
|
|
1350
1547
|
const [r] = reference;
|
|
1351
|
-
return
|
|
1548
|
+
return conflictPath(d, r, joinPath(path, 0), flexibleAt);
|
|
1352
1549
|
}
|
|
1353
1550
|
if (draftKind === "object") {
|
|
1354
1551
|
const d = draft;
|
|
1355
1552
|
const r = reference;
|
|
1356
|
-
return Object.keys(d).
|
|
1553
|
+
return Object.keys(d).reduce(
|
|
1554
|
+
(found, key) => found ?? conflictPath(d[key], r[key], joinPath(path, key), flexibleAt),
|
|
1555
|
+
null
|
|
1556
|
+
);
|
|
1357
1557
|
}
|
|
1358
|
-
return
|
|
1558
|
+
return null;
|
|
1359
1559
|
};
|
|
1360
1560
|
var defaultStorage = () => {
|
|
1361
1561
|
try {
|
|
@@ -1368,7 +1568,49 @@ var persistForm = (form, options) => {
|
|
|
1368
1568
|
const storage = options.storage ?? defaultStorage();
|
|
1369
1569
|
const debounceMs = options.debounceMs ?? 300;
|
|
1370
1570
|
const apply = options.apply ?? "adopt";
|
|
1371
|
-
const
|
|
1571
|
+
const schema = form.schema;
|
|
1572
|
+
const fillRequiredGaps = (draft, initial, path) => {
|
|
1573
|
+
if (!isPlainObject(draft) || !isPlainObject(initial)) return draft;
|
|
1574
|
+
if (!isObjectSchemaAt(schema, path)) return draft;
|
|
1575
|
+
return Object.keys(initial).reduce(
|
|
1576
|
+
(acc, key) => {
|
|
1577
|
+
const childPath = joinPath(path, key);
|
|
1578
|
+
if (Object.hasOwn(draft, key)) {
|
|
1579
|
+
const next = fillRequiredGaps(draft[key], initial[key], childPath);
|
|
1580
|
+
return Object.is(next, draft[key]) ? acc : { ...acc, [key]: next };
|
|
1581
|
+
}
|
|
1582
|
+
if (initial[key] === void 0) return acc;
|
|
1583
|
+
const sub = fieldSchemaForIntrospection(schema, childPath);
|
|
1584
|
+
return sub !== null && schemaAcceptsUndefined(sub) ? acc : { ...acc, [key]: initial[key] };
|
|
1585
|
+
},
|
|
1586
|
+
draft
|
|
1587
|
+
);
|
|
1588
|
+
};
|
|
1589
|
+
const applying = { current: false };
|
|
1590
|
+
const applyDraft = (values) => {
|
|
1591
|
+
const filled = fillRequiredGaps(
|
|
1592
|
+
values,
|
|
1593
|
+
form.getState().initialValues,
|
|
1594
|
+
""
|
|
1595
|
+
);
|
|
1596
|
+
applying.current = true;
|
|
1597
|
+
try {
|
|
1598
|
+
if (apply === "edit" || apply === "restore") {
|
|
1599
|
+
form.setValues(filled);
|
|
1600
|
+
} else {
|
|
1601
|
+
form.adoptValues(filled);
|
|
1602
|
+
}
|
|
1603
|
+
} finally {
|
|
1604
|
+
applying.current = false;
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
const isCurrent = (values) => {
|
|
1608
|
+
try {
|
|
1609
|
+
return JSON.stringify(values) === JSON.stringify(form.getState().values);
|
|
1610
|
+
} catch {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1372
1614
|
const referenceShape = () => {
|
|
1373
1615
|
try {
|
|
1374
1616
|
return JSON.parse(JSON.stringify(form.getState().initialValues));
|
|
@@ -1376,31 +1618,70 @@ var persistForm = (form, options) => {
|
|
|
1376
1618
|
return void 0;
|
|
1377
1619
|
}
|
|
1378
1620
|
};
|
|
1379
|
-
const
|
|
1621
|
+
const flexibleAt = (path) => schemaAllowsKindChangeAt(schema, path);
|
|
1622
|
+
const inspect = (skipWhenCurrent) => {
|
|
1623
|
+
const raw = (() => {
|
|
1624
|
+
try {
|
|
1625
|
+
return storage === null ? null : storage.getItem(options.key);
|
|
1626
|
+
} catch {
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
})();
|
|
1630
|
+
if (raw === null) return { kind: "absent" };
|
|
1380
1631
|
try {
|
|
1381
|
-
const raw = storage === null ? null : storage.getItem(options.key);
|
|
1382
|
-
if (raw === null) return false;
|
|
1383
1632
|
const parsed = JSON.parse(raw);
|
|
1384
1633
|
const versioned = options.version !== void 0 && typeof parsed === "object" && parsed !== null && "__v" in parsed;
|
|
1385
|
-
if (options.version !== void 0 && !versioned)
|
|
1634
|
+
if (options.version !== void 0 && !versioned) {
|
|
1635
|
+
return {
|
|
1636
|
+
kind: "ignored",
|
|
1637
|
+
reason: `it carries no version and this configuration expects version ${String(options.version)}`
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1386
1640
|
if (options.version === void 0 && typeof parsed === "object" && parsed !== null && Object.hasOwn(parsed, "__v") && Object.hasOwn(parsed, "values")) {
|
|
1387
|
-
return
|
|
1641
|
+
return {
|
|
1642
|
+
kind: "ignored",
|
|
1643
|
+
reason: "it was written with a version and this configuration has none"
|
|
1644
|
+
};
|
|
1388
1645
|
}
|
|
1389
1646
|
const wrapper = parsed;
|
|
1390
|
-
if (versioned && wrapper.__v !== options.version)
|
|
1391
|
-
|
|
1647
|
+
if (versioned && wrapper.__v !== options.version) {
|
|
1648
|
+
return {
|
|
1649
|
+
kind: "ignored",
|
|
1650
|
+
reason: `its version (${String(wrapper.__v)}) is not the configured version (${String(options.version)})`
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
if (versioned && wrapper.values === void 0) {
|
|
1654
|
+
return { kind: "ignored", reason: "its version wrapper holds no values" };
|
|
1655
|
+
}
|
|
1392
1656
|
const values = versioned ? wrapper.values : parsed;
|
|
1393
|
-
if (values === null || values === void 0)
|
|
1657
|
+
if (values === null || values === void 0) {
|
|
1658
|
+
return { kind: "ignored", reason: "it holds no values" };
|
|
1659
|
+
}
|
|
1394
1660
|
const reference = referenceShape();
|
|
1395
|
-
|
|
1396
|
-
|
|
1661
|
+
const conflict = reference === void 0 ? null : conflictPath(values, reference, "", flexibleAt);
|
|
1662
|
+
if (conflict !== null) {
|
|
1663
|
+
return {
|
|
1664
|
+
kind: "ignored",
|
|
1665
|
+
reason: `its shape conflicts with the form's initial values at "${conflict}" (set the version option when the schema changes)`
|
|
1666
|
+
};
|
|
1397
1667
|
}
|
|
1668
|
+
if (skipWhenCurrent && isCurrent(values)) return { kind: "applied" };
|
|
1398
1669
|
applyDraft(values);
|
|
1399
|
-
return
|
|
1670
|
+
return { kind: "applied" };
|
|
1400
1671
|
} catch {
|
|
1401
|
-
return
|
|
1672
|
+
return { kind: "ignored", reason: "it is not valid JSON" };
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
const loadWith = (skipWhenCurrent) => {
|
|
1676
|
+
const outcome = inspect(skipWhenCurrent);
|
|
1677
|
+
if (outcome.kind === "ignored" && isDevBuild()) {
|
|
1678
|
+
console.warn(
|
|
1679
|
+
`[formstand] persistForm: ignored the draft under "${options.key}": ${outcome.reason}. The next edit overwrites it.`
|
|
1680
|
+
);
|
|
1402
1681
|
}
|
|
1682
|
+
return outcome.kind === "applied";
|
|
1403
1683
|
};
|
|
1684
|
+
const load = () => loadWith(false);
|
|
1404
1685
|
const writeDraft = (values) => {
|
|
1405
1686
|
try {
|
|
1406
1687
|
storage?.setItem(
|
|
@@ -1413,24 +1694,29 @@ var persistForm = (form, options) => {
|
|
|
1413
1694
|
}
|
|
1414
1695
|
};
|
|
1415
1696
|
const writes = createDebouncer(debounceMs);
|
|
1416
|
-
const cancelPending = () => writes.cancel();
|
|
1417
1697
|
if (apply !== "manual") {
|
|
1418
|
-
|
|
1698
|
+
loadWith(true);
|
|
1419
1699
|
}
|
|
1700
|
+
const cleared = { current: false };
|
|
1420
1701
|
const unsubscribe = form.watchValues((values) => {
|
|
1702
|
+
if (applying.current) return;
|
|
1703
|
+
if (cleared.current && values === form.getState().initialValues) return;
|
|
1704
|
+
cleared.current = false;
|
|
1421
1705
|
writes.schedule(() => writeDraft(values));
|
|
1422
1706
|
});
|
|
1423
1707
|
return Object.freeze({
|
|
1424
1708
|
load,
|
|
1709
|
+
flush: () => writes.flush(),
|
|
1425
1710
|
clear: () => {
|
|
1426
|
-
|
|
1711
|
+
writes.cancel();
|
|
1712
|
+
cleared.current = true;
|
|
1427
1713
|
try {
|
|
1428
1714
|
storage?.removeItem(options.key);
|
|
1429
1715
|
} catch {
|
|
1430
1716
|
}
|
|
1431
1717
|
},
|
|
1432
1718
|
dispose: () => {
|
|
1433
|
-
|
|
1719
|
+
writes.flush();
|
|
1434
1720
|
unsubscribe();
|
|
1435
1721
|
}
|
|
1436
1722
|
});
|
|
@@ -1442,17 +1728,31 @@ var usePersistForm = (form, options) => {
|
|
|
1442
1728
|
latestOptions.current = options;
|
|
1443
1729
|
});
|
|
1444
1730
|
const key = options.key;
|
|
1731
|
+
const hasStorage = options.storage !== void 0;
|
|
1732
|
+
const storageProxy = react.useMemo(
|
|
1733
|
+
() => ({
|
|
1734
|
+
getItem: (k) => latestOptions.current.storage?.getItem(k) ?? null,
|
|
1735
|
+
setItem: (k, v) => latestOptions.current.storage?.setItem(k, v),
|
|
1736
|
+
removeItem: (k) => latestOptions.current.storage?.removeItem(k)
|
|
1737
|
+
}),
|
|
1738
|
+
[]
|
|
1739
|
+
);
|
|
1445
1740
|
react.useEffect(() => {
|
|
1446
|
-
const
|
|
1741
|
+
const options2 = latestOptions.current;
|
|
1742
|
+
const handle = persistForm(form, {
|
|
1743
|
+
...options2,
|
|
1744
|
+
...options2.storage === void 0 ? {} : { storage: storageProxy }
|
|
1745
|
+
});
|
|
1447
1746
|
inner.current = handle;
|
|
1448
1747
|
return () => {
|
|
1449
1748
|
inner.current = null;
|
|
1450
1749
|
handle.dispose();
|
|
1451
1750
|
};
|
|
1452
|
-
}, [form, key]);
|
|
1751
|
+
}, [form, key, hasStorage, storageProxy]);
|
|
1453
1752
|
return react.useMemo(
|
|
1454
1753
|
() => ({
|
|
1455
1754
|
load: () => inner.current?.load() ?? false,
|
|
1755
|
+
flush: () => inner.current?.flush(),
|
|
1456
1756
|
clear: () => inner.current?.clear(),
|
|
1457
1757
|
// dispose stops THIS component's watching early; the effect cleanup
|
|
1458
1758
|
// still runs its own dispose harmlessly (persistForm's dispose is
|
|
@@ -1486,9 +1786,12 @@ var useForm = (schema, options) => {
|
|
|
1486
1786
|
var hasFieldError = (error) => error !== void 0 && error.length > 0;
|
|
1487
1787
|
var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
|
|
1488
1788
|
var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
|
|
1789
|
+
var DECIMAL_TEXT = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
1489
1790
|
var parseNumberText = (text) => {
|
|
1490
|
-
|
|
1491
|
-
|
|
1791
|
+
const trimmed = text.trim();
|
|
1792
|
+
if (trimmed === "") return { kind: "empty" };
|
|
1793
|
+
if (!DECIMAL_TEXT.test(trimmed)) return { kind: "invalid" };
|
|
1794
|
+
const parsed = Number(trimmed);
|
|
1492
1795
|
return Number.isFinite(parsed) ? { kind: "number", value: parsed } : { kind: "invalid" };
|
|
1493
1796
|
};
|
|
1494
1797
|
var textInputProps = (field) => ({
|
|
@@ -1516,11 +1819,17 @@ var numberInputProps = (field) => ({
|
|
|
1516
1819
|
onBlur: field.onBlur
|
|
1517
1820
|
});
|
|
1518
1821
|
var dateToInputText = (value) => {
|
|
1519
|
-
if (value === void 0 || value === null
|
|
1520
|
-
|
|
1822
|
+
if (value === void 0 || value === null) return "";
|
|
1823
|
+
if (typeof value === "string") {
|
|
1824
|
+
const text = value;
|
|
1825
|
+
const parsed = parseDateText(text);
|
|
1826
|
+
if (parsed.kind === "date") return dateToInputText(parsed.value);
|
|
1827
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(text.trim())) return "";
|
|
1521
1828
|
}
|
|
1829
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1830
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
1522
1831
|
const pad = (part) => String(part).padStart(2, "0");
|
|
1523
|
-
return `${String(
|
|
1832
|
+
return `${String(date.getFullYear()).padStart(4, "0")}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
1524
1833
|
};
|
|
1525
1834
|
var parseDateText = (text) => {
|
|
1526
1835
|
if (text.trim() === "") return { kind: "empty" };
|
|
@@ -1600,7 +1909,8 @@ var useMaskedInput = (field, options) => {
|
|
|
1600
1909
|
field.setValue(parsed.value);
|
|
1601
1910
|
return;
|
|
1602
1911
|
case "empty": {
|
|
1603
|
-
const
|
|
1912
|
+
const isStringField = typeof field.initialValue === "string" || typeof field.value === "string";
|
|
1913
|
+
const empty = field.clearable || !isStringField ? field.emptyValue : "";
|
|
1604
1914
|
setEdit({ raw: text, pushed: empty });
|
|
1605
1915
|
field.setValue(empty);
|
|
1606
1916
|
return;
|
|
@@ -1624,6 +1934,15 @@ function useFormSelectorShallow(form, selector) {
|
|
|
1624
1934
|
}
|
|
1625
1935
|
|
|
1626
1936
|
// src/react/useFormSteps.ts
|
|
1937
|
+
var settleOnLiveValues = async (form, fields, round = 0) => {
|
|
1938
|
+
const before = fields.map((path) => form.getField(path));
|
|
1939
|
+
const result = await form.validateFieldsAsync(fields);
|
|
1940
|
+
const changed = fields.some(
|
|
1941
|
+
(path, i) => !Object.is(form.getField(path), before[i])
|
|
1942
|
+
);
|
|
1943
|
+
if (!changed) return result;
|
|
1944
|
+
return round < 3 ? settleOnLiveValues(form, fields, round + 1) : null;
|
|
1945
|
+
};
|
|
1627
1946
|
var useFormSteps = (form, steps) => {
|
|
1628
1947
|
const [step, setStep] = react.useState(0);
|
|
1629
1948
|
const [visited, setVisited] = react.useState(
|
|
@@ -1637,7 +1956,8 @@ var useFormSteps = (form, steps) => {
|
|
|
1637
1956
|
async (index) => {
|
|
1638
1957
|
const definition = latest.current[index];
|
|
1639
1958
|
if (definition === void 0) return true;
|
|
1640
|
-
const result = await form
|
|
1959
|
+
const result = await settleOnLiveValues(form, definition.fields);
|
|
1960
|
+
if (result === null) return false;
|
|
1641
1961
|
if (result.kind === "invalid") {
|
|
1642
1962
|
Object.keys(result.errors).forEach((key) => {
|
|
1643
1963
|
form.setTouched(key, true);
|
|
@@ -1649,45 +1969,49 @@ var useFormSteps = (form, steps) => {
|
|
|
1649
1969
|
[form]
|
|
1650
1970
|
);
|
|
1651
1971
|
const currentStep = react.useRef(step);
|
|
1652
|
-
react.
|
|
1653
|
-
currentStep.current =
|
|
1654
|
-
|
|
1972
|
+
const navigate = react.useCallback((target) => {
|
|
1973
|
+
currentStep.current = target;
|
|
1974
|
+
setStep(target);
|
|
1975
|
+
}, []);
|
|
1655
1976
|
const next = react.useCallback(async () => {
|
|
1656
|
-
const
|
|
1977
|
+
const from = currentStep.current;
|
|
1978
|
+
const ok = await validateStep(from);
|
|
1657
1979
|
if (!ok) return false;
|
|
1658
|
-
if (currentStep.current !==
|
|
1659
|
-
const target = Math.min(
|
|
1660
|
-
|
|
1980
|
+
if (currentStep.current !== from) return false;
|
|
1981
|
+
const target = Math.max(0, Math.min(from + 1, latest.current.length - 1));
|
|
1982
|
+
navigate(target);
|
|
1661
1983
|
setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
|
|
1662
1984
|
return true;
|
|
1663
|
-
}, [validateStep,
|
|
1985
|
+
}, [validateStep, navigate]);
|
|
1664
1986
|
const back = react.useCallback(() => {
|
|
1665
|
-
|
|
1666
|
-
}, []);
|
|
1987
|
+
navigate(Math.max(currentStep.current - 1, 0));
|
|
1988
|
+
}, [navigate]);
|
|
1667
1989
|
const goTo = react.useCallback(
|
|
1668
1990
|
async (target) => {
|
|
1669
1991
|
const count = latest.current.length;
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1992
|
+
if (!Number.isFinite(target)) return currentStep.current;
|
|
1993
|
+
const from = currentStep.current;
|
|
1994
|
+
const clamped = Math.max(0, Math.min(Math.trunc(target), count - 1));
|
|
1995
|
+
if (clamped <= from) {
|
|
1996
|
+
navigate(clamped);
|
|
1673
1997
|
return clamped;
|
|
1674
1998
|
}
|
|
1675
|
-
const walk = async (
|
|
1676
|
-
if (
|
|
1677
|
-
return await validateStep(
|
|
1999
|
+
const walk = async (at) => {
|
|
2000
|
+
if (at >= clamped) return clamped;
|
|
2001
|
+
return await validateStep(at) ? walk(at + 1) : at;
|
|
1678
2002
|
};
|
|
1679
|
-
const landed = await walk(
|
|
1680
|
-
if (currentStep.current !==
|
|
1681
|
-
|
|
2003
|
+
const landed = await walk(from);
|
|
2004
|
+
if (currentStep.current !== from) return currentStep.current;
|
|
2005
|
+
navigate(landed);
|
|
1682
2006
|
setVisited(
|
|
1683
2007
|
(prev) => /* @__PURE__ */ new Set([
|
|
1684
2008
|
...prev,
|
|
1685
|
-
...Array.from({ length: landed -
|
|
2009
|
+
...Array.from({ length: landed - from + 1 }, (_, i) => from + i)
|
|
1686
2010
|
])
|
|
1687
2011
|
);
|
|
1688
2012
|
return landed;
|
|
1689
2013
|
},
|
|
1690
|
-
[validateStep,
|
|
2014
|
+
[validateStep, navigate]
|
|
1691
2015
|
);
|
|
1692
2016
|
const errorFlags = useFormSelectorShallow(
|
|
1693
2017
|
form,
|
|
@@ -1740,8 +2064,7 @@ var useFormAction = (form, onValid, options) => {
|
|
|
1740
2064
|
const { onValid: handler, options: opts } = latest.current;
|
|
1741
2065
|
const result = await form.submit(
|
|
1742
2066
|
(data) => handler(data, formData),
|
|
1743
|
-
opts
|
|
1744
|
-
opts?.onError === void 0 ? void 0 : { onError: opts.onError }
|
|
2067
|
+
opts ?? {}
|
|
1745
2068
|
);
|
|
1746
2069
|
warnUnobserved(result, opts, "useFormAction");
|
|
1747
2070
|
},
|
|
@@ -1757,8 +2080,7 @@ var useFormActionState = (form, action, initialState, options) => {
|
|
|
1757
2080
|
async (data) => {
|
|
1758
2081
|
nextRef.current = await action(prev, data, formData);
|
|
1759
2082
|
},
|
|
1760
|
-
options
|
|
1761
|
-
options?.onError === void 0 ? void 0 : { onError: options.onError }
|
|
2083
|
+
options ?? {}
|
|
1762
2084
|
);
|
|
1763
2085
|
warnUnobserved(result, options, "useFormActionState");
|
|
1764
2086
|
return nextRef.current;
|
|
@@ -1782,6 +2104,7 @@ var createFormContext = () => {
|
|
|
1782
2104
|
return { Provider, useFormContext };
|
|
1783
2105
|
};
|
|
1784
2106
|
function useField(form, pathArg, options) {
|
|
2107
|
+
const formSchema = form.schema;
|
|
1785
2108
|
const slice = react$1.useStore(
|
|
1786
2109
|
form.store,
|
|
1787
2110
|
shallow.useShallow((state) => {
|
|
@@ -1797,25 +2120,26 @@ function useField(form, pathArg, options) {
|
|
|
1797
2120
|
error: entryAt(state.errors, p),
|
|
1798
2121
|
touched: flagAt(state.touched, p),
|
|
1799
2122
|
dirty: isFieldDirty(value, initialValue2),
|
|
1800
|
-
isValidating: flagAt(state.isValidating, p)
|
|
2123
|
+
isValidating: flagAt(state.isValidating, p),
|
|
2124
|
+
// Resolved inside the selector so a discriminant change re-reads
|
|
2125
|
+
// the ACTIVE variant's wrapper for a key the variants share; the
|
|
2126
|
+
// schema node is a stable reference, so the shallow compare holds.
|
|
2127
|
+
schema: formSchema === void 0 ? null : fieldSchemaForIntrospection(formSchema, p, state.values)
|
|
1801
2128
|
};
|
|
1802
2129
|
})
|
|
1803
2130
|
);
|
|
1804
2131
|
const path = slice.path;
|
|
1805
|
-
const
|
|
2132
|
+
const sub = slice.schema;
|
|
1806
2133
|
const initialValue = slice.initialValue;
|
|
1807
2134
|
const { emptyValue, clearable } = react.useMemo(() => {
|
|
1808
|
-
if (
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
clearable: isClearableSchema(sub)
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
2135
|
+
if (sub !== null) {
|
|
2136
|
+
return {
|
|
2137
|
+
emptyValue: emptyValueForSchema(sub),
|
|
2138
|
+
clearable: isClearableSchema(sub)
|
|
2139
|
+
};
|
|
1816
2140
|
}
|
|
1817
2141
|
return initialValue === null ? { emptyValue: null, clearable: true } : { emptyValue: void 0, clearable: false };
|
|
1818
|
-
}, [
|
|
2142
|
+
}, [sub, initialValue]);
|
|
1819
2143
|
const debounceMs = options?.debounceMs;
|
|
1820
2144
|
const debouncerRef = react.useRef(null);
|
|
1821
2145
|
react.useEffect(() => {
|
|
@@ -1824,6 +2148,12 @@ function useField(form, pathArg, options) {
|
|
|
1824
2148
|
debouncerRef.current = null;
|
|
1825
2149
|
};
|
|
1826
2150
|
}, [form, path, debounceMs]);
|
|
2151
|
+
const validateNow = react.useCallback(() => {
|
|
2152
|
+
const result = form.validateField(path);
|
|
2153
|
+
if (result.kind === "pending") {
|
|
2154
|
+
void result.promise.catch(reportBackgroundFailure);
|
|
2155
|
+
}
|
|
2156
|
+
}, [form, path]);
|
|
1827
2157
|
const triggerValidate = react.useCallback(() => {
|
|
1828
2158
|
if (debounceMs !== void 0 && debounceMs > 0) {
|
|
1829
2159
|
const debouncer = debouncerRef.current ?? createDebouncer(debounceMs);
|
|
@@ -1835,12 +2165,12 @@ function useField(form, pathArg, options) {
|
|
|
1835
2165
|
const rebased = state.values !== scheduledValues && state.values === state.initialValues;
|
|
1836
2166
|
if (rebased) return;
|
|
1837
2167
|
if (!Object.is(getAtPath(state.values, path), scheduled)) return;
|
|
1838
|
-
void form.validateFieldAsync(path);
|
|
2168
|
+
void form.validateFieldAsync(path).catch(reportBackgroundFailure);
|
|
1839
2169
|
});
|
|
1840
2170
|
return;
|
|
1841
2171
|
}
|
|
1842
|
-
|
|
1843
|
-
}, [form, path, debounceMs]);
|
|
2172
|
+
validateNow();
|
|
2173
|
+
}, [form, path, debounceMs, validateNow]);
|
|
1844
2174
|
const setValue = react.useCallback(
|
|
1845
2175
|
(value) => {
|
|
1846
2176
|
const before = form.store.getState();
|
|
@@ -1889,9 +2219,10 @@ function useField(form, pathArg, options) {
|
|
|
1889
2219
|
state.submitCount > 0,
|
|
1890
2220
|
true
|
|
1891
2221
|
)) {
|
|
1892
|
-
|
|
2222
|
+
debouncerRef.current?.cancel();
|
|
2223
|
+
validateNow();
|
|
1893
2224
|
}
|
|
1894
|
-
}, [form, path,
|
|
2225
|
+
}, [form, path, validateNow]);
|
|
1895
2226
|
return react.useMemo(
|
|
1896
2227
|
() => ({
|
|
1897
2228
|
path: slice.path,
|
|
@@ -1918,10 +2249,11 @@ function useField(form, pathArg, options) {
|
|
|
1918
2249
|
}
|
|
1919
2250
|
|
|
1920
2251
|
// src/react/useVariantField.ts
|
|
1921
|
-
function useVariantField(form, unionPath, field) {
|
|
2252
|
+
function useVariantField(form, unionPath, field, options) {
|
|
1922
2253
|
return useField(
|
|
1923
2254
|
form,
|
|
1924
|
-
`${unionPath}.${field}
|
|
2255
|
+
`${unionPath}.${field}`,
|
|
2256
|
+
options
|
|
1925
2257
|
);
|
|
1926
2258
|
}
|
|
1927
2259
|
var EMPTY_ITEMS = [];
|
|
@@ -2084,6 +2416,12 @@ function useFieldArray(form, pathArg) {
|
|
|
2084
2416
|
const items = slice.items;
|
|
2085
2417
|
const error = slice.error;
|
|
2086
2418
|
const ids = deriveIds(form.store, path, items).ids;
|
|
2419
|
+
const validateNow = react.useCallback(() => {
|
|
2420
|
+
const result = form.validateField?.(path);
|
|
2421
|
+
if (result?.kind === "pending") {
|
|
2422
|
+
void result.promise.catch(reportBackgroundFailure);
|
|
2423
|
+
}
|
|
2424
|
+
}, [form, path]);
|
|
2087
2425
|
const revalidate = react.useCallback(() => {
|
|
2088
2426
|
const state = form.store.getState();
|
|
2089
2427
|
if (shouldValidateOn(
|
|
@@ -2093,9 +2431,9 @@ function useFieldArray(form, pathArg) {
|
|
|
2093
2431
|
state.submitCount > 0,
|
|
2094
2432
|
flagAt(state.touched, path)
|
|
2095
2433
|
)) {
|
|
2096
|
-
|
|
2434
|
+
validateNow();
|
|
2097
2435
|
}
|
|
2098
|
-
}, [form, path]);
|
|
2436
|
+
}, [form, path, validateNow]);
|
|
2099
2437
|
const push = react.useCallback(
|
|
2100
2438
|
(item) => {
|
|
2101
2439
|
form.arrayPush(path, item);
|
|
@@ -2160,13 +2498,17 @@ function useFieldArray(form, pathArg) {
|
|
|
2160
2498
|
state.submitCount > 0,
|
|
2161
2499
|
true
|
|
2162
2500
|
)) {
|
|
2163
|
-
|
|
2501
|
+
validateNow();
|
|
2164
2502
|
}
|
|
2165
|
-
}, [form, path]);
|
|
2503
|
+
}, [form, path, validateNow]);
|
|
2166
2504
|
const validate = react.useCallback(
|
|
2167
2505
|
() => form.validateField?.(path) ?? { kind: "valid" },
|
|
2168
2506
|
[form, path]
|
|
2169
2507
|
);
|
|
2508
|
+
const validateAsync2 = react.useCallback(
|
|
2509
|
+
() => form.validateFieldAsync?.(path) ?? Promise.resolve({ kind: "valid" }),
|
|
2510
|
+
[form, path]
|
|
2511
|
+
);
|
|
2170
2512
|
const replace = react.useCallback(
|
|
2171
2513
|
(next) => {
|
|
2172
2514
|
form.setValue?.(path, next);
|
|
@@ -2176,7 +2518,15 @@ function useFieldArray(form, pathArg) {
|
|
|
2176
2518
|
);
|
|
2177
2519
|
const update = react.useCallback(
|
|
2178
2520
|
(index, item) => {
|
|
2179
|
-
form.
|
|
2521
|
+
const current = getAtPath(form.store.getState().values, path);
|
|
2522
|
+
const length = Array.isArray(current) ? current.length : 0;
|
|
2523
|
+
if (!Number.isInteger(index) || index < 0 || index >= length) {
|
|
2524
|
+
console.warn(
|
|
2525
|
+
`[formstand] array op on "${path}" with an out-of-range or non-integer index (length ${String(length)}). Operation skipped.`
|
|
2526
|
+
);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
form.setValue?.(`${path}.${String(index)}`, item);
|
|
2180
2530
|
revalidate();
|
|
2181
2531
|
},
|
|
2182
2532
|
[form, path, revalidate]
|
|
@@ -2208,6 +2558,7 @@ function useFieldArray(form, pathArg) {
|
|
|
2208
2558
|
setTouched,
|
|
2209
2559
|
onBlur,
|
|
2210
2560
|
validate,
|
|
2561
|
+
validateAsync: validateAsync2,
|
|
2211
2562
|
push,
|
|
2212
2563
|
remove,
|
|
2213
2564
|
insert,
|
|
@@ -2231,6 +2582,7 @@ function useFieldArray(form, pathArg) {
|
|
|
2231
2582
|
setTouched,
|
|
2232
2583
|
onBlur,
|
|
2233
2584
|
validate,
|
|
2585
|
+
validateAsync2,
|
|
2234
2586
|
push,
|
|
2235
2587
|
remove,
|
|
2236
2588
|
insert,
|
|
@@ -2258,29 +2610,41 @@ var useFormError = (form) => react$1.useStore(
|
|
|
2258
2610
|
function useFormValues(form) {
|
|
2259
2611
|
return react$1.useStore(form.store, (state) => state.values);
|
|
2260
2612
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2613
|
+
function useFields(form, paths, options) {
|
|
2614
|
+
const [arity] = react.useState(paths.length);
|
|
2615
|
+
if (paths.length !== arity) {
|
|
2616
|
+
throw new Error(
|
|
2617
|
+
`[formstand] useFields: the paths tuple changed length (${String(arity)} to ${String(paths.length)}); its length and order must be stable across renders, so pass a literal or a module-level constant.`
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2264
2620
|
const fields = paths.map(
|
|
2265
2621
|
(path) => (
|
|
2266
2622
|
// eslint-disable-next-line react-hooks/rules-of-hooks -- a fixed hook count by the stable-arity contract above; each element must be a REAL UseFieldReturn so it spreads into the prop builders
|
|
2267
|
-
useField(
|
|
2623
|
+
useField(
|
|
2624
|
+
form,
|
|
2625
|
+
path,
|
|
2626
|
+
options
|
|
2627
|
+
)
|
|
2268
2628
|
)
|
|
2269
2629
|
);
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
fields.
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2630
|
+
return react.useMemo(() => {
|
|
2631
|
+
const merged = [
|
|
2632
|
+
...new Set(fields.flatMap((field) => field.error ?? []))
|
|
2633
|
+
];
|
|
2634
|
+
return {
|
|
2635
|
+
fields,
|
|
2636
|
+
error: merged.length > 0 ? merged : void 0,
|
|
2637
|
+
firstError: merged[0],
|
|
2638
|
+
touched: fields.some((field) => field.touched),
|
|
2639
|
+
dirty: fields.some((field) => field.dirty),
|
|
2640
|
+
isValidating: fields.some((field) => field.isValidating),
|
|
2641
|
+
onBlur: () => {
|
|
2642
|
+
fields.forEach((field) => {
|
|
2643
|
+
field.onBlur();
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
}, fields);
|
|
2284
2648
|
}
|
|
2285
2649
|
function useIsDirty(form, path) {
|
|
2286
2650
|
return react$1.useStore(
|
|
@@ -2323,10 +2687,11 @@ var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase
|
|
|
2323
2687
|
var createFormHooks = (form, name) => {
|
|
2324
2688
|
const structural = form;
|
|
2325
2689
|
const useBoundField = (path, options) => useField(structural, path, options);
|
|
2326
|
-
const useBoundVariantField = (unionPath, field) => useVariantField(
|
|
2690
|
+
const useBoundVariantField = (unionPath, field, options) => useVariantField(
|
|
2327
2691
|
structural,
|
|
2328
2692
|
unionPath,
|
|
2329
|
-
field
|
|
2693
|
+
field,
|
|
2694
|
+
options
|
|
2330
2695
|
);
|
|
2331
2696
|
const useBoundFieldArray = (path) => useFieldArray(structural, path);
|
|
2332
2697
|
const useBoundVariantFieldArray = (unionPath, field) => useVariantFieldArray(
|
|
@@ -2342,12 +2707,16 @@ var createFormHooks = (form, name) => {
|
|
|
2342
2707
|
const useBoundIsValidating = (path) => useIsValidating(structural, path);
|
|
2343
2708
|
const useBoundIsSubmitting = () => useIsSubmitting(structural);
|
|
2344
2709
|
const useBoundValues = () => useFormValues(structural);
|
|
2345
|
-
const useBoundFields = (paths) => useFields(
|
|
2710
|
+
const useBoundFields = (paths, options) => useFields(
|
|
2346
2711
|
structural,
|
|
2347
|
-
paths
|
|
2712
|
+
paths,
|
|
2713
|
+
options
|
|
2348
2714
|
);
|
|
2349
2715
|
const useBoundSubmitCount = () => useSubmitCount(structural);
|
|
2350
2716
|
const useBoundSteps = (steps) => useFormSteps(form, steps);
|
|
2717
|
+
const useBoundAction = (onValid, options) => useFormAction(form, onValid, options);
|
|
2718
|
+
const useBoundActionState = (action, initialState, options) => useFormActionState(form, action, initialState, options);
|
|
2719
|
+
const useBoundPersist = (options) => usePersistForm(form, options);
|
|
2351
2720
|
const prefix = capitalize(name ?? "");
|
|
2352
2721
|
return {
|
|
2353
2722
|
[`use${prefix}Field`]: useBoundField,
|
|
@@ -2364,7 +2733,10 @@ var createFormHooks = (form, name) => {
|
|
|
2364
2733
|
[`use${prefix}SubmitCount`]: useBoundSubmitCount,
|
|
2365
2734
|
[`use${prefix}Values`]: useBoundValues,
|
|
2366
2735
|
[`use${prefix}Fields`]: useBoundFields,
|
|
2367
|
-
[`use${prefix}Steps`]: useBoundSteps
|
|
2736
|
+
[`use${prefix}Steps`]: useBoundSteps,
|
|
2737
|
+
[`use${prefix}Action`]: useBoundAction,
|
|
2738
|
+
[`use${prefix}ActionState`]: useBoundActionState,
|
|
2739
|
+
[`use${prefix}Persist`]: useBoundPersist
|
|
2368
2740
|
};
|
|
2369
2741
|
};
|
|
2370
2742
|
var useFieldA11y = (error) => {
|
|
@@ -2388,7 +2760,7 @@ var FieldShell = ({
|
|
|
2388
2760
|
wrapLabel,
|
|
2389
2761
|
children
|
|
2390
2762
|
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "zf-field", children: [
|
|
2391
|
-
wrapLabel === true ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
2763
|
+
wrapLabel === true && label !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
2392
2764
|
children,
|
|
2393
2765
|
" ",
|
|
2394
2766
|
label
|