formstand 0.21.0 → 0.22.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/CHANGELOG.md +259 -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 +187 -76
- 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 +176 -79
- 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 +1 -1
- package/dist/chunk-MQ6262S3.js.map +0 -1
- package/dist/chunk-OB23TUKY.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getAtPath, slotAtPath, setAtPath, isPlainObject, hasOwnSlot, valuesEqual, useFormSelectorShallow, isFieldDirty, useFormSelector, parsePath } from './chunk-
|
|
2
|
-
export { getAtPath, parsePath, setAtPath, useFormSelector, useFormSelectorShallow } from './chunk-
|
|
3
|
-
import { shouldValidateOn } from './chunk-
|
|
4
|
-
export { shouldValidateOn } from './chunk-
|
|
1
|
+
import { isDevToolsBuild, getAtPath, isDevBuild, slotAtPath, setAtPath, isPlainObject, hasOwnSlot, valuesEqual, useFormSelectorShallow, isFieldDirty, useFormSelector, parsePath } from './chunk-BMXIZEI4.js';
|
|
2
|
+
export { getAtPath, parsePath, setAtPath, useFormSelector, useFormSelectorShallow } from './chunk-BMXIZEI4.js';
|
|
3
|
+
import { flagAt, entryAt, shouldValidateOn } from './chunk-V3PDMTPP.js';
|
|
4
|
+
export { shouldValidateOn } from './chunk-V3PDMTPP.js';
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { devtools } from 'zustand/middleware';
|
|
7
7
|
import { z } from 'zod';
|
|
@@ -114,10 +114,17 @@ var expandIssue = (issue, prefix) => {
|
|
|
114
114
|
}
|
|
115
115
|
return [{ path: issuePathToFormPath(at), message: issue.message }];
|
|
116
116
|
};
|
|
117
|
-
var flattenIssues = (issues) =>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
var flattenIssues = (issues) => Object.fromEntries(
|
|
118
|
+
issues.flatMap((issue) => expandIssue(issue, [])).reduce((acc, { path, message }) => {
|
|
119
|
+
const existing = acc.get(path);
|
|
120
|
+
if (existing === void 0) {
|
|
121
|
+
acc.set(path, [message]);
|
|
122
|
+
} else if (!existing.includes(message)) {
|
|
123
|
+
existing.push(message);
|
|
124
|
+
}
|
|
125
|
+
return acc;
|
|
126
|
+
}, /* @__PURE__ */ new Map())
|
|
127
|
+
);
|
|
121
128
|
var validateSync = (schema, values) => {
|
|
122
129
|
const result = schema.safeParse(values);
|
|
123
130
|
return result.success ? { kind: "valid", data: result.data } : { kind: "invalid", errors: flattenIssues(result.error.issues) };
|
|
@@ -182,6 +189,34 @@ var hasChecks = (s) => {
|
|
|
182
189
|
const checks = s.def.checks;
|
|
183
190
|
return checks !== void 0 && checks.length > 0;
|
|
184
191
|
};
|
|
192
|
+
var fieldSchemaForIntrospection = (schema, path) => {
|
|
193
|
+
const childOf = (s, segment) => {
|
|
194
|
+
const inner = unwrapForWalk(s);
|
|
195
|
+
if (inner !== null) return childOf(inner, segment);
|
|
196
|
+
if (s instanceof z.ZodObject && typeof segment === "string") {
|
|
197
|
+
const shape = s.shape;
|
|
198
|
+
return Object.hasOwn(shape, segment) ? shape[segment] ?? null : null;
|
|
199
|
+
}
|
|
200
|
+
if (s instanceof z.ZodArray && typeof segment === "number") {
|
|
201
|
+
return s.element;
|
|
202
|
+
}
|
|
203
|
+
if (s instanceof z.ZodTuple && typeof segment === "number") {
|
|
204
|
+
return s.def.items[segment] ?? null;
|
|
205
|
+
}
|
|
206
|
+
if (s instanceof z.ZodDiscriminatedUnion || s instanceof z.ZodUnion) {
|
|
207
|
+
const options = s.def.options;
|
|
208
|
+
return options.reduce(
|
|
209
|
+
(found, option) => found ?? childOf(option, segment),
|
|
210
|
+
null
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
};
|
|
215
|
+
return parsePath(path).reduce(
|
|
216
|
+
(current, segment) => current === null ? null : childOf(current, segment),
|
|
217
|
+
schema
|
|
218
|
+
);
|
|
219
|
+
};
|
|
185
220
|
var fieldSchemaAtPath = (schema, path) => parsePath(path).reduce((current, segment) => {
|
|
186
221
|
if (current === null || hasChecks(current)) return null;
|
|
187
222
|
if (current instanceof z.ZodObject && typeof segment === "string") {
|
|
@@ -200,6 +235,12 @@ var emptyErrors = {};
|
|
|
200
235
|
var emptyBools = {};
|
|
201
236
|
var identityMapper = (n) => n;
|
|
202
237
|
var omitKey = (map, key) => Object.fromEntries(Object.entries(map).filter(([k]) => k !== key));
|
|
238
|
+
var omitKeys = (map, keys) => {
|
|
239
|
+
if (keys.size === 0) return map;
|
|
240
|
+
const entries = Object.entries(map);
|
|
241
|
+
const kept = entries.filter(([k]) => !keys.has(k));
|
|
242
|
+
return kept.length === entries.length ? map : Object.fromEntries(kept);
|
|
243
|
+
};
|
|
203
244
|
var inScopeOfAny = (key, paths) => paths.some((p) => isPathOrChild(key, p));
|
|
204
245
|
var omitScope = (map, paths) => {
|
|
205
246
|
const entries = Object.entries(map);
|
|
@@ -238,13 +279,13 @@ var stringArraysEqual = (a, b) => a === b || a.length === b.length && a.every((v
|
|
|
238
279
|
var reuseErrorRefs = (next, prev) => {
|
|
239
280
|
if (next === prev) return prev;
|
|
240
281
|
const entries = Object.entries(next).map(([k, v]) => {
|
|
241
|
-
const old = prev
|
|
282
|
+
const old = entryAt(prev, k);
|
|
242
283
|
return [
|
|
243
284
|
k,
|
|
244
285
|
old !== void 0 && stringArraysEqual(old, v) ? old : v
|
|
245
286
|
];
|
|
246
287
|
});
|
|
247
|
-
const sameShape = entries.length === Object.keys(prev).length && entries.every(([k, v]) => prev
|
|
288
|
+
const sameShape = entries.length === Object.keys(prev).length && entries.every(([k, v]) => entryAt(prev, k) === v);
|
|
248
289
|
return sameShape ? prev : Object.fromEntries(entries);
|
|
249
290
|
};
|
|
250
291
|
var scopeSettledResult = (result, path, viaSubschema) => result.kind !== "invalid" ? emptyErrors : viaSubschema ? prefixErrorKeys(result.errors, path) : path === "" ? result.errors : pickScope(result.errors, [path]);
|
|
@@ -301,9 +342,10 @@ var createForm = (schema, options) => {
|
|
|
301
342
|
// opting in during development must NOT stream every keystroke to
|
|
302
343
|
// an end user who happens to have the extension installed, where
|
|
303
344
|
// a module-singleton form's unbounded time-travel history is a
|
|
304
|
-
// real memory + serialization cost.
|
|
305
|
-
// without
|
|
306
|
-
|
|
345
|
+
// real memory + serialization cost. Fail-closed gate: a runtime
|
|
346
|
+
// without a readable NODE_ENV (CDN/ESM, Deno, a worker) reads as
|
|
347
|
+
// production here, unlike the console-warning gate.
|
|
348
|
+
enabled: isDevToolsBuild()
|
|
307
349
|
})
|
|
308
350
|
);
|
|
309
351
|
const commitFullPass = (state, next) => errorChannels(state, {
|
|
@@ -419,13 +461,19 @@ var createForm = (schema, options) => {
|
|
|
419
461
|
const slot = slotAtPath(values, path);
|
|
420
462
|
return slot.exists ? { kind: "parse", schema: sub, value: slot.value, viaSubschema: true } : { kind: "skip" };
|
|
421
463
|
};
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
);
|
|
464
|
+
const parseScopeAsync = async (path, scope) => scope.kind === "skip" ? emptyErrors : scopeSettledResult(
|
|
465
|
+
await validateAsync(scope.schema, scope.value),
|
|
466
|
+
path,
|
|
467
|
+
scope.viaSubschema
|
|
468
|
+
);
|
|
469
|
+
const scopeStale = (scope, path, valuesAtStart, live) => {
|
|
470
|
+
if (live === valuesAtStart) return false;
|
|
471
|
+
switch (scope.kind) {
|
|
472
|
+
case "skip":
|
|
473
|
+
return slotAtPath(live, path).exists;
|
|
474
|
+
case "parse":
|
|
475
|
+
return scope.viaSubschema ? !Object.is(getAtPath(live, path), scope.value) : true;
|
|
476
|
+
}
|
|
429
477
|
};
|
|
430
478
|
const fieldResult = (scoped) => {
|
|
431
479
|
const messages = Object.values(scoped).flat();
|
|
@@ -473,10 +521,10 @@ var createForm = (schema, options) => {
|
|
|
473
521
|
const value = getAtPath(state.values, path);
|
|
474
522
|
return {
|
|
475
523
|
value,
|
|
476
|
-
error: state.errors
|
|
477
|
-
touched: state.touched
|
|
524
|
+
error: entryAt(state.errors, path),
|
|
525
|
+
touched: flagAt(state.touched, path),
|
|
478
526
|
dirty: isFieldDirty(value, getAtPath(state.initialValues, path)),
|
|
479
|
-
isValidating: state.isValidating
|
|
527
|
+
isValidating: flagAt(state.isValidating, path)
|
|
480
528
|
};
|
|
481
529
|
};
|
|
482
530
|
const sequences = /* @__PURE__ */ new Map();
|
|
@@ -515,44 +563,66 @@ var createForm = (schema, options) => {
|
|
|
515
563
|
}
|
|
516
564
|
const passes = new Map(paths.map((p) => [p, claimPass(p)]));
|
|
517
565
|
const valuesAtStart = store.getState().values;
|
|
566
|
+
const scopes = paths.map((path) => ({
|
|
567
|
+
path,
|
|
568
|
+
scope: fieldScopeFor(path, valuesAtStart)
|
|
569
|
+
}));
|
|
570
|
+
const allViaSubschema = scopes.every(
|
|
571
|
+
({ scope }) => scope.kind === "skip" || scope.kind === "parse" && scope.viaSubschema
|
|
572
|
+
);
|
|
518
573
|
store.setState((state) => ({
|
|
519
574
|
...state,
|
|
520
|
-
isValidating:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
575
|
+
isValidating: {
|
|
576
|
+
...state.isValidating,
|
|
577
|
+
...Object.fromEntries(paths.map((p) => [p, true]))
|
|
578
|
+
}
|
|
524
579
|
}));
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
580
|
+
const parsed = allViaSubschema ? {
|
|
581
|
+
kind: "slots",
|
|
582
|
+
byPath: new Map(
|
|
583
|
+
await Promise.all(
|
|
584
|
+
scopes.map(
|
|
585
|
+
async ({ path, scope }) => [path, await parseScopeAsync(path, scope)]
|
|
586
|
+
)
|
|
587
|
+
)
|
|
588
|
+
)
|
|
589
|
+
} : {
|
|
590
|
+
kind: "full",
|
|
591
|
+
errors: await validateAsync(schema, valuesAtStart).then(
|
|
592
|
+
(result) => result.kind === "invalid" ? result.errors : emptyErrors
|
|
593
|
+
)
|
|
594
|
+
};
|
|
595
|
+
const errorsFor = (path) => {
|
|
596
|
+
switch (parsed.kind) {
|
|
597
|
+
case "slots":
|
|
598
|
+
return parsed.byPath.get(path) ?? emptyErrors;
|
|
599
|
+
case "full":
|
|
600
|
+
return pickScope(parsed.errors, [path]);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const requestedErrors = Object.fromEntries(
|
|
604
|
+
paths.flatMap((path) => Object.entries(errorsFor(path)))
|
|
605
|
+
);
|
|
528
606
|
const requested = settledFields(requestedErrors);
|
|
529
607
|
const live = store.getState();
|
|
530
|
-
const
|
|
531
|
-
(
|
|
608
|
+
const owned = scopes.filter(
|
|
609
|
+
({ path }) => sequences.get(path) === passes.get(path)
|
|
532
610
|
);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
(acc, p) => omitKey(acc, p),
|
|
539
|
-
state.isValidating
|
|
540
|
-
)
|
|
541
|
-
}));
|
|
542
|
-
}
|
|
543
|
-
return requested;
|
|
544
|
-
}
|
|
611
|
+
const fresh = owned.filter(
|
|
612
|
+
({ path, scope }) => !scopeStale(scope, path, valuesAtStart, live.values)
|
|
613
|
+
).map(({ path }) => path);
|
|
614
|
+
const ownedKeys = new Set(owned.map(({ path }) => path));
|
|
615
|
+
if (owned.length === 0) return requested;
|
|
545
616
|
store.setState((state) => ({
|
|
546
617
|
...state,
|
|
547
|
-
...commitScopedErrors(
|
|
618
|
+
...fresh.length === 0 ? {} : commitScopedErrors(
|
|
548
619
|
state,
|
|
549
|
-
|
|
550
|
-
|
|
620
|
+
Object.fromEntries(
|
|
621
|
+
fresh.flatMap((path) => Object.entries(errorsFor(path)))
|
|
622
|
+
),
|
|
623
|
+
fresh
|
|
551
624
|
),
|
|
552
|
-
isValidating:
|
|
553
|
-
(acc, p) => omitKey(acc, p),
|
|
554
|
-
state.isValidating
|
|
555
|
-
)
|
|
625
|
+
isValidating: omitKeys(state.isValidating, ownedKeys)
|
|
556
626
|
}));
|
|
557
627
|
return requested;
|
|
558
628
|
};
|
|
@@ -566,14 +636,15 @@ var createForm = (schema, options) => {
|
|
|
566
636
|
}
|
|
567
637
|
const pass = claimPass(path);
|
|
568
638
|
const valuesAtStart = store.getState().values;
|
|
639
|
+
const scope = fieldScopeFor(path, valuesAtStart);
|
|
569
640
|
store.setState((state) => ({
|
|
570
641
|
...state,
|
|
571
642
|
isValidating: { ...state.isValidating, [path]: true }
|
|
572
643
|
}));
|
|
573
|
-
const scoped = await
|
|
644
|
+
const scoped = await parseScopeAsync(path, scope);
|
|
574
645
|
const live = store.getState();
|
|
575
646
|
const stillOwns = sequences.get(path) === pass;
|
|
576
|
-
if (!stillOwns || live.values
|
|
647
|
+
if (!stillOwns || scopeStale(scope, path, valuesAtStart, live.values)) {
|
|
577
648
|
if (stillOwns) {
|
|
578
649
|
store.setState((state) => ({
|
|
579
650
|
...state,
|
|
@@ -808,17 +879,24 @@ var createForm = (schema, options) => {
|
|
|
808
879
|
...state,
|
|
809
880
|
...errorChannels(state, { serverErrors: errors })
|
|
810
881
|
})),
|
|
811
|
-
addErrors: (errors) => store.setState((state) =>
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
882
|
+
addErrors: (errors) => store.setState((state) => {
|
|
883
|
+
const entries = Object.entries(errors);
|
|
884
|
+
const removed = new Set(
|
|
885
|
+
entries.filter(([, list]) => list === void 0 || list.length === 0).map(([key]) => key)
|
|
886
|
+
);
|
|
887
|
+
const added = Object.fromEntries(
|
|
888
|
+
entries.filter(([, list]) => list !== void 0 && list.length > 0)
|
|
889
|
+
);
|
|
890
|
+
return {
|
|
891
|
+
...state,
|
|
892
|
+
...errorChannels(state, {
|
|
893
|
+
serverErrors: {
|
|
894
|
+
...omitKeys(state.serverErrors, removed),
|
|
895
|
+
...added
|
|
896
|
+
}
|
|
897
|
+
})
|
|
898
|
+
};
|
|
899
|
+
}),
|
|
822
900
|
clearErrors: (path) => store.setState((state) => {
|
|
823
901
|
const scrub = (map) => path === void 0 ? emptyErrors : omitScope(map, [path]);
|
|
824
902
|
return {
|
|
@@ -842,11 +920,12 @@ var createForm = (schema, options) => {
|
|
|
842
920
|
if (next.values !== state.values) {
|
|
843
921
|
clearArrayOpsUnder(store, "");
|
|
844
922
|
}
|
|
923
|
+
const serverErrors = patch.serverErrors !== void 0 ? patch.serverErrors : next.values !== state.values ? releaseChangedSlices(state.serverErrors, state.values, next.values) : state.serverErrors;
|
|
845
924
|
store.setState(() => ({
|
|
846
925
|
...next,
|
|
847
926
|
...errorChannels(state, {
|
|
848
927
|
schemaErrors: next.schemaErrors,
|
|
849
|
-
serverErrors
|
|
928
|
+
serverErrors
|
|
850
929
|
})
|
|
851
930
|
}));
|
|
852
931
|
},
|
|
@@ -957,7 +1036,7 @@ var createForm = (schema, options) => {
|
|
|
957
1036
|
handleSubmit: (onValid, onInvalid, opts) => async (event) => {
|
|
958
1037
|
event?.preventDefault();
|
|
959
1038
|
const result = await submit(onValid, onInvalid, opts);
|
|
960
|
-
if (result.kind === "error" && opts?.onError === void 0 &&
|
|
1039
|
+
if (result.kind === "error" && opts?.onError === void 0 && isDevBuild()) {
|
|
961
1040
|
console.warn(
|
|
962
1041
|
"[formstand] submit handler threw and nothing observed it \u2014 the form shows no feedback. Pass { onError } to handleSubmit (or setError inside your handler):",
|
|
963
1042
|
result.error
|
|
@@ -1068,7 +1147,7 @@ var persistForm = (form, options) => {
|
|
|
1068
1147
|
const storage = options.storage ?? defaultStorage();
|
|
1069
1148
|
const debounceMs = options.debounceMs ?? 300;
|
|
1070
1149
|
const apply = options.apply ?? "adopt";
|
|
1071
|
-
const applyDraft = (values) => apply === "restore" ? form.setValues(values) : form.adoptValues(values);
|
|
1150
|
+
const applyDraft = (values) => apply === "edit" || apply === "restore" ? form.setValues(values) : form.adoptValues(values);
|
|
1072
1151
|
const referenceShape = () => {
|
|
1073
1152
|
try {
|
|
1074
1153
|
return JSON.parse(JSON.stringify(form.getState().initialValues));
|
|
@@ -1090,6 +1169,7 @@ var persistForm = (form, options) => {
|
|
|
1090
1169
|
if (versioned && wrapper.__v !== options.version) return false;
|
|
1091
1170
|
if (versioned && wrapper.values === void 0) return false;
|
|
1092
1171
|
const values = versioned ? wrapper.values : parsed;
|
|
1172
|
+
if (values === null || values === void 0) return false;
|
|
1093
1173
|
const reference = referenceShape();
|
|
1094
1174
|
if (reference !== void 0 && conflictsWith(values, reference)) {
|
|
1095
1175
|
return false;
|
|
@@ -1299,7 +1379,7 @@ var useMaskedInput = (field, options) => {
|
|
|
1299
1379
|
field.setValue(parsed.value);
|
|
1300
1380
|
return;
|
|
1301
1381
|
case "empty": {
|
|
1302
|
-
const empty = field.emptyValue;
|
|
1382
|
+
const empty = field.clearable || typeof field.initialValue !== "string" ? field.emptyValue : "";
|
|
1303
1383
|
setEdit({ raw: text, pushed: empty });
|
|
1304
1384
|
field.setValue(empty);
|
|
1305
1385
|
return;
|
|
@@ -1339,9 +1419,14 @@ var useFormSteps = (form, steps) => {
|
|
|
1339
1419
|
},
|
|
1340
1420
|
[form]
|
|
1341
1421
|
);
|
|
1422
|
+
const currentStep = useRef(step);
|
|
1423
|
+
useEffect(() => {
|
|
1424
|
+
currentStep.current = step;
|
|
1425
|
+
});
|
|
1342
1426
|
const next = useCallback(async () => {
|
|
1343
1427
|
const ok = await validateStep(step);
|
|
1344
1428
|
if (!ok) return false;
|
|
1429
|
+
if (currentStep.current !== step) return false;
|
|
1345
1430
|
const target = Math.min(step + 1, latest.current.length - 1);
|
|
1346
1431
|
setStep(target);
|
|
1347
1432
|
setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
|
|
@@ -1363,6 +1448,7 @@ var useFormSteps = (form, steps) => {
|
|
|
1363
1448
|
return await validateStep(from) ? walk(from + 1) : from;
|
|
1364
1449
|
};
|
|
1365
1450
|
const landed = await walk(step);
|
|
1451
|
+
if (currentStep.current !== step) return currentStep.current;
|
|
1366
1452
|
setStep(landed);
|
|
1367
1453
|
setVisited(
|
|
1368
1454
|
(prev) => /* @__PURE__ */ new Set([
|
|
@@ -1408,7 +1494,7 @@ var useFormSteps = (form, steps) => {
|
|
|
1408
1494
|
);
|
|
1409
1495
|
};
|
|
1410
1496
|
var warnUnobserved = (result, options, site) => {
|
|
1411
|
-
if (result.kind === "error" && options?.onError === void 0 &&
|
|
1497
|
+
if (result.kind === "error" && options?.onError === void 0 && isDevBuild()) {
|
|
1412
1498
|
console.warn(
|
|
1413
1499
|
`[formstand] action handler threw and nothing observed it \u2014 the form shows no feedback. Pass { onError } to ${site} (or setError inside your handler):`,
|
|
1414
1500
|
result.error
|
|
@@ -1477,10 +1563,12 @@ function useField(form, pathArg, options) {
|
|
|
1477
1563
|
path: p,
|
|
1478
1564
|
value,
|
|
1479
1565
|
initialValue: initialValue2,
|
|
1480
|
-
|
|
1481
|
-
|
|
1566
|
+
// Own-key reads: a field named "constructor" or "toString" would
|
|
1567
|
+
// otherwise read Object.prototype's function as its error/flag.
|
|
1568
|
+
error: entryAt(state.errors, p),
|
|
1569
|
+
touched: flagAt(state.touched, p),
|
|
1482
1570
|
dirty: isFieldDirty(value, initialValue2),
|
|
1483
|
-
isValidating: state.isValidating
|
|
1571
|
+
isValidating: flagAt(state.isValidating, p)
|
|
1484
1572
|
};
|
|
1485
1573
|
})
|
|
1486
1574
|
);
|
|
@@ -1489,7 +1577,7 @@ function useField(form, pathArg, options) {
|
|
|
1489
1577
|
const initialValue = slice.initialValue;
|
|
1490
1578
|
const { emptyValue, clearable } = useMemo(() => {
|
|
1491
1579
|
if (formSchema !== void 0) {
|
|
1492
|
-
const sub =
|
|
1580
|
+
const sub = fieldSchemaForIntrospection(formSchema, path);
|
|
1493
1581
|
if (sub !== null) {
|
|
1494
1582
|
return {
|
|
1495
1583
|
emptyValue: emptyValueForSchema(sub),
|
|
@@ -1511,10 +1599,12 @@ function useField(form, pathArg, options) {
|
|
|
1511
1599
|
if (debounceMs !== void 0 && debounceMs > 0) {
|
|
1512
1600
|
const debouncer = debouncerRef.current ?? createDebouncer(debounceMs);
|
|
1513
1601
|
debouncerRef.current = debouncer;
|
|
1514
|
-
const
|
|
1602
|
+
const scheduledValues = form.store.getState().values;
|
|
1603
|
+
const scheduled = getAtPath(scheduledValues, path);
|
|
1515
1604
|
debouncer.schedule(() => {
|
|
1516
1605
|
const state = form.store.getState();
|
|
1517
|
-
|
|
1606
|
+
const rebased = state.values !== scheduledValues && state.values === state.initialValues;
|
|
1607
|
+
if (rebased) return;
|
|
1518
1608
|
if (!Object.is(getAtPath(state.values, path), scheduled)) return;
|
|
1519
1609
|
void form.validateFieldAsync(path);
|
|
1520
1610
|
});
|
|
@@ -1533,7 +1623,7 @@ function useField(form, pathArg, options) {
|
|
|
1533
1623
|
state.mode,
|
|
1534
1624
|
state.reValidateMode,
|
|
1535
1625
|
state.submitCount > 0,
|
|
1536
|
-
state.touched
|
|
1626
|
+
flagAt(state.touched, path)
|
|
1537
1627
|
)) {
|
|
1538
1628
|
triggerValidate();
|
|
1539
1629
|
}
|
|
@@ -1614,7 +1704,11 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1614
1704
|
if (prev.items.length === nextItems.length && prev.items.every((item, i) => Object.is(item, nextItems[i]))) {
|
|
1615
1705
|
return { ...prev, items: nextItems };
|
|
1616
1706
|
}
|
|
1707
|
+
const keptAtIndex = nextItems.map(
|
|
1708
|
+
(item, i) => i < prev.items.length && Object.is(item, prev.items[i])
|
|
1709
|
+
);
|
|
1617
1710
|
const buckets = prev.items.reduce((acc, item, i) => {
|
|
1711
|
+
if (keptAtIndex[i] === true) return acc;
|
|
1618
1712
|
const bucket = acc.get(item);
|
|
1619
1713
|
if (bucket === void 0) {
|
|
1620
1714
|
acc.set(item, [i]);
|
|
@@ -1623,7 +1717,8 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1623
1717
|
}
|
|
1624
1718
|
return acc;
|
|
1625
1719
|
}, /* @__PURE__ */ new Map());
|
|
1626
|
-
const matchedIds = nextItems.map((item) => {
|
|
1720
|
+
const matchedIds = nextItems.map((item, i) => {
|
|
1721
|
+
if (keptAtIndex[i] === true) return prev.ids[i] ?? null;
|
|
1627
1722
|
const prevIndex = buckets.get(item)?.shift();
|
|
1628
1723
|
return prevIndex === void 0 ? null : prev.ids[prevIndex] ?? null;
|
|
1629
1724
|
});
|
|
@@ -1717,7 +1812,9 @@ function useFieldArray(form, pathArg) {
|
|
|
1717
1812
|
const value = getAtPath(state.values, path);
|
|
1718
1813
|
return {
|
|
1719
1814
|
items: Array.isArray(value) ? value : EMPTY_ITEMS,
|
|
1720
|
-
|
|
1815
|
+
// Own-key read, like useField: an array named "constructor" must
|
|
1816
|
+
// not read Object as its error list.
|
|
1817
|
+
error: entryAt(state.errors, path)
|
|
1721
1818
|
};
|
|
1722
1819
|
})
|
|
1723
1820
|
);
|
|
@@ -1731,7 +1828,7 @@ function useFieldArray(form, pathArg) {
|
|
|
1731
1828
|
state.mode,
|
|
1732
1829
|
state.reValidateMode,
|
|
1733
1830
|
state.submitCount > 0,
|
|
1734
|
-
state.touched
|
|
1831
|
+
flagAt(state.touched, path)
|
|
1735
1832
|
)) {
|
|
1736
1833
|
form.validateField?.(path);
|
|
1737
1834
|
}
|