formstand 0.3.0 → 0.4.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.d.cts CHANGED
@@ -59,6 +59,7 @@ declare const flattenIssues: (issues: readonly z.core.$ZodIssue[]) => ErrorMap;
59
59
  declare const validateSync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => SettledValidationResult<z.output<TSchema>>;
60
60
  declare const validateAsync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => Promise<SettledValidationResult<z.output<TSchema>>>;
61
61
  declare const isAsyncRequiredError: (e: unknown) => boolean;
62
+ declare const emptyValueForSchema: (schema: z.ZodType) => null | undefined;
62
63
 
63
64
  type ReadonlyStoreApi<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
64
65
  type CreateFormOptions<TSchema extends z.ZodType> = Readonly<{
@@ -80,6 +81,9 @@ type SubmitResult<TOutput> = Readonly<{
80
81
  errors: ErrorMap;
81
82
  }> | Readonly<{
82
83
  kind: "skipped";
84
+ }> | Readonly<{
85
+ kind: "error";
86
+ error: unknown;
83
87
  }>;
84
88
  type ArrayItemOf<T> = T extends readonly (infer U)[] ? U : never;
85
89
  type ErrorPath<TValues> = FieldPath<TValues> | "";
@@ -169,14 +173,6 @@ declare function useFormSelectorShallow<TSchema extends z.ZodType, U>(form: Form
169
173
  declare function useFormSelectorShallow<U>(form: FormStateApi & {
170
174
  readonly schema?: undefined;
171
175
  }, selector: (state: FormState<unknown>) => U): U;
172
- /**
173
- * @deprecated Renamed to `useFormSelector` — React DOM ships its own
174
- * (deprecated) `useFormState`, and auto-imports regularly grab the wrong one.
175
- * This alias will be removed before 1.0.
176
- */
177
- declare const useFormState: typeof useFormSelector;
178
- /** @deprecated Renamed to `useFormSelectorShallow`; see `useFormState`. */
179
- declare const useFormStateShallow: typeof useFormSelectorShallow;
180
176
 
181
177
  declare const useFormError: (form: FormStateApi) => readonly string[] | undefined;
182
178
 
@@ -190,7 +186,7 @@ type FieldFormApi = Readonly<{
190
186
  store: ReadonlyStore$1<FormState<unknown>>;
191
187
  setValue(path: string, value: unknown): void;
192
188
  setTouched(path: string, touched?: boolean): void;
193
- setError(path: string, errors: readonly string[]): void;
189
+ setError(path: string, errors: string | readonly string[]): void;
194
190
  clearErrors(path?: string): void;
195
191
  validateField(path: string): FieldValidationResult;
196
192
  validateFieldAsync(path: string): Promise<FieldValidationResult>;
@@ -210,61 +206,63 @@ type UseFieldReturn<TValue> = Readonly<{
210
206
  isValidating: boolean;
211
207
  setValue: (value: TValue) => void;
212
208
  setTouched: (touched?: boolean) => void;
213
- setError: (errors: readonly string[]) => void;
209
+ setError: (errors: string | readonly string[]) => void;
214
210
  clearError: () => void;
215
211
  validate: () => FieldValidationResult;
216
212
  validateAsync: () => Promise<FieldValidationResult>;
217
213
  onBlur: () => void;
218
214
  }>;
219
- declare function useField<TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>>>(form: Form<TSchema>, path: P, options?: UseFieldOptions): UseFieldReturn<FieldValue<z.input<TSchema>, P>>;
220
215
  declare function useField<TSchema extends z.ZodType>(form: Form<TSchema>, pathSelector: (state: FormState<z.input<TSchema>>) => string, options?: UseFieldOptions): UseFieldReturn<unknown>;
221
216
  declare function useField<TValue = unknown>(form: FieldFormApi & {
222
217
  readonly schema?: undefined;
223
218
  }, path: string | ((state: FormState<unknown>) => string), options?: UseFieldOptions): UseFieldReturn<TValue>;
219
+ declare function useField<TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>>>(form: Form<TSchema>, path: P, options?: UseFieldOptions): UseFieldReturn<FieldValue<z.input<TSchema>, P>>;
224
220
 
221
+ type PathsOf<F extends FieldFormApi> = F extends Form<infer TSchema extends z.ZodType> ? FieldPath<z.input<TSchema>> : string;
225
222
  type FieldRef<T> = ((instance: T | null) => void) | Readonly<{
226
223
  current: T | null;
227
224
  }> | null;
228
- type TextFieldProps = Readonly<{
229
- form: FieldFormApi;
230
- path: string;
225
+ type TextFieldProps<F extends FieldFormApi = FieldFormApi> = Readonly<{
226
+ form: F;
227
+ path: PathsOf<F>;
231
228
  label?: ReactNode;
232
229
  placeholder?: string;
233
230
  type?: "text" | "password" | "email" | "url" | "tel";
234
231
  autoComplete?: string;
235
232
  ref?: FieldRef<HTMLInputElement>;
236
233
  }>;
237
- declare const TextField: ({ form, path, label, placeholder, type, autoComplete, ref, }: TextFieldProps) => react.JSX.Element;
238
- type NumberFieldProps = Readonly<{
239
- form: FieldFormApi;
240
- path: string;
234
+ declare const TextField: <F extends FieldFormApi>({ form, path, label, placeholder, type, autoComplete, ref, }: TextFieldProps<F>) => react.JSX.Element;
235
+ type NumberFieldProps<F extends FieldFormApi = FieldFormApi> = Readonly<{
236
+ form: F;
237
+ path: PathsOf<F>;
241
238
  label?: ReactNode;
242
239
  placeholder?: string;
243
240
  ref?: FieldRef<HTMLInputElement>;
244
241
  }>;
245
- declare const NumberField: ({ form, path, label, placeholder, ref, }: NumberFieldProps) => react.JSX.Element;
246
- type CheckboxFieldProps = Readonly<{
247
- form: FieldFormApi;
248
- path: string;
242
+ declare const NumberField: <F extends FieldFormApi>({ form, path, label, placeholder, ref, }: NumberFieldProps<F>) => react.JSX.Element;
243
+ type CheckboxFieldProps<F extends FieldFormApi = FieldFormApi> = Readonly<{
244
+ form: F;
245
+ path: PathsOf<F>;
249
246
  label?: ReactNode;
250
247
  ref?: FieldRef<HTMLInputElement>;
251
248
  }>;
252
- declare const CheckboxField: ({ form, path, label, ref }: CheckboxFieldProps) => react.JSX.Element;
249
+ declare const CheckboxField: <F extends FieldFormApi>({ form, path, label, ref, }: CheckboxFieldProps<F>) => react.JSX.Element;
253
250
  type SelectFieldOption<T extends string> = Readonly<{
254
251
  value: T;
255
252
  label: ReactNode;
256
253
  }>;
257
- type SelectFieldProps<T extends string> = Readonly<{
258
- form: FieldFormApi;
259
- path: string;
254
+ type SelectFieldProps<T extends string, F extends FieldFormApi = FieldFormApi> = Readonly<{
255
+ form: F;
256
+ path: PathsOf<F>;
260
257
  label?: ReactNode;
261
258
  options: readonly SelectFieldOption<T>[];
262
259
  placeholder?: string;
263
260
  ref?: FieldRef<HTMLSelectElement>;
264
261
  }>;
265
- declare const SelectField: <T extends string>({ form, path, label, options, placeholder, ref, }: SelectFieldProps<T>) => react.JSX.Element;
262
+ declare const SelectField: <T extends string, F extends FieldFormApi>({ form, path, label, options, placeholder, ref, }: SelectFieldProps<T, F>) => react.JSX.Element;
266
263
 
267
264
  declare const focusFirstError: (errors: ErrorMap, root?: ParentNode) => boolean;
265
+ declare const focusField: (path: string, root?: ParentNode) => boolean;
268
266
 
269
267
  type ReadonlyStore<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
270
268
  type FieldArrayFormApi = Readonly<{
@@ -337,4 +335,4 @@ declare const numberInputProps: <T extends number | null | undefined>(field: Use
337
335
  declare const checkboxProps: <T extends boolean | null | undefined>(field: UseFieldReturn<T>) => CheckboxProps;
338
336
  declare const selectProps: <T extends string | null | undefined>(field: UseFieldReturn<T>) => SelectProps;
339
337
 
340
- export { type BoolMap, CheckboxField, type CheckboxFieldProps, type CheckboxProps, type CreateFormOptions, type ErrorMap, type FieldArrayEntry, type FieldArrayFormApi, type FieldFormApi, type FieldPath, type FieldPathArg, type FieldRef, type FieldSnapshot, type FieldValidationResult, type FieldValue, type Form, type FormContextApi, type FormProviderProps, type FormState, type FormStateApi, type InvalidSubmitHandler, NumberField, type NumberFieldProps, type NumberInputProps, type ParsedNumberText, type PathSegment, type ReadonlyStoreApi, type ResetOptions, SelectField, type SelectFieldOption, type SelectFieldProps, type SelectProps, type SettledFieldValidationResult, type SettledValidationResult, type SubmitHandler, type SubmitOptions, type SubmitResult, TextField, type TextFieldProps, type TextInputProps, type UseFieldArrayReturn, type UseFieldOptions, type UseFieldReturn, type ValidationMode, type ValidationResult, type ValidationTrigger, checkboxProps, createForm, createFormContext, flattenIssues, focusFirstError, getAtPath, isAsyncRequiredError, numberInputProps, numberToInputText, parseNumberText, parsePath, selectProps, setAtPath, shouldValidateOn, textInputProps, useField, useFieldArray, useForm, useFormError, useFormSelector, useFormSelectorShallow, useFormState, useFormStateShallow, useIsDirty, useIsSubmitting, useIsValid, useSubmitCount, validateAsync, validateSync };
338
+ export { type BoolMap, CheckboxField, type CheckboxFieldProps, type CheckboxProps, type CreateFormOptions, type ErrorMap, type FieldArrayEntry, type FieldArrayFormApi, type FieldFormApi, type FieldPath, type FieldPathArg, type FieldRef, type FieldSnapshot, type FieldValidationResult, type FieldValue, type Form, type FormContextApi, type FormProviderProps, type FormState, type FormStateApi, type InvalidSubmitHandler, NumberField, type NumberFieldProps, type NumberInputProps, type ParsedNumberText, type PathSegment, type PathsOf, type ReadonlyStoreApi, type ResetOptions, SelectField, type SelectFieldOption, type SelectFieldProps, type SelectProps, type SettledFieldValidationResult, type SettledValidationResult, type SubmitHandler, type SubmitOptions, type SubmitResult, TextField, type TextFieldProps, type TextInputProps, type UseFieldArrayReturn, type UseFieldOptions, type UseFieldReturn, type ValidationMode, type ValidationResult, type ValidationTrigger, checkboxProps, createForm, createFormContext, 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 };
package/dist/index.d.ts CHANGED
@@ -59,6 +59,7 @@ declare const flattenIssues: (issues: readonly z.core.$ZodIssue[]) => ErrorMap;
59
59
  declare const validateSync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => SettledValidationResult<z.output<TSchema>>;
60
60
  declare const validateAsync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => Promise<SettledValidationResult<z.output<TSchema>>>;
61
61
  declare const isAsyncRequiredError: (e: unknown) => boolean;
62
+ declare const emptyValueForSchema: (schema: z.ZodType) => null | undefined;
62
63
 
63
64
  type ReadonlyStoreApi<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
64
65
  type CreateFormOptions<TSchema extends z.ZodType> = Readonly<{
@@ -80,6 +81,9 @@ type SubmitResult<TOutput> = Readonly<{
80
81
  errors: ErrorMap;
81
82
  }> | Readonly<{
82
83
  kind: "skipped";
84
+ }> | Readonly<{
85
+ kind: "error";
86
+ error: unknown;
83
87
  }>;
84
88
  type ArrayItemOf<T> = T extends readonly (infer U)[] ? U : never;
85
89
  type ErrorPath<TValues> = FieldPath<TValues> | "";
@@ -169,14 +173,6 @@ declare function useFormSelectorShallow<TSchema extends z.ZodType, U>(form: Form
169
173
  declare function useFormSelectorShallow<U>(form: FormStateApi & {
170
174
  readonly schema?: undefined;
171
175
  }, selector: (state: FormState<unknown>) => U): U;
172
- /**
173
- * @deprecated Renamed to `useFormSelector` — React DOM ships its own
174
- * (deprecated) `useFormState`, and auto-imports regularly grab the wrong one.
175
- * This alias will be removed before 1.0.
176
- */
177
- declare const useFormState: typeof useFormSelector;
178
- /** @deprecated Renamed to `useFormSelectorShallow`; see `useFormState`. */
179
- declare const useFormStateShallow: typeof useFormSelectorShallow;
180
176
 
181
177
  declare const useFormError: (form: FormStateApi) => readonly string[] | undefined;
182
178
 
@@ -190,7 +186,7 @@ type FieldFormApi = Readonly<{
190
186
  store: ReadonlyStore$1<FormState<unknown>>;
191
187
  setValue(path: string, value: unknown): void;
192
188
  setTouched(path: string, touched?: boolean): void;
193
- setError(path: string, errors: readonly string[]): void;
189
+ setError(path: string, errors: string | readonly string[]): void;
194
190
  clearErrors(path?: string): void;
195
191
  validateField(path: string): FieldValidationResult;
196
192
  validateFieldAsync(path: string): Promise<FieldValidationResult>;
@@ -210,61 +206,63 @@ type UseFieldReturn<TValue> = Readonly<{
210
206
  isValidating: boolean;
211
207
  setValue: (value: TValue) => void;
212
208
  setTouched: (touched?: boolean) => void;
213
- setError: (errors: readonly string[]) => void;
209
+ setError: (errors: string | readonly string[]) => void;
214
210
  clearError: () => void;
215
211
  validate: () => FieldValidationResult;
216
212
  validateAsync: () => Promise<FieldValidationResult>;
217
213
  onBlur: () => void;
218
214
  }>;
219
- declare function useField<TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>>>(form: Form<TSchema>, path: P, options?: UseFieldOptions): UseFieldReturn<FieldValue<z.input<TSchema>, P>>;
220
215
  declare function useField<TSchema extends z.ZodType>(form: Form<TSchema>, pathSelector: (state: FormState<z.input<TSchema>>) => string, options?: UseFieldOptions): UseFieldReturn<unknown>;
221
216
  declare function useField<TValue = unknown>(form: FieldFormApi & {
222
217
  readonly schema?: undefined;
223
218
  }, path: string | ((state: FormState<unknown>) => string), options?: UseFieldOptions): UseFieldReturn<TValue>;
219
+ declare function useField<TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>>>(form: Form<TSchema>, path: P, options?: UseFieldOptions): UseFieldReturn<FieldValue<z.input<TSchema>, P>>;
224
220
 
221
+ type PathsOf<F extends FieldFormApi> = F extends Form<infer TSchema extends z.ZodType> ? FieldPath<z.input<TSchema>> : string;
225
222
  type FieldRef<T> = ((instance: T | null) => void) | Readonly<{
226
223
  current: T | null;
227
224
  }> | null;
228
- type TextFieldProps = Readonly<{
229
- form: FieldFormApi;
230
- path: string;
225
+ type TextFieldProps<F extends FieldFormApi = FieldFormApi> = Readonly<{
226
+ form: F;
227
+ path: PathsOf<F>;
231
228
  label?: ReactNode;
232
229
  placeholder?: string;
233
230
  type?: "text" | "password" | "email" | "url" | "tel";
234
231
  autoComplete?: string;
235
232
  ref?: FieldRef<HTMLInputElement>;
236
233
  }>;
237
- declare const TextField: ({ form, path, label, placeholder, type, autoComplete, ref, }: TextFieldProps) => react.JSX.Element;
238
- type NumberFieldProps = Readonly<{
239
- form: FieldFormApi;
240
- path: string;
234
+ declare const TextField: <F extends FieldFormApi>({ form, path, label, placeholder, type, autoComplete, ref, }: TextFieldProps<F>) => react.JSX.Element;
235
+ type NumberFieldProps<F extends FieldFormApi = FieldFormApi> = Readonly<{
236
+ form: F;
237
+ path: PathsOf<F>;
241
238
  label?: ReactNode;
242
239
  placeholder?: string;
243
240
  ref?: FieldRef<HTMLInputElement>;
244
241
  }>;
245
- declare const NumberField: ({ form, path, label, placeholder, ref, }: NumberFieldProps) => react.JSX.Element;
246
- type CheckboxFieldProps = Readonly<{
247
- form: FieldFormApi;
248
- path: string;
242
+ declare const NumberField: <F extends FieldFormApi>({ form, path, label, placeholder, ref, }: NumberFieldProps<F>) => react.JSX.Element;
243
+ type CheckboxFieldProps<F extends FieldFormApi = FieldFormApi> = Readonly<{
244
+ form: F;
245
+ path: PathsOf<F>;
249
246
  label?: ReactNode;
250
247
  ref?: FieldRef<HTMLInputElement>;
251
248
  }>;
252
- declare const CheckboxField: ({ form, path, label, ref }: CheckboxFieldProps) => react.JSX.Element;
249
+ declare const CheckboxField: <F extends FieldFormApi>({ form, path, label, ref, }: CheckboxFieldProps<F>) => react.JSX.Element;
253
250
  type SelectFieldOption<T extends string> = Readonly<{
254
251
  value: T;
255
252
  label: ReactNode;
256
253
  }>;
257
- type SelectFieldProps<T extends string> = Readonly<{
258
- form: FieldFormApi;
259
- path: string;
254
+ type SelectFieldProps<T extends string, F extends FieldFormApi = FieldFormApi> = Readonly<{
255
+ form: F;
256
+ path: PathsOf<F>;
260
257
  label?: ReactNode;
261
258
  options: readonly SelectFieldOption<T>[];
262
259
  placeholder?: string;
263
260
  ref?: FieldRef<HTMLSelectElement>;
264
261
  }>;
265
- declare const SelectField: <T extends string>({ form, path, label, options, placeholder, ref, }: SelectFieldProps<T>) => react.JSX.Element;
262
+ declare const SelectField: <T extends string, F extends FieldFormApi>({ form, path, label, options, placeholder, ref, }: SelectFieldProps<T, F>) => react.JSX.Element;
266
263
 
267
264
  declare const focusFirstError: (errors: ErrorMap, root?: ParentNode) => boolean;
265
+ declare const focusField: (path: string, root?: ParentNode) => boolean;
268
266
 
269
267
  type ReadonlyStore<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
270
268
  type FieldArrayFormApi = Readonly<{
@@ -337,4 +335,4 @@ declare const numberInputProps: <T extends number | null | undefined>(field: Use
337
335
  declare const checkboxProps: <T extends boolean | null | undefined>(field: UseFieldReturn<T>) => CheckboxProps;
338
336
  declare const selectProps: <T extends string | null | undefined>(field: UseFieldReturn<T>) => SelectProps;
339
337
 
340
- export { type BoolMap, CheckboxField, type CheckboxFieldProps, type CheckboxProps, type CreateFormOptions, type ErrorMap, type FieldArrayEntry, type FieldArrayFormApi, type FieldFormApi, type FieldPath, type FieldPathArg, type FieldRef, type FieldSnapshot, type FieldValidationResult, type FieldValue, type Form, type FormContextApi, type FormProviderProps, type FormState, type FormStateApi, type InvalidSubmitHandler, NumberField, type NumberFieldProps, type NumberInputProps, type ParsedNumberText, type PathSegment, type ReadonlyStoreApi, type ResetOptions, SelectField, type SelectFieldOption, type SelectFieldProps, type SelectProps, type SettledFieldValidationResult, type SettledValidationResult, type SubmitHandler, type SubmitOptions, type SubmitResult, TextField, type TextFieldProps, type TextInputProps, type UseFieldArrayReturn, type UseFieldOptions, type UseFieldReturn, type ValidationMode, type ValidationResult, type ValidationTrigger, checkboxProps, createForm, createFormContext, flattenIssues, focusFirstError, getAtPath, isAsyncRequiredError, numberInputProps, numberToInputText, parseNumberText, parsePath, selectProps, setAtPath, shouldValidateOn, textInputProps, useField, useFieldArray, useForm, useFormError, useFormSelector, useFormSelectorShallow, useFormState, useFormStateShallow, useIsDirty, useIsSubmitting, useIsValid, useSubmitCount, validateAsync, validateSync };
338
+ export { type BoolMap, CheckboxField, type CheckboxFieldProps, type CheckboxProps, type CreateFormOptions, type ErrorMap, type FieldArrayEntry, type FieldArrayFormApi, type FieldFormApi, type FieldPath, type FieldPathArg, type FieldRef, type FieldSnapshot, type FieldValidationResult, type FieldValue, type Form, type FormContextApi, type FormProviderProps, type FormState, type FormStateApi, type InvalidSubmitHandler, NumberField, type NumberFieldProps, type NumberInputProps, type ParsedNumberText, type PathSegment, type PathsOf, type ReadonlyStoreApi, type ResetOptions, SelectField, type SelectFieldOption, type SelectFieldProps, type SelectProps, type SettledFieldValidationResult, type SettledValidationResult, type SubmitHandler, type SubmitOptions, type SubmitResult, TextField, type TextFieldProps, type TextInputProps, type UseFieldArrayReturn, type UseFieldOptions, type UseFieldReturn, type ValidationMode, type ValidationResult, type ValidationTrigger, checkboxProps, createForm, createFormContext, 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 };
package/dist/index.js CHANGED
@@ -186,6 +186,39 @@ var emptyValueForSchema = (schema) => {
186
186
  };
187
187
  return walk(schema, false);
188
188
  };
189
+ var unwrapForWalk = (s) => {
190
+ if (s instanceof z.ZodOptional || s instanceof z.ZodNullable) {
191
+ return s.unwrap();
192
+ }
193
+ if (s instanceof z.ZodDefault || s instanceof z.ZodPrefault || s instanceof z.ZodCatch || s instanceof z.ZodReadonly || s instanceof z.ZodNonOptional) {
194
+ return s.def.innerType;
195
+ }
196
+ if (s instanceof z.ZodPipe) return s.def.in;
197
+ return null;
198
+ };
199
+ var isLeafSchema = (s) => s instanceof z.ZodString || s instanceof z.ZodNumber || s instanceof z.ZodBoolean || s instanceof z.ZodBigInt || s instanceof z.ZodDate || s instanceof z.ZodEnum || s instanceof z.ZodLiteral || s instanceof z.ZodNull || s instanceof z.ZodUndefined || s instanceof z.ZodVoid || s instanceof z.ZodNaN || s instanceof z.ZodSymbol;
200
+ var SCHEMA_WALK_BUDGET = 64;
201
+ var schemaHasPath = (schema, path) => {
202
+ const walk = (s, segments, budget) => {
203
+ if (budget <= 0) return true;
204
+ const inner = unwrapForWalk(s);
205
+ if (inner !== null) return walk(inner, segments, budget - 1);
206
+ const [head, ...rest] = segments;
207
+ if (head === void 0) return true;
208
+ if (s instanceof z.ZodObject) {
209
+ const shape = s.shape;
210
+ const child = shape[String(head)];
211
+ if (child !== void 0) return walk(child, rest, budget - 1);
212
+ const catchall = s.def.catchall;
213
+ return catchall !== void 0 && !(catchall instanceof z.ZodNever);
214
+ }
215
+ if (s instanceof z.ZodArray) {
216
+ return typeof head === "number" ? walk(s.element, rest, budget - 1) : false;
217
+ }
218
+ return !isLeafSchema(s);
219
+ };
220
+ return walk(schema, parsePath(path), SCHEMA_WALK_BUDGET);
221
+ };
189
222
  var hasChecks = (s) => {
190
223
  const checks = s.def.checks;
191
224
  return checks !== void 0 && checks.length > 0;
@@ -227,13 +260,14 @@ var dirtyPathsOf = (values, initial, prefix) => {
227
260
  if (valuesEqual(values, initial)) return [];
228
261
  if (isPlainObject(values) && isPlainObject(initial)) {
229
262
  const keys = /* @__PURE__ */ new Set([...Object.keys(values), ...Object.keys(initial)]);
230
- return [...keys].flatMap(
263
+ const childPaths = [...keys].flatMap(
231
264
  (k) => dirtyPathsOf(
232
265
  values[k],
233
266
  initial[k],
234
267
  prefix === "" ? k : `${prefix}.${k}`
235
268
  )
236
269
  );
270
+ return childPaths.length > 0 ? childPaths : [prefix];
237
271
  }
238
272
  return [prefix];
239
273
  };
@@ -357,6 +391,16 @@ var createForm = (schema, options) => {
357
391
  subschemaCache.set(path, sub);
358
392
  return sub;
359
393
  };
394
+ const checkedSchemaPaths = /* @__PURE__ */ new Set();
395
+ const warnMissingSchemaPath = (fn, path) => {
396
+ if (path === "" || checkedSchemaPaths.has(path)) return;
397
+ checkedSchemaPaths.add(path);
398
+ if (!schemaHasPath(schema, path)) {
399
+ console.warn(
400
+ `[formstand] ${fn}("${path}"): path does not exist in the schema \u2014 result will always be valid.`
401
+ );
402
+ }
403
+ };
360
404
  const fieldScopeFor = (path, values) => {
361
405
  const sub = path === "" ? null : subschemaAt(path);
362
406
  if (sub === null) {
@@ -394,6 +438,7 @@ var createForm = (schema, options) => {
394
438
  serverErrors: omitScope(state.serverErrors, [path])
395
439
  });
396
440
  const validateField = (path) => {
441
+ warnMissingSchemaPath("validateField", path);
397
442
  try {
398
443
  const scoped = scopedFieldErrors(path);
399
444
  store.setState((state) => ({
@@ -419,10 +464,11 @@ var createForm = (schema, options) => {
419
464
  };
420
465
  };
421
466
  const sequences = /* @__PURE__ */ new Map();
467
+ const seqRef = { current: 0 };
422
468
  const nextSeq = (key) => {
423
- const next = (sequences.get(key) ?? 0) + 1;
424
- sequences.set(key, next);
425
- return next;
469
+ seqRef.current += 1;
470
+ sequences.set(key, seqRef.current);
471
+ return seqRef.current;
426
472
  };
427
473
  const validateAsyncOnForm = async () => {
428
474
  const seq = nextSeq(FORM_SEQ_KEY);
@@ -502,6 +548,13 @@ var createForm = (schema, options) => {
502
548
  return requestedValid;
503
549
  };
504
550
  const validateFieldAsync = async (path) => {
551
+ warnMissingSchemaPath("validateFieldAsync", path);
552
+ if (path === "") {
553
+ const result = await validateAsyncOnForm();
554
+ return fieldResult(
555
+ result.kind === "invalid" ? result.errors : emptyErrors
556
+ );
557
+ }
505
558
  const seq = nextSeq(path);
506
559
  const valuesAtStart = store.getState().values;
507
560
  store.setState((state) => ({
@@ -561,7 +614,13 @@ var createForm = (schema, options) => {
561
614
  )
562
615
  }),
563
616
  touched: reKeyByArrayPath(state.touched, path, mapper),
564
- isValidating: reKeyByArrayPath(state.isValidating, path, mapper)
617
+ // Drop (don't re-key) in-flight flags under the array: the op changes
618
+ // `values`, so every pass they belong to fails its values-changed
619
+ // guard and never commits — but each pass's cleanup omits its
620
+ // ORIGINAL key, so a re-keyed entry would be orphaned and stick
621
+ // forever. Dropped, the flag is gone now and the cleanup is a
622
+ // harmless no-op.
623
+ isValidating: omitScope(state.isValidating, [path])
565
624
  };
566
625
  });
567
626
  };
@@ -583,33 +642,43 @@ var createForm = (schema, options) => {
583
642
  submitCount: state.submitCount + 1
584
643
  }));
585
644
  try {
586
- const result = await validateAsync(schema, store.getState().values);
645
+ const valuesAtStart = store.getState().values;
646
+ const result = await validateAsync(schema, valuesAtStart);
647
+ const staleValues = store.getState().values !== valuesAtStart;
587
648
  if (result.kind === "invalid") {
588
- store.setState((state) => {
589
- const committed = commitFullPass(state, result.errors);
590
- return {
591
- ...state,
592
- ...committed,
593
- // Mark every errored field touched so touched-gated error UIs
594
- // show something after the canonical first failed submit. The
595
- // "" key (schema-level refine) has no field to touch.
596
- touched: {
597
- ...state.touched,
598
- ...Object.fromEntries(
599
- Object.keys(committed.errors).filter((k) => k !== "").map((k) => [k, true])
600
- )
601
- }
602
- };
603
- });
604
- const merged = store.getState().errors;
649
+ if (!staleValues) {
650
+ store.setState((state) => {
651
+ const committed = commitFullPass(state, result.errors);
652
+ return {
653
+ ...state,
654
+ ...committed,
655
+ // Mark every errored field touched so touched-gated error UIs
656
+ // show something after the canonical first failed submit. The
657
+ // "" key (schema-level refine) has no field to touch.
658
+ touched: {
659
+ ...state.touched,
660
+ ...Object.fromEntries(
661
+ Object.keys(committed.errors).filter((k) => k !== "").map((k) => [k, true])
662
+ )
663
+ }
664
+ };
665
+ });
666
+ }
667
+ const merged = staleValues ? result.errors : store.getState().errors;
605
668
  onInvalid?.(merged);
606
669
  return { kind: "invalid", errors: merged };
607
670
  }
608
- store.setState((state) => ({
609
- ...state,
610
- ...commitFullPass(state, emptyErrors)
611
- }));
612
- await onValid(result.data);
671
+ if (!staleValues) {
672
+ store.setState((state) => ({
673
+ ...state,
674
+ ...commitFullPass(state, emptyErrors)
675
+ }));
676
+ }
677
+ try {
678
+ await onValid(result.data);
679
+ } catch (error) {
680
+ return { kind: "error", error };
681
+ }
613
682
  return { kind: "valid", data: result.data };
614
683
  } finally {
615
684
  inFlight.count -= 1;
@@ -741,7 +810,12 @@ var createForm = (schema, options) => {
741
810
  ...errorChannels(state, {
742
811
  schemaErrors: emptyErrors,
743
812
  serverErrors: emptyErrors
744
- })
813
+ }),
814
+ // Clearing `sequences` disowns every in-flight pass, including its
815
+ // flag cleanup (the ownership check fails, so it never writes) —
816
+ // clear the flags here too, mirroring reset(), or they'd stick.
817
+ isValidating: emptyBools,
818
+ isValidatingForm: false
745
819
  }));
746
820
  },
747
821
  reset: (nextInitial, resetOptions) => {
@@ -906,8 +980,6 @@ function useFormSelector(form, selector) {
906
980
  function useFormSelectorShallow(form, selector) {
907
981
  return useStore(form.store, useShallow(selector));
908
982
  }
909
- var useFormState = useFormSelector;
910
- var useFormStateShallow = useFormSelectorShallow;
911
983
  var useFormError = (form) => useStore(
912
984
  form.store,
913
985
  useShallow((state) => state.errors[""])
@@ -1204,7 +1276,12 @@ var NumberField = ({
1204
1276
  /* @__PURE__ */ jsx(ErrorText, { id: errorId, error: field.error })
1205
1277
  ] });
1206
1278
  };
1207
- var CheckboxField = ({ form, path, label, ref }) => {
1279
+ var CheckboxField = ({
1280
+ form,
1281
+ path,
1282
+ label,
1283
+ ref
1284
+ }) => {
1208
1285
  const id = useId();
1209
1286
  const errorId = `${id}-error`;
1210
1287
  const field = useField(form, path);
@@ -1248,7 +1325,12 @@ var SelectField = ({
1248
1325
  "aria-describedby": hasError(field.error) ? errorId : void 0,
1249
1326
  children: [
1250
1327
  showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder ?? "" }) : null,
1251
- options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
1328
+ options.map((opt, index) => (
1329
+ // Index-qualified: option lists may legitimately repeat a value
1330
+ // (e.g. differently-labelled aliases), and bare value keys would
1331
+ // collide.
1332
+ /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, `${index}-${opt.value}`)
1333
+ ))
1252
1334
  ]
1253
1335
  }
1254
1336
  ),
@@ -1257,25 +1339,40 @@ var SelectField = ({
1257
1339
  };
1258
1340
 
1259
1341
  // src/react/focusError.ts
1342
+ var focusableControls = (scope) => [
1343
+ ...scope.querySelectorAll(
1344
+ "input[name], select[name], textarea[name]"
1345
+ )
1346
+ ].filter(
1347
+ (el) => !el.matches(':disabled, [type="hidden"]') && el.closest("dialog:not([open])") === null
1348
+ );
1349
+ var focusFirstOf = (candidates) => candidates.some((el) => {
1350
+ el.focus();
1351
+ return document.activeElement === el;
1352
+ });
1353
+ var nameMatchesAny = (el, paths) => {
1354
+ const name = el.getAttribute("name");
1355
+ return name !== null && paths.some((p) => isPathOrChild(name, p));
1356
+ };
1260
1357
  var focusFirstError = (errors, root) => {
1261
1358
  const scope = root ?? document;
1262
1359
  const erroredPaths = Object.keys(errors).filter(
1263
1360
  (k) => k !== "" && (errors[k]?.length ?? 0) > 0
1264
1361
  );
1265
1362
  const hasRootError = (errors[""]?.length ?? 0) > 0;
1266
- const controls = [
1267
- ...scope.querySelectorAll(
1268
- "input[name], select[name], textarea[name]"
1269
- )
1270
- ].filter((el) => !el.matches(':disabled, [type="hidden"]'));
1271
- const fieldMatch = controls.find((el) => {
1272
- const name = el.getAttribute("name");
1273
- return name !== null && erroredPaths.some((k) => isPathOrChild(name, k));
1274
- });
1275
- const target = fieldMatch ?? (hasRootError ? controls[0] : void 0);
1276
- target?.focus();
1277
- return target !== void 0;
1363
+ const controls = focusableControls(scope);
1364
+ const fieldMatches = controls.filter(
1365
+ (el) => nameMatchesAny(el, erroredPaths)
1366
+ );
1367
+ const rootFallbackApplies = hasRootError && (root !== void 0 || document.querySelectorAll("form").length <= 1);
1368
+ const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls : [];
1369
+ return focusFirstOf(candidates);
1278
1370
  };
1371
+ var focusField = (path, root) => focusFirstOf(
1372
+ focusableControls(root ?? document).filter(
1373
+ (el) => nameMatchesAny(el, [path])
1374
+ )
1375
+ );
1279
1376
  var EMPTY_ID_STATE = { items: [], ids: [], counter: 0 };
1280
1377
  var EMPTY_ITEMS = [];
1281
1378
  var reconcileIds = (prev, nextItems) => {
@@ -1381,6 +1478,6 @@ var useFieldArray = (form, pathArg) => {
1381
1478
  );
1382
1479
  };
1383
1480
 
1384
- export { CheckboxField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, flattenIssues, focusFirstError, getAtPath, isAsyncRequiredError, numberInputProps, numberToInputText, parseNumberText, parsePath, selectProps, setAtPath, shouldValidateOn, textInputProps, useField, useFieldArray, useForm, useFormError, useFormSelector, useFormSelectorShallow, useFormState, useFormStateShallow, useIsDirty, useIsSubmitting, useIsValid, useSubmitCount, validateAsync, validateSync };
1481
+ export { CheckboxField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, 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 };
1385
1482
  //# sourceMappingURL=index.js.map
1386
1483
  //# sourceMappingURL=index.js.map