formstand 0.21.0 → 0.22.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 +430 -0
- package/README.md +2 -1
- package/dist/{chunk-OB23TUKY.js → chunk-BMXIZEI4.js} +14 -4
- package/dist/chunk-BMXIZEI4.js.map +1 -0
- package/dist/{chunk-MQ6262S3.js → chunk-V3PDMTPP.js} +7 -3
- package/dist/chunk-V3PDMTPP.js.map +1 -0
- package/dist/{createForm-B73p1TgL.d.cts → createForm-BJum0RBu.d.cts} +10 -7
- package/dist/{createForm-B73p1TgL.d.ts → createForm-BJum0RBu.d.ts} +10 -7
- package/dist/devtools/index.cjs +8 -2
- package/dist/devtools/index.cjs.map +1 -1
- package/dist/devtools/index.d.cts +7 -5
- package/dist/devtools/index.d.ts +7 -5
- package/dist/devtools/index.js +2 -3
- package/dist/devtools/index.js.map +1 -1
- package/dist/index.cjs +223 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -15
- package/dist/index.d.ts +18 -15
- package/dist/index.js +212 -84
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +4 -1
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/index.js.map +1 -1
- package/package.json +17 -17
- package/dist/chunk-MQ6262S3.js.map +0 -1
- package/dist/chunk-OB23TUKY.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -112,8 +112,10 @@ var isPlainObject = (v) => {
|
|
|
112
112
|
};
|
|
113
113
|
var valuesEqual = (a, b) => {
|
|
114
114
|
if (Object.is(a, b)) return true;
|
|
115
|
+
if (typeof a === "number" && typeof b === "number") return a === b;
|
|
115
116
|
if (a instanceof Date && b instanceof Date) {
|
|
116
|
-
|
|
117
|
+
const [ta, tb] = [a.getTime(), b.getTime()];
|
|
118
|
+
return ta === tb || Number.isNaN(ta) && Number.isNaN(tb);
|
|
117
119
|
}
|
|
118
120
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
119
121
|
return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));
|
|
@@ -143,6 +145,18 @@ var isFieldDirty = (value, initialValue) => {
|
|
|
143
145
|
return dirty;
|
|
144
146
|
};
|
|
145
147
|
|
|
148
|
+
// src/core/env.ts
|
|
149
|
+
var isDevBuild = () => typeof process === "undefined" || process.env?.["NODE_ENV"] !== "production";
|
|
150
|
+
var isDevToolsBuild = () => {
|
|
151
|
+
if (typeof process === "undefined") return false;
|
|
152
|
+
const env = process.env?.["NODE_ENV"];
|
|
153
|
+
return typeof env === "string" && env !== "production";
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/core/maps.ts
|
|
157
|
+
var entryAt = (map, key) => Object.hasOwn(map, key) ? map[key] : void 0;
|
|
158
|
+
var flagAt = (map, key) => Object.hasOwn(map, key) && map[key] === true;
|
|
159
|
+
|
|
146
160
|
// src/core/path.ts
|
|
147
161
|
var MAX_ARRAY_INDEX = 1e5;
|
|
148
162
|
var isIndexSegment = (s) => {
|
|
@@ -253,10 +267,17 @@ var expandIssue = (issue, prefix) => {
|
|
|
253
267
|
}
|
|
254
268
|
return [{ path: issuePathToFormPath(at), message: issue.message }];
|
|
255
269
|
};
|
|
256
|
-
var flattenIssues = (issues) =>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
270
|
+
var flattenIssues = (issues) => Object.fromEntries(
|
|
271
|
+
issues.flatMap((issue) => expandIssue(issue, [])).reduce((acc, { path, message }) => {
|
|
272
|
+
const existing = acc.get(path);
|
|
273
|
+
if (existing === void 0) {
|
|
274
|
+
acc.set(path, [message]);
|
|
275
|
+
} else if (!existing.includes(message)) {
|
|
276
|
+
existing.push(message);
|
|
277
|
+
}
|
|
278
|
+
return acc;
|
|
279
|
+
}, /* @__PURE__ */ new Map())
|
|
280
|
+
);
|
|
260
281
|
var validateSync = (schema, values) => {
|
|
261
282
|
const result = schema.safeParse(values);
|
|
262
283
|
return result.success ? { kind: "valid", data: result.data } : { kind: "invalid", errors: flattenIssues(result.error.issues) };
|
|
@@ -321,6 +342,34 @@ var hasChecks = (s) => {
|
|
|
321
342
|
const checks = s.def.checks;
|
|
322
343
|
return checks !== void 0 && checks.length > 0;
|
|
323
344
|
};
|
|
345
|
+
var fieldSchemaForIntrospection = (schema, path) => {
|
|
346
|
+
const childOf = (s, segment) => {
|
|
347
|
+
const inner = unwrapForWalk(s);
|
|
348
|
+
if (inner !== null) return childOf(inner, segment);
|
|
349
|
+
if (s instanceof zod.z.ZodObject && typeof segment === "string") {
|
|
350
|
+
const shape = s.shape;
|
|
351
|
+
return Object.hasOwn(shape, segment) ? shape[segment] ?? null : null;
|
|
352
|
+
}
|
|
353
|
+
if (s instanceof zod.z.ZodArray && typeof segment === "number") {
|
|
354
|
+
return s.element;
|
|
355
|
+
}
|
|
356
|
+
if (s instanceof zod.z.ZodTuple && typeof segment === "number") {
|
|
357
|
+
return s.def.items[segment] ?? null;
|
|
358
|
+
}
|
|
359
|
+
if (s instanceof zod.z.ZodDiscriminatedUnion || s instanceof zod.z.ZodUnion) {
|
|
360
|
+
const options = s.def.options;
|
|
361
|
+
return options.reduce(
|
|
362
|
+
(found, option) => found ?? childOf(option, segment),
|
|
363
|
+
null
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
};
|
|
368
|
+
return parsePath(path).reduce(
|
|
369
|
+
(current, segment) => current === null ? null : childOf(current, segment),
|
|
370
|
+
schema
|
|
371
|
+
);
|
|
372
|
+
};
|
|
324
373
|
var fieldSchemaAtPath = (schema, path) => parsePath(path).reduce((current, segment) => {
|
|
325
374
|
if (current === null || hasChecks(current)) return null;
|
|
326
375
|
if (current instanceof zod.z.ZodObject && typeof segment === "string") {
|
|
@@ -339,6 +388,12 @@ var emptyErrors = {};
|
|
|
339
388
|
var emptyBools = {};
|
|
340
389
|
var identityMapper = (n) => n;
|
|
341
390
|
var omitKey = (map, key) => Object.fromEntries(Object.entries(map).filter(([k]) => k !== key));
|
|
391
|
+
var omitKeys = (map, keys) => {
|
|
392
|
+
if (keys.size === 0) return map;
|
|
393
|
+
const entries = Object.entries(map);
|
|
394
|
+
const kept = entries.filter(([k]) => !keys.has(k));
|
|
395
|
+
return kept.length === entries.length ? map : Object.fromEntries(kept);
|
|
396
|
+
};
|
|
342
397
|
var inScopeOfAny = (key, paths) => paths.some((p) => isPathOrChild(key, p));
|
|
343
398
|
var omitScope = (map, paths) => {
|
|
344
399
|
const entries = Object.entries(map);
|
|
@@ -377,13 +432,13 @@ var stringArraysEqual = (a, b) => a === b || a.length === b.length && a.every((v
|
|
|
377
432
|
var reuseErrorRefs = (next, prev) => {
|
|
378
433
|
if (next === prev) return prev;
|
|
379
434
|
const entries = Object.entries(next).map(([k, v]) => {
|
|
380
|
-
const old = prev
|
|
435
|
+
const old = entryAt(prev, k);
|
|
381
436
|
return [
|
|
382
437
|
k,
|
|
383
438
|
old !== void 0 && stringArraysEqual(old, v) ? old : v
|
|
384
439
|
];
|
|
385
440
|
});
|
|
386
|
-
const sameShape = entries.length === Object.keys(prev).length && entries.every(([k, v]) => prev
|
|
441
|
+
const sameShape = entries.length === Object.keys(prev).length && entries.every(([k, v]) => entryAt(prev, k) === v);
|
|
387
442
|
return sameShape ? prev : Object.fromEntries(entries);
|
|
388
443
|
};
|
|
389
444
|
var scopeSettledResult = (result, path, viaSubschema) => result.kind !== "invalid" ? emptyErrors : viaSubschema ? prefixErrorKeys(result.errors, path) : path === "" ? result.errors : pickScope(result.errors, [path]);
|
|
@@ -440,9 +495,10 @@ var createForm = (schema, options) => {
|
|
|
440
495
|
// opting in during development must NOT stream every keystroke to
|
|
441
496
|
// an end user who happens to have the extension installed, where
|
|
442
497
|
// a module-singleton form's unbounded time-travel history is a
|
|
443
|
-
// real memory + serialization cost.
|
|
444
|
-
// without
|
|
445
|
-
|
|
498
|
+
// real memory + serialization cost. Fail-closed gate: a runtime
|
|
499
|
+
// without a readable NODE_ENV (CDN/ESM, Deno, a worker) reads as
|
|
500
|
+
// production here, unlike the console-warning gate.
|
|
501
|
+
enabled: isDevToolsBuild()
|
|
446
502
|
})
|
|
447
503
|
);
|
|
448
504
|
const commitFullPass = (state, next) => errorChannels(state, {
|
|
@@ -558,13 +614,19 @@ var createForm = (schema, options) => {
|
|
|
558
614
|
const slot = slotAtPath(values, path);
|
|
559
615
|
return slot.exists ? { kind: "parse", schema: sub, value: slot.value, viaSubschema: true } : { kind: "skip" };
|
|
560
616
|
};
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
);
|
|
617
|
+
const parseScopeAsync = async (path, scope) => scope.kind === "skip" ? emptyErrors : scopeSettledResult(
|
|
618
|
+
await validateAsync(scope.schema, scope.value),
|
|
619
|
+
path,
|
|
620
|
+
scope.viaSubschema
|
|
621
|
+
);
|
|
622
|
+
const scopeStale = (scope, path, valuesAtStart, live) => {
|
|
623
|
+
if (live === valuesAtStart) return false;
|
|
624
|
+
switch (scope.kind) {
|
|
625
|
+
case "skip":
|
|
626
|
+
return slotAtPath(live, path).exists;
|
|
627
|
+
case "parse":
|
|
628
|
+
return scope.viaSubschema ? !Object.is(getAtPath(live, path), scope.value) : true;
|
|
629
|
+
}
|
|
568
630
|
};
|
|
569
631
|
const fieldResult = (scoped) => {
|
|
570
632
|
const messages = Object.values(scoped).flat();
|
|
@@ -612,10 +674,10 @@ var createForm = (schema, options) => {
|
|
|
612
674
|
const value = getAtPath(state.values, path);
|
|
613
675
|
return {
|
|
614
676
|
value,
|
|
615
|
-
error: state.errors
|
|
616
|
-
touched: state.touched
|
|
677
|
+
error: entryAt(state.errors, path),
|
|
678
|
+
touched: flagAt(state.touched, path),
|
|
617
679
|
dirty: isFieldDirty(value, getAtPath(state.initialValues, path)),
|
|
618
|
-
isValidating: state.isValidating
|
|
680
|
+
isValidating: flagAt(state.isValidating, path)
|
|
619
681
|
};
|
|
620
682
|
};
|
|
621
683
|
const sequences = /* @__PURE__ */ new Map();
|
|
@@ -654,44 +716,66 @@ var createForm = (schema, options) => {
|
|
|
654
716
|
}
|
|
655
717
|
const passes = new Map(paths.map((p) => [p, claimPass(p)]));
|
|
656
718
|
const valuesAtStart = store.getState().values;
|
|
719
|
+
const scopes = paths.map((path) => ({
|
|
720
|
+
path,
|
|
721
|
+
scope: fieldScopeFor(path, valuesAtStart)
|
|
722
|
+
}));
|
|
723
|
+
const allViaSubschema = scopes.every(
|
|
724
|
+
({ scope }) => scope.kind === "skip" || scope.kind === "parse" && scope.viaSubschema
|
|
725
|
+
);
|
|
657
726
|
store.setState((state) => ({
|
|
658
727
|
...state,
|
|
659
|
-
isValidating:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
728
|
+
isValidating: {
|
|
729
|
+
...state.isValidating,
|
|
730
|
+
...Object.fromEntries(paths.map((p) => [p, true]))
|
|
731
|
+
}
|
|
663
732
|
}));
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
733
|
+
const parsed = allViaSubschema ? {
|
|
734
|
+
kind: "slots",
|
|
735
|
+
byPath: new Map(
|
|
736
|
+
await Promise.all(
|
|
737
|
+
scopes.map(
|
|
738
|
+
async ({ path, scope }) => [path, await parseScopeAsync(path, scope)]
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
)
|
|
742
|
+
} : {
|
|
743
|
+
kind: "full",
|
|
744
|
+
errors: await validateAsync(schema, valuesAtStart).then(
|
|
745
|
+
(result) => result.kind === "invalid" ? result.errors : emptyErrors
|
|
746
|
+
)
|
|
747
|
+
};
|
|
748
|
+
const errorsFor = (path) => {
|
|
749
|
+
switch (parsed.kind) {
|
|
750
|
+
case "slots":
|
|
751
|
+
return parsed.byPath.get(path) ?? emptyErrors;
|
|
752
|
+
case "full":
|
|
753
|
+
return pickScope(parsed.errors, [path]);
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const requestedErrors = Object.fromEntries(
|
|
757
|
+
paths.flatMap((path) => Object.entries(errorsFor(path)))
|
|
758
|
+
);
|
|
667
759
|
const requested = settledFields(requestedErrors);
|
|
668
760
|
const live = store.getState();
|
|
669
|
-
const
|
|
670
|
-
(
|
|
761
|
+
const owned = scopes.filter(
|
|
762
|
+
({ path }) => sequences.get(path) === passes.get(path)
|
|
671
763
|
);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
(acc, p) => omitKey(acc, p),
|
|
678
|
-
state.isValidating
|
|
679
|
-
)
|
|
680
|
-
}));
|
|
681
|
-
}
|
|
682
|
-
return requested;
|
|
683
|
-
}
|
|
764
|
+
const fresh = owned.filter(
|
|
765
|
+
({ path, scope }) => !scopeStale(scope, path, valuesAtStart, live.values)
|
|
766
|
+
).map(({ path }) => path);
|
|
767
|
+
const ownedKeys = new Set(owned.map(({ path }) => path));
|
|
768
|
+
if (owned.length === 0) return requested;
|
|
684
769
|
store.setState((state) => ({
|
|
685
770
|
...state,
|
|
686
|
-
...commitScopedErrors(
|
|
771
|
+
...fresh.length === 0 ? {} : commitScopedErrors(
|
|
687
772
|
state,
|
|
688
|
-
|
|
689
|
-
|
|
773
|
+
Object.fromEntries(
|
|
774
|
+
fresh.flatMap((path) => Object.entries(errorsFor(path)))
|
|
775
|
+
),
|
|
776
|
+
fresh
|
|
690
777
|
),
|
|
691
|
-
isValidating:
|
|
692
|
-
(acc, p) => omitKey(acc, p),
|
|
693
|
-
state.isValidating
|
|
694
|
-
)
|
|
778
|
+
isValidating: omitKeys(state.isValidating, ownedKeys)
|
|
695
779
|
}));
|
|
696
780
|
return requested;
|
|
697
781
|
};
|
|
@@ -705,14 +789,15 @@ var createForm = (schema, options) => {
|
|
|
705
789
|
}
|
|
706
790
|
const pass = claimPass(path);
|
|
707
791
|
const valuesAtStart = store.getState().values;
|
|
792
|
+
const scope = fieldScopeFor(path, valuesAtStart);
|
|
708
793
|
store.setState((state) => ({
|
|
709
794
|
...state,
|
|
710
795
|
isValidating: { ...state.isValidating, [path]: true }
|
|
711
796
|
}));
|
|
712
|
-
const scoped = await
|
|
797
|
+
const scoped = await parseScopeAsync(path, scope);
|
|
713
798
|
const live = store.getState();
|
|
714
799
|
const stillOwns = sequences.get(path) === pass;
|
|
715
|
-
if (!stillOwns || live.values
|
|
800
|
+
if (!stillOwns || scopeStale(scope, path, valuesAtStart, live.values)) {
|
|
716
801
|
if (stillOwns) {
|
|
717
802
|
store.setState((state) => ({
|
|
718
803
|
...state,
|
|
@@ -947,17 +1032,24 @@ var createForm = (schema, options) => {
|
|
|
947
1032
|
...state,
|
|
948
1033
|
...errorChannels(state, { serverErrors: errors })
|
|
949
1034
|
})),
|
|
950
|
-
addErrors: (errors) => store.setState((state) =>
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1035
|
+
addErrors: (errors) => store.setState((state) => {
|
|
1036
|
+
const entries = Object.entries(errors);
|
|
1037
|
+
const removed = new Set(
|
|
1038
|
+
entries.filter(([, list]) => list === void 0 || list.length === 0).map(([key]) => key)
|
|
1039
|
+
);
|
|
1040
|
+
const added = Object.fromEntries(
|
|
1041
|
+
entries.filter(([, list]) => list !== void 0 && list.length > 0)
|
|
1042
|
+
);
|
|
1043
|
+
return {
|
|
1044
|
+
...state,
|
|
1045
|
+
...errorChannels(state, {
|
|
1046
|
+
serverErrors: {
|
|
1047
|
+
...omitKeys(state.serverErrors, removed),
|
|
1048
|
+
...added
|
|
1049
|
+
}
|
|
1050
|
+
})
|
|
1051
|
+
};
|
|
1052
|
+
}),
|
|
961
1053
|
clearErrors: (path) => store.setState((state) => {
|
|
962
1054
|
const scrub = (map) => path === void 0 ? emptyErrors : omitScope(map, [path]);
|
|
963
1055
|
return {
|
|
@@ -981,11 +1073,12 @@ var createForm = (schema, options) => {
|
|
|
981
1073
|
if (next.values !== state.values) {
|
|
982
1074
|
clearArrayOpsUnder(store, "");
|
|
983
1075
|
}
|
|
1076
|
+
const serverErrors = patch.serverErrors !== void 0 ? patch.serverErrors : next.values !== state.values ? releaseChangedSlices(state.serverErrors, state.values, next.values) : state.serverErrors;
|
|
984
1077
|
store.setState(() => ({
|
|
985
1078
|
...next,
|
|
986
1079
|
...errorChannels(state, {
|
|
987
1080
|
schemaErrors: next.schemaErrors,
|
|
988
|
-
serverErrors
|
|
1081
|
+
serverErrors
|
|
989
1082
|
})
|
|
990
1083
|
}));
|
|
991
1084
|
},
|
|
@@ -1096,7 +1189,7 @@ var createForm = (schema, options) => {
|
|
|
1096
1189
|
handleSubmit: (onValid, onInvalid, opts) => async (event) => {
|
|
1097
1190
|
event?.preventDefault();
|
|
1098
1191
|
const result = await submit(onValid, onInvalid, opts);
|
|
1099
|
-
if (result.kind === "error" && opts?.onError === void 0 &&
|
|
1192
|
+
if (result.kind === "error" && opts?.onError === void 0 && isDevBuild()) {
|
|
1100
1193
|
console.warn(
|
|
1101
1194
|
"[formstand] submit handler threw and nothing observed it \u2014 the form shows no feedback. Pass { onError } to handleSubmit (or setError inside your handler):",
|
|
1102
1195
|
result.error
|
|
@@ -1224,7 +1317,7 @@ var persistForm = (form, options) => {
|
|
|
1224
1317
|
const storage = options.storage ?? defaultStorage();
|
|
1225
1318
|
const debounceMs = options.debounceMs ?? 300;
|
|
1226
1319
|
const apply = options.apply ?? "adopt";
|
|
1227
|
-
const applyDraft = (values) => apply === "restore" ? form.setValues(values) : form.adoptValues(values);
|
|
1320
|
+
const applyDraft = (values) => apply === "edit" || apply === "restore" ? form.setValues(values) : form.adoptValues(values);
|
|
1228
1321
|
const referenceShape = () => {
|
|
1229
1322
|
try {
|
|
1230
1323
|
return JSON.parse(JSON.stringify(form.getState().initialValues));
|
|
@@ -1246,6 +1339,7 @@ var persistForm = (form, options) => {
|
|
|
1246
1339
|
if (versioned && wrapper.__v !== options.version) return false;
|
|
1247
1340
|
if (versioned && wrapper.values === void 0) return false;
|
|
1248
1341
|
const values = versioned ? wrapper.values : parsed;
|
|
1342
|
+
if (values === null || values === void 0) return false;
|
|
1249
1343
|
const reference = referenceShape();
|
|
1250
1344
|
if (reference !== void 0 && conflictsWith(values, reference)) {
|
|
1251
1345
|
return false;
|
|
@@ -1455,7 +1549,7 @@ var useMaskedInput = (field, options) => {
|
|
|
1455
1549
|
field.setValue(parsed.value);
|
|
1456
1550
|
return;
|
|
1457
1551
|
case "empty": {
|
|
1458
|
-
const empty = field.emptyValue;
|
|
1552
|
+
const empty = field.clearable || typeof field.initialValue !== "string" ? field.emptyValue : "";
|
|
1459
1553
|
setEdit({ raw: text, pushed: empty });
|
|
1460
1554
|
field.setValue(empty);
|
|
1461
1555
|
return;
|
|
@@ -1503,9 +1597,14 @@ var useFormSteps = (form, steps) => {
|
|
|
1503
1597
|
},
|
|
1504
1598
|
[form]
|
|
1505
1599
|
);
|
|
1600
|
+
const currentStep = react.useRef(step);
|
|
1601
|
+
react.useEffect(() => {
|
|
1602
|
+
currentStep.current = step;
|
|
1603
|
+
});
|
|
1506
1604
|
const next = react.useCallback(async () => {
|
|
1507
1605
|
const ok = await validateStep(step);
|
|
1508
1606
|
if (!ok) return false;
|
|
1607
|
+
if (currentStep.current !== step) return false;
|
|
1509
1608
|
const target = Math.min(step + 1, latest.current.length - 1);
|
|
1510
1609
|
setStep(target);
|
|
1511
1610
|
setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
|
|
@@ -1527,6 +1626,7 @@ var useFormSteps = (form, steps) => {
|
|
|
1527
1626
|
return await validateStep(from) ? walk(from + 1) : from;
|
|
1528
1627
|
};
|
|
1529
1628
|
const landed = await walk(step);
|
|
1629
|
+
if (currentStep.current !== step) return currentStep.current;
|
|
1530
1630
|
setStep(landed);
|
|
1531
1631
|
setVisited(
|
|
1532
1632
|
(prev) => /* @__PURE__ */ new Set([
|
|
@@ -1572,7 +1672,7 @@ var useFormSteps = (form, steps) => {
|
|
|
1572
1672
|
);
|
|
1573
1673
|
};
|
|
1574
1674
|
var warnUnobserved = (result, options, site) => {
|
|
1575
|
-
if (result.kind === "error" && options?.onError === void 0 &&
|
|
1675
|
+
if (result.kind === "error" && options?.onError === void 0 && isDevBuild()) {
|
|
1576
1676
|
console.warn(
|
|
1577
1677
|
`[formstand] action handler threw and nothing observed it \u2014 the form shows no feedback. Pass { onError } to ${site} (or setError inside your handler):`,
|
|
1578
1678
|
result.error
|
|
@@ -1641,10 +1741,12 @@ function useField(form, pathArg, options) {
|
|
|
1641
1741
|
path: p,
|
|
1642
1742
|
value,
|
|
1643
1743
|
initialValue: initialValue2,
|
|
1644
|
-
|
|
1645
|
-
|
|
1744
|
+
// Own-key reads: a field named "constructor" or "toString" would
|
|
1745
|
+
// otherwise read Object.prototype's function as its error/flag.
|
|
1746
|
+
error: entryAt(state.errors, p),
|
|
1747
|
+
touched: flagAt(state.touched, p),
|
|
1646
1748
|
dirty: isFieldDirty(value, initialValue2),
|
|
1647
|
-
isValidating: state.isValidating
|
|
1749
|
+
isValidating: flagAt(state.isValidating, p)
|
|
1648
1750
|
};
|
|
1649
1751
|
})
|
|
1650
1752
|
);
|
|
@@ -1653,7 +1755,7 @@ function useField(form, pathArg, options) {
|
|
|
1653
1755
|
const initialValue = slice.initialValue;
|
|
1654
1756
|
const { emptyValue, clearable } = react.useMemo(() => {
|
|
1655
1757
|
if (formSchema !== void 0) {
|
|
1656
|
-
const sub =
|
|
1758
|
+
const sub = fieldSchemaForIntrospection(formSchema, path);
|
|
1657
1759
|
if (sub !== null) {
|
|
1658
1760
|
return {
|
|
1659
1761
|
emptyValue: emptyValueForSchema(sub),
|
|
@@ -1675,10 +1777,12 @@ function useField(form, pathArg, options) {
|
|
|
1675
1777
|
if (debounceMs !== void 0 && debounceMs > 0) {
|
|
1676
1778
|
const debouncer = debouncerRef.current ?? createDebouncer(debounceMs);
|
|
1677
1779
|
debouncerRef.current = debouncer;
|
|
1678
|
-
const
|
|
1780
|
+
const scheduledValues = form.store.getState().values;
|
|
1781
|
+
const scheduled = getAtPath(scheduledValues, path);
|
|
1679
1782
|
debouncer.schedule(() => {
|
|
1680
1783
|
const state = form.store.getState();
|
|
1681
|
-
|
|
1784
|
+
const rebased = state.values !== scheduledValues && state.values === state.initialValues;
|
|
1785
|
+
if (rebased) return;
|
|
1682
1786
|
if (!Object.is(getAtPath(state.values, path), scheduled)) return;
|
|
1683
1787
|
void form.validateFieldAsync(path);
|
|
1684
1788
|
});
|
|
@@ -1697,7 +1801,7 @@ function useField(form, pathArg, options) {
|
|
|
1697
1801
|
state.mode,
|
|
1698
1802
|
state.reValidateMode,
|
|
1699
1803
|
state.submitCount > 0,
|
|
1700
|
-
state.touched
|
|
1804
|
+
flagAt(state.touched, path)
|
|
1701
1805
|
)) {
|
|
1702
1806
|
triggerValidate();
|
|
1703
1807
|
}
|
|
@@ -1778,7 +1882,11 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1778
1882
|
if (prev.items.length === nextItems.length && prev.items.every((item, i) => Object.is(item, nextItems[i]))) {
|
|
1779
1883
|
return { ...prev, items: nextItems };
|
|
1780
1884
|
}
|
|
1885
|
+
const keptAtIndex = nextItems.map(
|
|
1886
|
+
(item, i) => i < prev.items.length && Object.is(item, prev.items[i])
|
|
1887
|
+
);
|
|
1781
1888
|
const buckets = prev.items.reduce((acc, item, i) => {
|
|
1889
|
+
if (keptAtIndex[i] === true) return acc;
|
|
1782
1890
|
const bucket = acc.get(item);
|
|
1783
1891
|
if (bucket === void 0) {
|
|
1784
1892
|
acc.set(item, [i]);
|
|
@@ -1787,7 +1895,8 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1787
1895
|
}
|
|
1788
1896
|
return acc;
|
|
1789
1897
|
}, /* @__PURE__ */ new Map());
|
|
1790
|
-
const matchedIds = nextItems.map((item) => {
|
|
1898
|
+
const matchedIds = nextItems.map((item, i) => {
|
|
1899
|
+
if (keptAtIndex[i] === true) return prev.ids[i] ?? null;
|
|
1791
1900
|
const prevIndex = buckets.get(item)?.shift();
|
|
1792
1901
|
return prevIndex === void 0 ? null : prev.ids[prevIndex] ?? null;
|
|
1793
1902
|
});
|
|
@@ -1828,10 +1937,13 @@ var mappedIdState = (prev, mapping, liveItems) => {
|
|
|
1828
1937
|
};
|
|
1829
1938
|
var MAX_ID_PATHS = 4096;
|
|
1830
1939
|
var sharedIdEntries = /* @__PURE__ */ new WeakMap();
|
|
1831
|
-
var
|
|
1832
|
-
const
|
|
1833
|
-
const forStore =
|
|
1834
|
-
if (
|
|
1940
|
+
var storeIdEntries = (store) => {
|
|
1941
|
+
const existing = sharedIdEntries.get(store);
|
|
1942
|
+
const forStore = existing ?? { byPath: /* @__PURE__ */ new Map(), seq: 1 };
|
|
1943
|
+
if (existing === void 0) sharedIdEntries.set(store, forStore);
|
|
1944
|
+
return forStore;
|
|
1945
|
+
};
|
|
1946
|
+
var sharedIdEntry = (forStore, path) => {
|
|
1835
1947
|
const existing = forStore.byPath.get(path);
|
|
1836
1948
|
if (existing !== void 0) {
|
|
1837
1949
|
forStore.byPath.delete(path);
|
|
@@ -1861,12 +1973,40 @@ var walkOps = (start, items, ops) => {
|
|
|
1861
1973
|
}, start);
|
|
1862
1974
|
return walked.items === items ? walked : reconcileIds(walked, items);
|
|
1863
1975
|
};
|
|
1976
|
+
var reseatDescendants = (forStore, path, prev, next) => {
|
|
1977
|
+
if (prev.ids.length === 0) return;
|
|
1978
|
+
const newIndexById = new Map(next.ids.map((id, i) => [id, i]));
|
|
1979
|
+
const moves = prev.ids.map((id) => newIndexById.get(id));
|
|
1980
|
+
if (moves.every((to, from) => to === from)) return;
|
|
1981
|
+
const prefix = `${path}.`;
|
|
1982
|
+
const descendants = [...forStore.byPath.keys()].filter(
|
|
1983
|
+
(key) => key.startsWith(prefix)
|
|
1984
|
+
);
|
|
1985
|
+
const relocations = descendants.flatMap((key) => {
|
|
1986
|
+
const rest = key.slice(prefix.length);
|
|
1987
|
+
const dot = rest.indexOf(".");
|
|
1988
|
+
const segment = dot === -1 ? rest : rest.slice(0, dot);
|
|
1989
|
+
const from = Number(segment);
|
|
1990
|
+
if (!/^\d+$/.test(segment) || from >= prev.ids.length) return [];
|
|
1991
|
+
const entry = forStore.byPath.get(key);
|
|
1992
|
+
if (entry === void 0) return [];
|
|
1993
|
+
const to = moves[from];
|
|
1994
|
+
const target = to === void 0 ? void 0 : `${prefix}${to}${dot === -1 ? "" : rest.slice(dot)}`;
|
|
1995
|
+
return [{ key, target, entry }];
|
|
1996
|
+
});
|
|
1997
|
+
relocations.forEach(({ key }) => forStore.byPath.delete(key));
|
|
1998
|
+
relocations.forEach(({ target, entry }) => {
|
|
1999
|
+
if (target !== void 0) forStore.byPath.set(target, entry);
|
|
2000
|
+
});
|
|
2001
|
+
};
|
|
1864
2002
|
var deriveIds = (store, path, items) => {
|
|
1865
|
-
const
|
|
2003
|
+
const forStore = storeIdEntries(store);
|
|
2004
|
+
const entry = sharedIdEntry(forStore, path);
|
|
1866
2005
|
if (entry.state.items === items) return entry.state;
|
|
1867
2006
|
const ops = arrayOpsFor(store, path);
|
|
1868
2007
|
if (ops.length > 0) clearArrayOps(store, path);
|
|
1869
2008
|
const next = walkOps(entry.state, items, ops);
|
|
2009
|
+
reseatDescendants(forStore, path, entry.state, next);
|
|
1870
2010
|
entry.state = next;
|
|
1871
2011
|
return next;
|
|
1872
2012
|
};
|
|
@@ -1881,7 +2021,9 @@ function useFieldArray(form, pathArg) {
|
|
|
1881
2021
|
const value = getAtPath(state.values, path);
|
|
1882
2022
|
return {
|
|
1883
2023
|
items: Array.isArray(value) ? value : EMPTY_ITEMS,
|
|
1884
|
-
|
|
2024
|
+
// Own-key read, like useField: an array named "constructor" must
|
|
2025
|
+
// not read Object as its error list.
|
|
2026
|
+
error: entryAt(state.errors, path)
|
|
1885
2027
|
};
|
|
1886
2028
|
})
|
|
1887
2029
|
);
|
|
@@ -1895,7 +2037,7 @@ function useFieldArray(form, pathArg) {
|
|
|
1895
2037
|
state.mode,
|
|
1896
2038
|
state.reValidateMode,
|
|
1897
2039
|
state.submitCount > 0,
|
|
1898
|
-
state.touched
|
|
2040
|
+
flagAt(state.touched, path)
|
|
1899
2041
|
)) {
|
|
1900
2042
|
form.validateField?.(path);
|
|
1901
2043
|
}
|