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/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
- return a.getTime() === b.getTime();
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) => issues.flatMap((issue) => expandIssue(issue, [])).reduce((acc, { path, message }) => {
257
- const existing = (Object.hasOwn(acc, path) ? acc[path] : void 0) ?? [];
258
- return existing.includes(message) ? acc : { ...acc, [path]: [...existing, message] };
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[k];
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[k] === v);
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. Guarded for environments
444
- // without process (a bundler defines NODE_ENV in real dev).
445
- enabled: typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production"
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 scopedFieldErrorsAsync = async (path, values) => {
562
- const scope = fieldScopeFor(path, values);
563
- return scope.kind === "skip" ? emptyErrors : scopeSettledResult(
564
- await validateAsync(scope.schema, scope.value),
565
- path,
566
- scope.viaSubschema
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[path],
616
- touched: state.touched[path] ?? false,
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[path] ?? false
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: paths.reduce(
660
- (acc, p) => ({ ...acc, [p]: true }),
661
- { ...state.isValidating }
662
- )
728
+ isValidating: {
729
+ ...state.isValidating,
730
+ ...Object.fromEntries(paths.map((p) => [p, true]))
731
+ }
663
732
  }));
664
- const result = await validateAsync(schema, valuesAtStart);
665
- const fullErrors = result.kind === "invalid" ? result.errors : emptyErrors;
666
- const requestedErrors = pickScope(fullErrors, paths);
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 stillCurrent = paths.filter(
670
- (p) => sequences.get(p) === passes.get(p)
761
+ const owned = scopes.filter(
762
+ ({ path }) => sequences.get(path) === passes.get(path)
671
763
  );
672
- if (live.values !== valuesAtStart) {
673
- if (stillCurrent.length > 0) {
674
- store.setState((state) => ({
675
- ...state,
676
- isValidating: stillCurrent.reduce(
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
- pickScope(fullErrors, stillCurrent),
689
- stillCurrent
773
+ Object.fromEntries(
774
+ fresh.flatMap((path) => Object.entries(errorsFor(path)))
775
+ ),
776
+ fresh
690
777
  ),
691
- isValidating: stillCurrent.reduce(
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 scopedFieldErrorsAsync(path, valuesAtStart);
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 !== valuesAtStart) {
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
- ...state,
952
- ...errorChannels(state, {
953
- serverErrors: Object.entries(errors).reduce(
954
- // Computed keys throughout, so a server field named
955
- // "__proto__" lands as an own property like everywhere else.
956
- (acc, [key, list]) => list === void 0 || list.length === 0 ? omitKey(acc, key) : { ...acc, [key]: list },
957
- state.serverErrors
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: next.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 && process.env.NODE_ENV !== "production") {
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 && process.env.NODE_ENV !== "production") {
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
- error: state.errors[p],
1645
- touched: state.touched[p] ?? false,
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[p] ?? false
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 = fieldSchemaAtPath(formSchema, path);
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 scheduled = getAtPath(form.store.getState().values, path);
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
- if (state.values === state.initialValues) return;
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[path] ?? false
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 sharedIdEntry = (store, path) => {
1832
- const existingStore = sharedIdEntries.get(store);
1833
- const forStore = existingStore ?? { byPath: /* @__PURE__ */ new Map(), seq: 1 };
1834
- if (existingStore === void 0) sharedIdEntries.set(store, forStore);
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 entry = sharedIdEntry(store, path);
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
- error: state.errors[path]
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[path] ?? false
2040
+ flagAt(state.touched, path)
1899
2041
  )) {
1900
2042
  form.validateField?.(path);
1901
2043
  }