UNPKG

@conform-to/react

Version:

Conform view adapter for react

374 lines 17.6 kB
import type { FormError, FormValue, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future'; import type { StandardSchemaV1 } from './standard-schema'; export type Prettify<T> = { [K in keyof T]: T[K]; } & {}; /** Reference to a form element. Can be either a React ref object or a form ID string. */ export type FormRef = React.RefObject<HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null> | string; export type InputSnapshot = { value?: string; options?: string[]; checked?: boolean; files?: File[]; }; export type Control = { /** * Current value of the base input. Undefined if the registered input * is a multi-select, file input, or checkbox group. */ value: string | undefined; /** * Selected options of the base input. Defined only when the registered input * is a multi-select or checkbox group. */ checked: boolean | undefined; /** * Checked state of the base input. Defined only when the registered input * is a single checkbox or radio input. */ options: string[] | undefined; /** * Selected files of the base input. Defined only when the registered input * is a file input. */ files: File[] | undefined; /** * Registers the base input element(s). Accepts a single input or an array for groups. */ register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLCollectionOf<HTMLInputElement> | NodeListOf<HTMLInputElement> | null | undefined) => void; /** * Programmatically updates the input value and emits * both [change](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) and * [input](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) events. */ change(value: string | string[] | boolean | File | File[] | FileList | null): void; /** * Emits [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) and * [focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event) events. * Does not actually move focus. */ focus(): void; /** * Emits [focus](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event) and * [focusin](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event) events. * This does not move the actual keyboard focus to the input. Use `element.focus()` instead * if you want to move focus to the input. */ blur(): void; }; export type Selector<FormValue, Result> = (formData: FormValue | null, lastResult: Result | undefined) => Result; export type UseFormDataOptions = { /** * Set to `true` to preserve file inputs and receive a `FormData` object in the selector. * If omitted or `false`, the selector receives a `URLSearchParams` object, where all values are coerced to strings. */ acceptFiles?: boolean; }; export type DefaultValue<FormShape> = FormShape extends string | number | boolean | Date | File | bigint | null | undefined ? FormShape | null | undefined : FormShape extends Array<infer Item> | null | undefined ? Array<DefaultValue<Item>> | null | undefined : FormShape extends Record<string, any> | null | undefined ? { [Key in keyof FormShape]?: DefaultValue<FormShape[Key]>; } | null | undefined : unknown; export type FormState<ErrorShape> = { /** Unique identifier that changes on form reset to trigger reset side effects */ resetKey: string; /** Form values from client actions that will be synced to the DOM */ clientIntendedValue: Record<string, unknown> | null; /** Form values from server actions, or submitted values when no server intent exists */ serverIntendedValue: Record<string, unknown> | null; /** Validation errors from server-side processing */ serverError: FormError<ErrorShape> | null; /** Validation errors from client-side validation */ clientError: FormError<ErrorShape> | null; /** Array of field names that have been touched (validated) */ touchedFields: string[]; /** Mapping of array field names to their item keys for React list rendering */ listKeys: Record<string, string[]>; }; export type FormAction<ErrorShape, Intent extends UnknownIntent | null | undefined = UnknownIntent | null, Context = {}> = SubmissionResult<ErrorShape> & { type: 'initialize' | 'server' | 'client'; intent: Intent; ctx: Context; }; export interface FormOptions<FormShape, ErrorShape = string, Value = undefined> { /** Optional form identifier. If not provided, a unique ID is automatically generated. */ id?: string; /** Optional key for form state reset. When the key changes, the form resets to its initial state. */ key?: string; /** Optional standard schema for validation (e.g., Zod, Valibot, Yup). Removes the need for manual onValidate setup. */ schema?: StandardSchemaV1<FormShape, Value>; /** Initial form values. Can be a partial object matching your form structure. */ defaultValue?: NoInfer<DefaultValue<FormShape>>; /** HTML validation attributes for fields (required, minLength, pattern, etc.). */ constraint?: Record<string, ValidationAttributes>; /** * Define when conform should start validation. * Support "onSubmit", "onInput", "onBlur". * * @default "onSubmit" */ shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput'; /** * Define when conform should revalidate again. * Support "onSubmit", "onInput", "onBlur". * * @default Same as shouldValidate, or "onSubmit" if shouldValidate is not provided. */ shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput'; /** Server-side submission result for form state synchronization. */ lastResult?: SubmissionResult<NoInfer<ErrorShape>> | null; /** Custom validation handler. Can be skipped if using the schema property, or combined with schema to customize validation errors. */ onValidate?: ValidateHandler<ErrorShape, Value>; /** Error handling callback triggered when validation errors occur. By default, it focuses the first invalid field. */ onError?: ErrorHandler<ErrorShape>; /** Form submission handler called when the form is submitted with no validation errors. */ onSubmit?: SubmitHandler<NoInfer<ErrorShape>, NoInfer<Value>>; /** Input event handler for custom input event logic. */ onInput?: InputHandler; /** Blur event handler for custom focus handling logic. */ onBlur?: BlurHandler; } export interface FormContext<ErrorShape = string> { /** The form's unique identifier */ formId: string; /** Internal form state with validation results and field data */ state: FormState<ErrorShape>; /** Initial form values */ defaultValue: NonNullable<DefaultValue<Record<string, any>>> | null; /** HTML validation attributes for fields */ constraint: Record<string, ValidationAttributes> | null; /** Form submission event handler */ handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void; /** Input event handler for form-wide input events */ handleInput: (event: React.FormEvent) => void; /** Blur event handler for form-wide blur events */ handleBlur: (event: React.FocusEvent) => void; } /** The name of an input field with type information for TypeScript inference. */ export type FieldName<FieldShape> = string & { '~shape'?: FieldShape; }; export type UnknownIntent = { type: string; payload?: unknown; }; export type UnknownArgs<Args extends any[]> = { [Key in keyof Args]: unknown; }; export interface IntentDispatcher { /** * Validate the whole form or a specific field? */ validate(name?: string): void; /** * Reset the form to its initial state. */ reset(): void; /** * Update a field or a fieldset. * If you provide a fieldset name, it will update all fields within that fieldset */ update<FieldShape>(options: { /** * The name of the field. If you provide a fieldset name, it will update all fields within that fieldset. */ name?: FieldName<FieldShape>; /** * Specify the index of the item to update if the field is an array. */ index?: [FieldShape] extends [Array<any> | null | undefined] ? number : never; /** * The new value for the field or fieldset. */ value: Partial<FieldShape>; }): void; /** * Insert a new item into an array field. */ insert<FieldShape extends Array<any>>(options: { /** * The name of the array field to insert into. */ name: FieldName<FieldShape>; /** * The index at which to insert the new item. * If not provided, it will be added to the end of the array. */ index?: number; /** * The default value for the new item. */ defaultValue?: [FieldShape] extends [ Array<infer ItemShape> | null | undefined ] ? Partial<ItemShape> : never; }): void; /** * Remove an item from an array field. */ remove(options: { /** * The name of the array field to remove from. */ name: FieldName<Array<any>>; /** * The index of the item to remove. */ index: number; }): void; /** * Reorder items in an array field. */ reorder(options: { name: FieldName<Array<any>>; from: number; to: number; }): void; } export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> = { [Type in keyof Dispatcher]: Dispatcher[Type] extends (...args: infer Args) => void ? { type: Type; payload: Args extends [infer Payload] ? Payload : undefined; } : never; }[keyof Dispatcher]; export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = { validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean; onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | null; onUpdate?<ErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, { type: string; payload: Signature extends (payload: infer Payload) => void ? Payload : undefined; }>): FormState<ErrorShape>; }; type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never; export type Combine<T> = { [K in keyof BaseCombine<T>]: BaseCombine<T>[K]; }; /** Field metadata object containing field state, validation attributes, and nested field access methods. */ export type FieldMetadata<FieldShape, Metadata extends Record<string, unknown> = DefaultMetadata<unknown>> = Readonly<Metadata & { /** Unique key for React list rendering (for array fields). */ key: string | undefined; /** The field name path exactly as provided. */ name: FieldName<FieldShape>; /** Method to get nested fieldset for object fields under this field. */ getFieldset(): Fieldset<[ FieldShape ] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, Metadata>; /** Method to get array of fields for list/array fields under this field. */ getFieldList(): Array<FieldMetadata<[ FieldShape ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, Metadata>>; }>; /** Fieldset object containing all form fields as properties with their respective field metadata. */ export type Fieldset<FieldShape, // extends Record<string, unknown>, Metadata extends Record<string, unknown>> = { [Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], Metadata>; }; /** Form-level metadata and state object containing validation status, errors, and field access methods. */ export type FormMetadata<ErrorShape, Metadata extends Record<string, unknown> = DefaultMetadata<ErrorShape>> = Readonly<{ /** Unique identifier that changes on form reset */ key: string; /** The form's unique identifier. */ id: string; /** Auto-generated ID for associating form descriptions via aria-describedby. */ descriptionId: string; /** Auto-generated ID for associating form errors via aria-describedby. */ errorId: string; /** Whether any field in the form has been touched (through intent.validate() or the shouldValidate option). */ touched: boolean; /** Whether the form currently has no validation errors. */ valid: boolean; /** @deprecated Use `.valid` instead. This was not an intentional breaking change and would be removed in the next minor version soon */ invalid: boolean; /** Form-level validation errors, if any exist. */ errors: ErrorShape[] | undefined; /** Object containing errors for all touched fields. */ fieldErrors: Record<string, ErrorShape[]>; /** Form props object for spreading onto the <form> element. */ props: Readonly<{ id: string; onSubmit: React.FormEventHandler<HTMLFormElement>; onBlur: React.FocusEventHandler<HTMLFormElement>; onInput: React.FormEventHandler<HTMLFormElement>; noValidate: boolean; }>; /** The current state of the form */ context: FormContext<ErrorShape>; /** Method to get metadata for a specific field by name. */ getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, Metadata>; /** Method to get a fieldset object for nested object fields. */ getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<[ FieldShape ] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, Metadata>; /** Method to get an array of field objects for array fields. */ getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[ FieldShape ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, Metadata>>; }>; /** Default field metadata object containing field state, validation attributes, and accessibility IDs. */ export type DefaultMetadata<ErrorShape> = Readonly<ValidationAttributes & { /** The field's unique identifier, automatically generated as {formId}-field-{fieldName}. */ id: string; /** Auto-generated ID for associating field descriptions via aria-describedby. */ descriptionId: string; /** Auto-generated ID for associating field errors via aria-describedby. */ errorId: string; /** The form's unique identifier for associating field via the `form` attribute. */ formId: string; /** The field's default value as a string. */ defaultValue: string | undefined; /** Default selected options for multi-select fields or checkbox group. */ defaultOptions: string[] | undefined; /** Default checked state for checkbox/radio inputs. */ defaultChecked: boolean | undefined; /** Whether this field has been touched (through intent.validate() or the shouldValidate option). */ touched: boolean; /** Whether this field currently has no validation errors. */ valid: boolean; /** @deprecated Use `.valid` instead. This was not an intentionl breaking change and would be removed in the next minor version soon */ invalid: boolean; /** Array of validation error messages for this field. */ errors: ErrorShape[] | undefined; /** Object containing errors for all touched subfields. */ fieldErrors: Record<string, ErrorShape[]>; }>; export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | { error: FormError<ErrorShape> | null; value?: Value; }; export type ValidateContext = { payload: Record<string, FormValue>; error: FormError<string>; intent: UnknownIntent | null; formData: FormData; formElement: HTMLFormElement; submitter: HTMLElement | null; }; export type ValidateHandler<ErrorShape, Value> = (ctx: ValidateContext) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [ ValidateResult<ErrorShape, Value> | undefined, Promise<ValidateResult<ErrorShape, Value>> | undefined ] | undefined; export interface FormInputEvent extends React.FormEvent<HTMLFormElement> { currentTarget: EventTarget & HTMLFormElement; target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement); } export interface FormFocusEvent extends React.FormEvent<HTMLFormElement> { currentTarget: EventTarget & HTMLFormElement; relatedTarget: EventTarget | null; target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement); } export type ErrorContext<ErrorShape> = { formElement: HTMLFormElement; error: FormError<ErrorShape>; intent: UnknownIntent | null; }; export type ErrorHandler<ErrorShape> = (ctx: ErrorContext<ErrorShape>) => void; export type InputHandler = (event: FormInputEvent) => void; export type BlurHandler = (event: FormFocusEvent) => void; export type SubmitContext<ErrorShape = string, Value = undefined> = { formData: FormData; value: Value; update: (options: { error?: Partial<FormError<ErrorShape>> | null; reset?: boolean; }) => void; }; export type SubmitHandler<ErrorShape = string, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<ErrorShape, Value>) => void | Promise<void>; export {}; //# sourceMappingURL=types.d.ts.map