formstand 0.3.0 → 0.4.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/CHANGELOG.md +110 -0
- package/README.md +26 -4
- package/dist/index.cjs +169 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -28
- package/dist/index.d.ts +26 -28
- package/dist/index.js +168 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
230
|
-
path:
|
|
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:
|
|
240
|
-
path:
|
|
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:
|
|
248
|
-
path:
|
|
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:
|
|
259
|
-
path:
|
|
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,
|
|
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:
|
|
230
|
-
path:
|
|
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:
|
|
240
|
-
path:
|
|
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:
|
|
248
|
-
path:
|
|
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:
|
|
259
|
-
path:
|
|
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,
|
|
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,17 +260,19 @@ 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
|
-
|
|
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
|
};
|
|
240
274
|
var FORM_SEQ_KEY = "__form__";
|
|
275
|
+
var SUBMIT_SEQ_KEY = "__submit__";
|
|
241
276
|
var stringArraysEqual = (a, b) => a === b || a.length === b.length && a.every((v, i) => v === b[i]);
|
|
242
277
|
var reuseErrorRefs = (next, prev) => {
|
|
243
278
|
if (next === prev) return prev;
|
|
@@ -357,6 +392,16 @@ var createForm = (schema, options) => {
|
|
|
357
392
|
subschemaCache.set(path, sub);
|
|
358
393
|
return sub;
|
|
359
394
|
};
|
|
395
|
+
const checkedSchemaPaths = /* @__PURE__ */ new Set();
|
|
396
|
+
const warnMissingSchemaPath = (fn, path) => {
|
|
397
|
+
if (path === "" || checkedSchemaPaths.has(path)) return;
|
|
398
|
+
checkedSchemaPaths.add(path);
|
|
399
|
+
if (!schemaHasPath(schema, path)) {
|
|
400
|
+
console.warn(
|
|
401
|
+
`[formstand] ${fn}("${path}"): path does not exist in the schema \u2014 result will always be valid.`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
360
405
|
const fieldScopeFor = (path, values) => {
|
|
361
406
|
const sub = path === "" ? null : subschemaAt(path);
|
|
362
407
|
if (sub === null) {
|
|
@@ -394,6 +439,7 @@ var createForm = (schema, options) => {
|
|
|
394
439
|
serverErrors: omitScope(state.serverErrors, [path])
|
|
395
440
|
});
|
|
396
441
|
const validateField = (path) => {
|
|
442
|
+
warnMissingSchemaPath("validateField", path);
|
|
397
443
|
try {
|
|
398
444
|
const scoped = scopedFieldErrors(path);
|
|
399
445
|
store.setState((state) => ({
|
|
@@ -419,18 +465,18 @@ var createForm = (schema, options) => {
|
|
|
419
465
|
};
|
|
420
466
|
};
|
|
421
467
|
const sequences = /* @__PURE__ */ new Map();
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
sequences.set(key,
|
|
425
|
-
return
|
|
468
|
+
const claimPass = (key) => {
|
|
469
|
+
const pass = Symbol(key);
|
|
470
|
+
sequences.set(key, pass);
|
|
471
|
+
return pass;
|
|
426
472
|
};
|
|
427
473
|
const validateAsyncOnForm = async () => {
|
|
428
|
-
const
|
|
474
|
+
const pass = claimPass(FORM_SEQ_KEY);
|
|
429
475
|
const valuesAtStart = store.getState().values;
|
|
430
476
|
store.setState((state) => ({ ...state, isValidatingForm: true }));
|
|
431
477
|
const result = await validateAsync(schema, valuesAtStart);
|
|
432
478
|
const live = store.getState();
|
|
433
|
-
const stillOwns = sequences.get(FORM_SEQ_KEY) ===
|
|
479
|
+
const stillOwns = sequences.get(FORM_SEQ_KEY) === pass;
|
|
434
480
|
if (!stillOwns || live.values !== valuesAtStart) {
|
|
435
481
|
if (stillOwns) {
|
|
436
482
|
store.setState((state) => ({ ...state, isValidatingForm: false }));
|
|
@@ -453,7 +499,7 @@ var createForm = (schema, options) => {
|
|
|
453
499
|
const result2 = await validateAsyncOnForm();
|
|
454
500
|
return result2.kind === "valid";
|
|
455
501
|
}
|
|
456
|
-
const
|
|
502
|
+
const passes = new Map(paths.map((p) => [p, claimPass(p)]));
|
|
457
503
|
const valuesAtStart = store.getState().values;
|
|
458
504
|
store.setState((state) => ({
|
|
459
505
|
...state,
|
|
@@ -468,7 +514,9 @@ var createForm = (schema, options) => {
|
|
|
468
514
|
(k) => !inScopeOfAny(k, paths)
|
|
469
515
|
);
|
|
470
516
|
const live = store.getState();
|
|
471
|
-
const stillCurrent = paths.filter(
|
|
517
|
+
const stillCurrent = paths.filter(
|
|
518
|
+
(p) => sequences.get(p) === passes.get(p)
|
|
519
|
+
);
|
|
472
520
|
if (live.values !== valuesAtStart) {
|
|
473
521
|
if (stillCurrent.length > 0) {
|
|
474
522
|
store.setState((state) => ({
|
|
@@ -502,7 +550,14 @@ var createForm = (schema, options) => {
|
|
|
502
550
|
return requestedValid;
|
|
503
551
|
};
|
|
504
552
|
const validateFieldAsync = async (path) => {
|
|
505
|
-
|
|
553
|
+
warnMissingSchemaPath("validateFieldAsync", path);
|
|
554
|
+
if (path === "") {
|
|
555
|
+
const result = await validateAsyncOnForm();
|
|
556
|
+
return fieldResult(
|
|
557
|
+
result.kind === "invalid" ? result.errors : emptyErrors
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const pass = claimPass(path);
|
|
506
561
|
const valuesAtStart = store.getState().values;
|
|
507
562
|
store.setState((state) => ({
|
|
508
563
|
...state,
|
|
@@ -510,7 +565,7 @@ var createForm = (schema, options) => {
|
|
|
510
565
|
}));
|
|
511
566
|
const scoped = await scopedFieldErrorsAsync(path, valuesAtStart);
|
|
512
567
|
const live = store.getState();
|
|
513
|
-
const stillOwns = sequences.get(path) ===
|
|
568
|
+
const stillOwns = sequences.get(path) === pass;
|
|
514
569
|
if (!stillOwns || live.values !== valuesAtStart) {
|
|
515
570
|
if (stillOwns) {
|
|
516
571
|
store.setState((state) => ({
|
|
@@ -561,7 +616,13 @@ var createForm = (schema, options) => {
|
|
|
561
616
|
)
|
|
562
617
|
}),
|
|
563
618
|
touched: reKeyByArrayPath(state.touched, path, mapper),
|
|
564
|
-
|
|
619
|
+
// Drop (don't re-key) in-flight flags under the array: the op changes
|
|
620
|
+
// `values`, so every pass they belong to fails its values-changed
|
|
621
|
+
// guard and never commits — but each pass's cleanup omits its
|
|
622
|
+
// ORIGINAL key, so a re-keyed entry would be orphaned and stick
|
|
623
|
+
// forever. Dropped, the flag is gone now and the cleanup is a
|
|
624
|
+
// harmless no-op.
|
|
625
|
+
isValidating: omitScope(state.isValidating, [path])
|
|
565
626
|
};
|
|
566
627
|
});
|
|
567
628
|
};
|
|
@@ -583,33 +644,44 @@ var createForm = (schema, options) => {
|
|
|
583
644
|
submitCount: state.submitCount + 1
|
|
584
645
|
}));
|
|
585
646
|
try {
|
|
586
|
-
const
|
|
647
|
+
const pass = claimPass(SUBMIT_SEQ_KEY);
|
|
648
|
+
const valuesAtStart = store.getState().values;
|
|
649
|
+
const result = await validateAsync(schema, valuesAtStart);
|
|
650
|
+
const stale = sequences.get(SUBMIT_SEQ_KEY) !== pass || store.getState().values !== valuesAtStart;
|
|
587
651
|
if (result.kind === "invalid") {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
Object.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
652
|
+
if (!stale) {
|
|
653
|
+
store.setState((state) => {
|
|
654
|
+
const committed = commitFullPass(state, result.errors);
|
|
655
|
+
return {
|
|
656
|
+
...state,
|
|
657
|
+
...committed,
|
|
658
|
+
// Mark every errored field touched so touched-gated error UIs
|
|
659
|
+
// show something after the canonical first failed submit. The
|
|
660
|
+
// "" key (schema-level refine) has no field to touch.
|
|
661
|
+
touched: {
|
|
662
|
+
...state.touched,
|
|
663
|
+
...Object.fromEntries(
|
|
664
|
+
Object.keys(committed.errors).filter((k) => k !== "").map((k) => [k, true])
|
|
665
|
+
)
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
const merged = stale ? result.errors : store.getState().errors;
|
|
605
671
|
onInvalid?.(merged);
|
|
606
672
|
return { kind: "invalid", errors: merged };
|
|
607
673
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
674
|
+
if (!stale) {
|
|
675
|
+
store.setState((state) => ({
|
|
676
|
+
...state,
|
|
677
|
+
...commitFullPass(state, emptyErrors)
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
await onValid(result.data);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
return { kind: "error", error };
|
|
684
|
+
}
|
|
613
685
|
return { kind: "valid", data: result.data };
|
|
614
686
|
} finally {
|
|
615
687
|
inFlight.count -= 1;
|
|
@@ -741,7 +813,12 @@ var createForm = (schema, options) => {
|
|
|
741
813
|
...errorChannels(state, {
|
|
742
814
|
schemaErrors: emptyErrors,
|
|
743
815
|
serverErrors: emptyErrors
|
|
744
|
-
})
|
|
816
|
+
}),
|
|
817
|
+
// Clearing `sequences` disowns every in-flight pass, including its
|
|
818
|
+
// flag cleanup (the ownership check fails, so it never writes) —
|
|
819
|
+
// clear the flags here too, mirroring reset(), or they'd stick.
|
|
820
|
+
isValidating: emptyBools,
|
|
821
|
+
isValidatingForm: false
|
|
745
822
|
}));
|
|
746
823
|
},
|
|
747
824
|
reset: (nextInitial, resetOptions) => {
|
|
@@ -847,7 +924,13 @@ var createForm = (schema, options) => {
|
|
|
847
924
|
errors: reuseErrorRefs(
|
|
848
925
|
mergeErrorChannels(schemaErrors, serverErrors),
|
|
849
926
|
snap.errors ?? emptyErrors
|
|
850
|
-
)
|
|
927
|
+
),
|
|
928
|
+
// In-flight state is owned by live passes, never by snapshots: a
|
|
929
|
+
// snapshotted flag has no pass left to clear it (the pass that set
|
|
930
|
+
// it cleared its ORIGINAL entry when it settled), so restoring one
|
|
931
|
+
// would stick it forever.
|
|
932
|
+
isValidating: emptyBools,
|
|
933
|
+
isValidatingForm: false
|
|
851
934
|
};
|
|
852
935
|
})
|
|
853
936
|
});
|
|
@@ -906,8 +989,6 @@ function useFormSelector(form, selector) {
|
|
|
906
989
|
function useFormSelectorShallow(form, selector) {
|
|
907
990
|
return useStore(form.store, useShallow(selector));
|
|
908
991
|
}
|
|
909
|
-
var useFormState = useFormSelector;
|
|
910
|
-
var useFormStateShallow = useFormSelectorShallow;
|
|
911
992
|
var useFormError = (form) => useStore(
|
|
912
993
|
form.store,
|
|
913
994
|
useShallow((state) => state.errors[""])
|
|
@@ -1005,7 +1086,10 @@ function useField(form, pathArg, options) {
|
|
|
1005
1086
|
[form, path]
|
|
1006
1087
|
);
|
|
1007
1088
|
const setError = useCallback(
|
|
1008
|
-
|
|
1089
|
+
// Normalize the string shorthand HERE, before forwarding: custom
|
|
1090
|
+
// FieldFormApi implementations written against the pre-0.4 surface are
|
|
1091
|
+
// typed for readonly string[] and must never receive a bare string.
|
|
1092
|
+
(errors) => form.setError(path, typeof errors === "string" ? [errors] : errors),
|
|
1009
1093
|
[form, path]
|
|
1010
1094
|
);
|
|
1011
1095
|
const clearError = useCallback(
|
|
@@ -1204,7 +1288,12 @@ var NumberField = ({
|
|
|
1204
1288
|
/* @__PURE__ */ jsx(ErrorText, { id: errorId, error: field.error })
|
|
1205
1289
|
] });
|
|
1206
1290
|
};
|
|
1207
|
-
var CheckboxField = ({
|
|
1291
|
+
var CheckboxField = ({
|
|
1292
|
+
form,
|
|
1293
|
+
path,
|
|
1294
|
+
label,
|
|
1295
|
+
ref
|
|
1296
|
+
}) => {
|
|
1208
1297
|
const id = useId();
|
|
1209
1298
|
const errorId = `${id}-error`;
|
|
1210
1299
|
const field = useField(form, path);
|
|
@@ -1248,7 +1337,12 @@ var SelectField = ({
|
|
|
1248
1337
|
"aria-describedby": hasError(field.error) ? errorId : void 0,
|
|
1249
1338
|
children: [
|
|
1250
1339
|
showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder ?? "" }) : null,
|
|
1251
|
-
options.map((opt) =>
|
|
1340
|
+
options.map((opt, index) => (
|
|
1341
|
+
// Index-qualified: option lists may legitimately repeat a value
|
|
1342
|
+
// (e.g. differently-labelled aliases), and bare value keys would
|
|
1343
|
+
// collide.
|
|
1344
|
+
/* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, `${index}-${opt.value}`)
|
|
1345
|
+
))
|
|
1252
1346
|
]
|
|
1253
1347
|
}
|
|
1254
1348
|
),
|
|
@@ -1257,24 +1351,43 @@ var SelectField = ({
|
|
|
1257
1351
|
};
|
|
1258
1352
|
|
|
1259
1353
|
// src/react/focusError.ts
|
|
1354
|
+
var focusableControls = (scope) => [
|
|
1355
|
+
...scope.querySelectorAll(
|
|
1356
|
+
"input[name], select[name], textarea[name]"
|
|
1357
|
+
)
|
|
1358
|
+
].filter(
|
|
1359
|
+
(el) => !el.matches(':disabled, [type="hidden"]') && el.closest("dialog:not([open])") === null
|
|
1360
|
+
);
|
|
1361
|
+
var holdsFocus = (el) => {
|
|
1362
|
+
const rootNode = el.getRootNode();
|
|
1363
|
+
const active = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : document.activeElement;
|
|
1364
|
+
return active === el;
|
|
1365
|
+
};
|
|
1366
|
+
var focusFirstOf = (candidates) => candidates.some((el) => {
|
|
1367
|
+
el.focus();
|
|
1368
|
+
return holdsFocus(el);
|
|
1369
|
+
});
|
|
1370
|
+
var nameMatchesAny = (el, paths) => {
|
|
1371
|
+
const name = el.getAttribute("name");
|
|
1372
|
+
return name !== null && paths.some((p) => isPathOrChild(name, p));
|
|
1373
|
+
};
|
|
1260
1374
|
var focusFirstError = (errors, root) => {
|
|
1261
1375
|
const scope = root ?? document;
|
|
1262
1376
|
const erroredPaths = Object.keys(errors).filter(
|
|
1263
1377
|
(k) => k !== "" && (errors[k]?.length ?? 0) > 0
|
|
1264
1378
|
);
|
|
1265
1379
|
const hasRootError = (errors[""]?.length ?? 0) > 0;
|
|
1266
|
-
const controls =
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
return target !== void 0;
|
|
1380
|
+
const controls = focusableControls(scope);
|
|
1381
|
+
const fieldMatches = controls.filter(
|
|
1382
|
+
(el) => nameMatchesAny(el, erroredPaths)
|
|
1383
|
+
);
|
|
1384
|
+
const rootFallbackApplies = hasRootError && (root !== void 0 || document.querySelectorAll("form").length <= 1);
|
|
1385
|
+
const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls : [];
|
|
1386
|
+
return focusFirstOf(candidates);
|
|
1387
|
+
};
|
|
1388
|
+
var focusField = (path, root) => {
|
|
1389
|
+
const controls = focusableControls(root ?? document);
|
|
1390
|
+
return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(controls) : focusFirstOf(controls.filter((el) => nameMatchesAny(el, [path])));
|
|
1278
1391
|
};
|
|
1279
1392
|
var EMPTY_ID_STATE = { items: [], ids: [], counter: 0 };
|
|
1280
1393
|
var EMPTY_ITEMS = [];
|
|
@@ -1381,6 +1494,6 @@ var useFieldArray = (form, pathArg) => {
|
|
|
1381
1494
|
);
|
|
1382
1495
|
};
|
|
1383
1496
|
|
|
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,
|
|
1497
|
+
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
1498
|
//# sourceMappingURL=index.js.map
|
|
1386
1499
|
//# sourceMappingURL=index.js.map
|