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