formstand 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createStore } from 'zustand/vanilla';
2
+ import { devtools } from 'zustand/middleware';
2
3
  import { z } from 'zod';
3
4
  import { useRef, useEffect, createContext, useMemo, useCallback, useState, useId, useContext } from 'react';
4
5
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -57,7 +58,7 @@ var valuesEqual = (a, b) => {
57
58
  if (isPlainObject(a) && isPlainObject(b)) {
58
59
  const aKeys = Object.keys(a);
59
60
  const bKeys = Object.keys(b);
60
- return aKeys.length === bKeys.length && aKeys.every((k) => valuesEqual(a[k], b[k]));
61
+ return aKeys.length === bKeys.length && aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]));
61
62
  }
62
63
  return false;
63
64
  };
@@ -85,21 +86,33 @@ var isIndexSegment = (s) => {
85
86
  const n = Number(s);
86
87
  return Number.isInteger(n) && n >= 0 && String(n) === s;
87
88
  };
88
- var parsePath = (path) => path === "" ? [] : path.split(".").map((s) => isIndexSegment(s) ? Number(s) : s);
89
+ var PARSE_CACHE_MAX = 4096;
90
+ var parseCache = /* @__PURE__ */ new Map();
91
+ var parsePath = (path) => {
92
+ const cached = parseCache.get(path);
93
+ if (cached !== void 0) return cached;
94
+ const segments = Object.freeze(
95
+ path === "" ? [] : path.split(".").map((s) => isIndexSegment(s) ? Number(s) : s)
96
+ );
97
+ if (parseCache.size >= PARSE_CACHE_MAX) parseCache.clear();
98
+ parseCache.set(path, segments);
99
+ return segments;
100
+ };
89
101
  var isPlainRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
90
102
  var readSegment = (obj, segment) => {
91
103
  if (obj == null) return void 0;
92
104
  if (Array.isArray(obj)) {
93
105
  return typeof segment === "number" ? obj[segment] : void 0;
94
106
  }
95
- return isPlainRecord(obj) ? obj[String(segment)] : void 0;
107
+ return isPlainRecord(obj) && Object.hasOwn(obj, String(segment)) ? obj[String(segment)] : void 0;
96
108
  };
97
109
  var getAtPath = (obj, path) => parsePath(path).reduce(readSegment, obj);
98
110
  var slotAtPath = (obj, path) => {
99
111
  const walked = parsePath(path).reduce(
100
112
  (acc, segment) => {
101
- const outOfBounds = typeof segment === "number" && (Array.isArray(acc.current) ? segment >= acc.current.length : !isPlainRecord(acc.current));
102
- return !acc.ok || outOfBounds ? { ok: false, current: void 0 } : { ok: true, current: readSegment(acc.current, segment) };
113
+ const container = acc.current;
114
+ const addressable = typeof segment === "number" ? Array.isArray(container) ? segment < container.length : isPlainRecord(container) : isPlainRecord(container);
115
+ return !acc.ok || !addressable ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
103
116
  },
104
117
  { ok: true, current: obj }
105
118
  );
@@ -135,6 +148,12 @@ var writeSegments = (obj, segments, value) => {
135
148
  return writeIntoArray(obj, head, rest, value);
136
149
  }
137
150
  if (isPlainRecord(obj)) {
151
+ if (!isPlainObject(obj)) {
152
+ console.warn(
153
+ `[formstand] cannot write key "${String(head)}" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`
154
+ );
155
+ return obj;
156
+ }
138
157
  const key = String(head);
139
158
  return { ...obj, [key]: writeSegments(obj[key], rest, value) };
140
159
  }
@@ -257,7 +276,7 @@ var releaseAncestorEntries = (map, path) => {
257
276
  return kept.length === entries.length ? map : Object.fromEntries(kept);
258
277
  };
259
278
  var dirtyPathsOf = (values, initial, prefix) => {
260
- if (valuesEqual(values, initial)) return [];
279
+ if (Object.is(values, initial)) return [];
261
280
  if (isPlainObject(values) && isPlainObject(initial)) {
262
281
  const keys = /* @__PURE__ */ new Set([...Object.keys(values), ...Object.keys(initial)]);
263
282
  const childPaths = [...keys].flatMap(
@@ -267,9 +286,11 @@ var dirtyPathsOf = (values, initial, prefix) => {
267
286
  prefix === "" ? k : `${prefix}.${k}`
268
287
  )
269
288
  );
270
- return childPaths.length > 0 ? childPaths : [prefix];
289
+ if (childPaths.length > 0) return childPaths;
290
+ const sameKeySet = Object.keys(values).length === Object.keys(initial).length && Object.keys(values).every((k) => Object.hasOwn(initial, k));
291
+ return sameKeySet ? [] : [prefix];
271
292
  }
272
- return [prefix];
293
+ return valuesEqual(values, initial) ? [] : [prefix];
273
294
  };
274
295
  var FORM_SEQ_KEY = "__form__";
275
296
  var SUBMIT_SEQ_KEY = "__submit__";
@@ -331,7 +352,18 @@ var createForm = (schema, options) => {
331
352
  mode: initialMode,
332
353
  reValidateMode: initialReValidateMode
333
354
  };
334
- const store = createStore(() => initial);
355
+ const store = options.devtools === void 0 || options.devtools === false ? createStore(() => initial) : createStore()(
356
+ devtools(() => initial, {
357
+ name: typeof options.devtools === "string" ? options.devtools : "formstand",
358
+ // Non-production only, matching zustand's own devtools default:
359
+ // opting in during development must NOT stream every keystroke to
360
+ // an end user who happens to have the extension installed, where
361
+ // a module-singleton form's unbounded time-travel history is a
362
+ // real memory + serialization cost. Guarded for environments
363
+ // without process (a bundler defines NODE_ENV in real dev).
364
+ enabled: typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production"
365
+ })
366
+ );
335
367
  const commitFullPass = (state, next) => errorChannels(state, {
336
368
  schemaErrors: reuseErrorRefs(next, state.schemaErrors)
337
369
  });
@@ -360,7 +392,38 @@ var createForm = (schema, options) => {
360
392
  return result.kind === "pending" ? result.promise.then((r) => r.kind === "valid") : result.kind === "valid";
361
393
  }
362
394
  try {
363
- const result = validateSync(schema, store.getState().values);
395
+ const values = store.getState().values;
396
+ const scopes = paths.map((path) => ({
397
+ path,
398
+ scope: fieldScopeFor(path, values)
399
+ }));
400
+ if (scopes.every(
401
+ ({ scope }) => scope.kind === "skip" || scope.kind === "parse" && scope.viaSubschema
402
+ )) {
403
+ const merged = scopes.reduce(
404
+ (acc, { path, scope }) => scope.kind === "skip" ? acc : {
405
+ ...acc,
406
+ ...scopeSettledResult(
407
+ validateSync(scope.schema, scope.value),
408
+ path,
409
+ true
410
+ )
411
+ },
412
+ {}
413
+ );
414
+ store.setState((state) => ({
415
+ ...state,
416
+ ...errorChannels(state, {
417
+ schemaErrors: reuseErrorRefs(
418
+ mergeScopedErrors(state.schemaErrors, merged, paths),
419
+ state.schemaErrors
420
+ ),
421
+ serverErrors: omitScope(state.serverErrors, paths)
422
+ })
423
+ }));
424
+ return Object.values(merged).flat().length === 0;
425
+ }
426
+ const result = validateSync(schema, values);
364
427
  const fullErrors = result.kind === "invalid" ? result.errors : emptyErrors;
365
428
  store.setState((state) => ({
366
429
  ...state,
@@ -668,7 +731,11 @@ var createForm = (schema, options) => {
668
731
  });
669
732
  }
670
733
  const merged = stale ? result.errors : store.getState().errors;
671
- onInvalid?.(merged);
734
+ try {
735
+ onInvalid?.(merged);
736
+ } catch (error) {
737
+ return { kind: "error", error };
738
+ }
672
739
  return { kind: "invalid", errors: merged };
673
740
  }
674
741
  if (!stale) {
@@ -928,9 +995,13 @@ var createForm = (schema, options) => {
928
995
  // In-flight state is owned by live passes, never by snapshots: a
929
996
  // snapshotted flag has no pass left to clear it (the pass that set
930
997
  // it cleared its ORIGINAL entry when it settled), so restoring one
931
- // would stick it forever.
998
+ // would stick it forever. isSubmitting follows the same rule — a
999
+ // mid-submit snapshot restored later must not disable submit
1000
+ // buttons forever, so it reflects the LIVE submit count instead of
1001
+ // the snapshotted flag (a live pass's finally still resets it).
932
1002
  isValidating: emptyBools,
933
- isValidatingForm: false
1003
+ isValidatingForm: false,
1004
+ isSubmitting: inFlight.count > 0
934
1005
  };
935
1006
  })
936
1007
  });
@@ -952,21 +1023,91 @@ var shouldValidateOn = (trigger, mode, reValidateMode, submitAttempted, touched
952
1023
  return true;
953
1024
  }
954
1025
  };
1026
+
1027
+ // src/core/persist.ts
1028
+ var defaultStorage = () => {
1029
+ try {
1030
+ return typeof globalThis.localStorage === "undefined" ? null : globalThis.localStorage;
1031
+ } catch {
1032
+ return null;
1033
+ }
1034
+ };
1035
+ var persistForm = (form, options) => {
1036
+ const storage = options.storage ?? defaultStorage();
1037
+ const debounceMs = options.debounceMs ?? 300;
1038
+ const apply = options.apply ?? "adopt";
1039
+ const applyDraft = (values) => apply === "restore" ? form.setValues(values) : form.adoptValues(values);
1040
+ const restore = () => {
1041
+ try {
1042
+ const raw = storage === null ? null : storage.getItem(options.key);
1043
+ if (raw === null) return false;
1044
+ applyDraft(JSON.parse(raw));
1045
+ return true;
1046
+ } catch {
1047
+ return false;
1048
+ }
1049
+ };
1050
+ const writeDraft = (values) => {
1051
+ try {
1052
+ storage?.setItem(options.key, JSON.stringify(values));
1053
+ } catch {
1054
+ }
1055
+ };
1056
+ const timerRef = {
1057
+ current: null
1058
+ };
1059
+ const cancelPending = () => {
1060
+ if (timerRef.current !== null) {
1061
+ clearTimeout(timerRef.current);
1062
+ timerRef.current = null;
1063
+ }
1064
+ };
1065
+ if (apply !== "manual") {
1066
+ restore();
1067
+ }
1068
+ const unsubscribe = form.watchValues((values) => {
1069
+ cancelPending();
1070
+ if (debounceMs === 0) {
1071
+ writeDraft(values);
1072
+ } else {
1073
+ timerRef.current = setTimeout(() => {
1074
+ timerRef.current = null;
1075
+ writeDraft(values);
1076
+ }, debounceMs);
1077
+ }
1078
+ });
1079
+ return Object.freeze({
1080
+ restore,
1081
+ clear: () => {
1082
+ cancelPending();
1083
+ try {
1084
+ storage?.removeItem(options.key);
1085
+ } catch {
1086
+ }
1087
+ },
1088
+ dispose: () => {
1089
+ cancelPending();
1090
+ unsubscribe();
1091
+ }
1092
+ });
1093
+ };
955
1094
  var useForm = (schema, options) => {
956
1095
  const formRef = useRef(null);
957
1096
  if (formRef.current === null) {
958
1097
  formRef.current = createForm(schema, options);
959
1098
  }
960
1099
  const form = formRef.current;
1100
+ const mountInitialsRef = useRef(options.initialValues);
961
1101
  const warnedRef = useRef(false);
962
1102
  useEffect(() => {
963
- if (!warnedRef.current && form.schema !== schema) {
1103
+ const changedInitials = !Object.is(options.initialValues, mountInitialsRef.current) && !valuesEqual(options.initialValues, mountInitialsRef.current);
1104
+ if (!warnedRef.current && (form.schema !== schema || changedInitials)) {
964
1105
  warnedRef.current = true;
965
1106
  console.warn(
966
- "[formstand] useForm received a different schema reference after the first render; schema and options changes are ignored for the life of the component. If the schema is defined inline this is harmless (but consider hoisting it to module scope). For initial values that arrive later (e.g. from a fetch), call form.adoptValues(values) or form.reset(values) instead."
1107
+ "[formstand] useForm received a different schema or initial values after the first render; schema and options changes are ignored for the life of the component. If the schema is defined inline this is harmless (but consider hoisting it to module scope). For initial values that arrive later (e.g. from a fetch), call form.adoptValues(values) or form.reset(values) instead."
967
1108
  );
968
1109
  }
969
- }, [form, schema]);
1110
+ }, [form, schema, options.initialValues]);
970
1111
  return form;
971
1112
  };
972
1113
  var createFormContext = () => {
@@ -1112,6 +1253,14 @@ function useField(form, pathArg, options) {
1112
1253
  [slice, emptyValue, setValue, setTouched, setError, clearError, validate, validateAsync2, onBlur]
1113
1254
  );
1114
1255
  }
1256
+
1257
+ // src/react/useVariantField.ts
1258
+ function useVariantField(form, unionPath, field) {
1259
+ return useField(
1260
+ form,
1261
+ `${unionPath}.${field}`
1262
+ );
1263
+ }
1115
1264
  var EMPTY_ID_STATE = { items: [], ids: [], counter: 0 };
1116
1265
  var EMPTY_ITEMS = [];
1117
1266
  var reconcileIds = (prev, nextItems) => {
@@ -1132,23 +1281,22 @@ var reconcileIds = (prev, nextItems) => {
1132
1281
  const prevIndex = buckets.get(item)?.shift();
1133
1282
  return prevIndex === void 0 ? null : prev.ids[prevIndex] ?? null;
1134
1283
  });
1135
- const leftover = [...buckets.values()].flat().sort((a, b) => a - b).map((i) => prev.ids[i]).filter((id) => id !== void 0);
1136
- const ordinals = matchedIds.reduce((acc, id) => {
1137
- const count = acc.length === 0 ? 0 : acc[acc.length - 1] ?? 0;
1138
- acc.push(id === null ? count + 1 : count);
1139
- return acc;
1284
+ const leftoverAt = new Map(
1285
+ [...buckets.values()].flat().flatMap((i) => {
1286
+ const id = prev.ids[i];
1287
+ return id === void 0 ? [] : [[i, id]];
1288
+ })
1289
+ );
1290
+ const reused = matchedIds.map((id, i) => id ?? leftoverAt.get(i) ?? null);
1291
+ const mintCounts = reused.reduce((acc, id) => {
1292
+ const count = acc[acc.length - 1] ?? 0;
1293
+ return [...acc, id === null ? count + 1 : count];
1140
1294
  }, []);
1141
- const unmatched = ordinals.length === 0 ? 0 : ordinals[ordinals.length - 1] ?? 0;
1142
- const ids = matchedIds.map((id, i) => {
1143
- if (id !== null) return id;
1144
- const ordinal = ordinals[i] ?? 0;
1145
- return leftover[ordinal - 1] ?? `__zfa_${prev.counter + (ordinal - leftover.length)}`;
1146
- });
1147
- return {
1148
- items: nextItems,
1149
- ids,
1150
- counter: prev.counter + Math.max(0, unmatched - leftover.length)
1151
- };
1295
+ const minted = mintCounts[mintCounts.length - 1] ?? 0;
1296
+ const ids = reused.map(
1297
+ (id, i) => id ?? `__zfa_${prev.counter + (mintCounts[i] ?? 0)}`
1298
+ );
1299
+ return { items: nextItems, ids, counter: prev.counter + minted };
1152
1300
  };
1153
1301
  function useFieldArray(form, pathArg) {
1154
1302
  const path = useStore(
@@ -1254,6 +1402,11 @@ var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase
1254
1402
  var createFormHooks = (form, name) => {
1255
1403
  const structural = form;
1256
1404
  const useBoundField = (path, options) => useField(structural, path, options);
1405
+ const useBoundVariantField = (unionPath, field) => useVariantField(
1406
+ structural,
1407
+ unionPath,
1408
+ field
1409
+ );
1257
1410
  const useBoundFieldArray = (path) => useFieldArray(structural, path);
1258
1411
  const useBoundSelector = (selector) => useFormSelector(structural, selector);
1259
1412
  const useBoundSelectorShallow = (selector) => useFormSelectorShallow(structural, selector);
@@ -1265,6 +1418,7 @@ var createFormHooks = (form, name) => {
1265
1418
  const prefix = capitalize(name ?? "");
1266
1419
  return {
1267
1420
  [`use${prefix}Field`]: useBoundField,
1421
+ [`use${prefix}VariantField`]: useBoundVariantField,
1268
1422
  [`use${prefix}FieldArray`]: useBoundFieldArray,
1269
1423
  [`use${prefix}Selector`]: useBoundSelector,
1270
1424
  [`use${prefix}SelectorShallow`]: useBoundSelectorShallow,
@@ -1309,6 +1463,48 @@ var numberInputProps = (field) => ({
1309
1463
  },
1310
1464
  onBlur: field.onBlur
1311
1465
  });
1466
+ var dateToInputText = (value) => {
1467
+ if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
1468
+ return "";
1469
+ }
1470
+ const pad = (part) => String(part).padStart(2, "0");
1471
+ return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
1472
+ };
1473
+ var parseDateText = (text) => {
1474
+ if (text.trim() === "") return { kind: "empty" };
1475
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
1476
+ if (match === null) return { kind: "invalid" };
1477
+ const [, year, month, day] = match;
1478
+ const y = Number(year);
1479
+ const date = new Date(y, Number(month) - 1, Number(day));
1480
+ if (y < 100) date.setFullYear(y);
1481
+ return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
1482
+ };
1483
+ var dateInputProps = (field) => ({
1484
+ type: "date",
1485
+ name: field.path,
1486
+ value: dateToInputText(field.value),
1487
+ "aria-invalid": ariaInvalid(field),
1488
+ onChange: (e) => {
1489
+ const parsed = parseDateText(e.target.value);
1490
+ if (parsed.kind !== "date") {
1491
+ field.setValue(field.emptyValue);
1492
+ return;
1493
+ }
1494
+ const current = field.value;
1495
+ const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
1496
+ parsed.value.getFullYear(),
1497
+ parsed.value.getMonth(),
1498
+ parsed.value.getDate(),
1499
+ current.getHours(),
1500
+ current.getMinutes(),
1501
+ current.getSeconds(),
1502
+ current.getMilliseconds()
1503
+ ) : parsed.value;
1504
+ field.setValue(next);
1505
+ },
1506
+ onBlur: field.onBlur
1507
+ });
1312
1508
  var checkboxProps = (field) => ({
1313
1509
  type: "checkbox",
1314
1510
  name: field.path,
@@ -1363,6 +1559,33 @@ var TextField = ({
1363
1559
  /* @__PURE__ */ jsx(ErrorText, { id: errorId, error: field.error })
1364
1560
  ] });
1365
1561
  };
1562
+ var DateField = ({
1563
+ form,
1564
+ path,
1565
+ label,
1566
+ min,
1567
+ max,
1568
+ ref
1569
+ }) => {
1570
+ const id = useId();
1571
+ const errorId = `${id}-error`;
1572
+ const field = useField(form, path);
1573
+ return /* @__PURE__ */ jsxs("div", { className: "zf-field", children: [
1574
+ label !== void 0 ? /* @__PURE__ */ jsx("label", { htmlFor: id, className: "zf-label", children: label }) : null,
1575
+ /* @__PURE__ */ jsx(
1576
+ "input",
1577
+ {
1578
+ id,
1579
+ ref,
1580
+ ...dateInputProps(field),
1581
+ min,
1582
+ max,
1583
+ "aria-describedby": hasError(field.error) ? errorId : void 0
1584
+ }
1585
+ ),
1586
+ /* @__PURE__ */ jsx(ErrorText, { id: errorId, error: field.error })
1587
+ ] });
1588
+ };
1366
1589
  var IDLE_EDIT = { raw: null, pushed: void 0 };
1367
1590
  var useNumberInput = (field) => {
1368
1591
  const [edit, setEdit] = useState(IDLE_EDIT);
@@ -1465,7 +1688,8 @@ var SelectField = ({
1465
1688
  const id = useId();
1466
1689
  const errorId = `${id}-error`;
1467
1690
  const field = useField(form, path);
1468
- const showEmptyOption = field.value === void 0 || field.value === null || placeholder !== void 0;
1691
+ const clearable = field.emptyValue === null;
1692
+ const showEmptyOption = clearable || field.value === void 0 || field.value === null || placeholder !== void 0;
1469
1693
  return /* @__PURE__ */ jsxs("div", { className: "zf-field", children: [
1470
1694
  label !== void 0 ? /* @__PURE__ */ jsx("label", { htmlFor: id, className: "zf-label", children: label }) : null,
1471
1695
  /* @__PURE__ */ jsxs(
@@ -1476,7 +1700,7 @@ var SelectField = ({
1476
1700
  ...selectProps(field),
1477
1701
  "aria-describedby": hasError(field.error) ? errorId : void 0,
1478
1702
  children: [
1479
- showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder ?? "" }) : null,
1703
+ showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled: !clearable, children: placeholder ?? "" }) : null,
1480
1704
  options.map((opt, index) => (
1481
1705
  // Index-qualified: option lists may legitimately repeat a value
1482
1706
  // (e.g. differently-labelled aliases), and bare value keys would
@@ -1491,13 +1715,12 @@ var SelectField = ({
1491
1715
  };
1492
1716
 
1493
1717
  // src/react/focusError.ts
1494
- var focusableControls = (scope) => [
1718
+ var namedControls = (scope) => [
1495
1719
  ...scope.querySelectorAll(
1496
1720
  "input[name], select[name], textarea[name]"
1497
1721
  )
1498
- ].filter(
1499
- (el) => !el.matches(':disabled, [type="hidden"]') && el.closest("dialog:not([open])") === null
1500
- );
1722
+ ];
1723
+ var isFocusCandidate = (el) => !el.matches(':disabled, [type="hidden"]') && el.closest("dialog:not([open])") === null;
1501
1724
  var holdsFocus = (el) => {
1502
1725
  const rootNode = el.getRootNode();
1503
1726
  const active = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : document.activeElement;
@@ -1517,19 +1740,19 @@ var focusFirstError = (errors, root) => {
1517
1740
  (k) => k !== "" && (errors[k]?.length ?? 0) > 0
1518
1741
  );
1519
1742
  const hasRootError = (errors[""]?.length ?? 0) > 0;
1520
- const controls = focusableControls(scope);
1521
- const fieldMatches = controls.filter(
1522
- (el) => nameMatchesAny(el, erroredPaths)
1523
- );
1743
+ const controls = namedControls(scope);
1744
+ const fieldMatches = controls.filter((el) => nameMatchesAny(el, erroredPaths)).filter(isFocusCandidate);
1524
1745
  const rootFallbackApplies = hasRootError && (root !== void 0 || document.querySelectorAll("form").length <= 1);
1525
- const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls : [];
1746
+ const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls.filter(isFocusCandidate) : [];
1526
1747
  return focusFirstOf(candidates);
1527
1748
  };
1528
1749
  var focusField = (path, root) => {
1529
- const controls = focusableControls(root ?? document);
1530
- return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(controls) : focusFirstOf(controls.filter((el) => nameMatchesAny(el, [path])));
1750
+ const controls = namedControls(root ?? document);
1751
+ return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(controls.filter(isFocusCandidate)) : focusFirstOf(
1752
+ controls.filter((el) => nameMatchesAny(el, [path])).filter(isFocusCandidate)
1753
+ );
1531
1754
  };
1532
1755
 
1533
- export { CheckboxField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, createFormHooks, emptyValueForSchema, flattenIssues, focusField, focusFirstError, getAtPath, isAsyncRequiredError, numberInputProps, numberToInputText, parseNumberText, parsePath, selectProps, setAtPath, shouldValidateOn, textInputProps, useField, useFieldArray, useForm, useFormError, useFormSelector, useFormSelectorShallow, useIsDirty, useIsSubmitting, useIsValid, useSubmitCount, validateAsync, validateSync };
1756
+ export { CheckboxField, DateField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, createFormHooks, dateInputProps, dateToInputText, emptyValueForSchema, flattenIssues, focusField, focusFirstError, getAtPath, isAsyncRequiredError, numberInputProps, numberToInputText, parseDateText, parseNumberText, parsePath, persistForm, selectProps, setAtPath, shouldValidateOn, textInputProps, useField, useFieldArray, useForm, useFormError, useFormSelector, useFormSelectorShallow, useIsDirty, useIsSubmitting, useIsValid, useSubmitCount, useVariantField, validateAsync, validateSync };
1534
1757
  //# sourceMappingURL=index.js.map
1535
1758
  //# sourceMappingURL=index.js.map