@conform-to/react
Version:
Conform view adapter for react
854 lines • 39.6 kB
TypeScript
import type { CustomSerialize, FieldName, FormError, FormValue, Serialize, 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 DefaultControlValue = string | string[] | File | File[] | FileList;
export type Control<Value = DefaultControlValue, DefaultValue = Value, Payload = unknown> = {
/**
* Current string value derived from the control payload.
*/
value: string | undefined;
/**
* Checked state derived from the control payload.
*/
checked: boolean | undefined;
/**
* Current string array derived from the control payload.
*/
options: string[] | undefined;
/**
* Current file array derived from the control payload.
*/
files: File[] | undefined;
/**
* The rendered payload used as the source for base control(s).
*
* For simple native controls, this mirrors `defaultValue` / `defaultChecked`.
* For structural controls (i.e. `<fieldset>`), this is the latest payload
* snapshot that drives which hidden inputs are rendered.
*/
defaultValue: DefaultValue | null | undefined;
/**
* Current payload snapshot derived from the registered base control(s).
*
* For structural controls (i.e. `<fieldset>`), this is reconstructed from
* descendant fields under the registered fieldset name.
*/
payload: Payload | null | undefined;
/**
* Registers the base control element.
*
* Accepts `<input>`, `<select>`, `<textarea>`, `<fieldset>`,
* or a collection of checkbox / radio inputs with the same name.
*/
register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement | HTMLCollectionOf<HTMLInputElement> | NodeListOf<HTMLInputElement> | null | undefined) => void;
/**
* A ref object containing the form element associated with the registered base control.
* Use this with hooks like useFormData() and useIntent().
*/
formRef: React.RefObject<HTMLFormElement | null>;
/**
* 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: Value | null) => 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 [HTMLElement.focus()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)
* if you want to move focus to the input.
*/
focus: () => 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.
*
* This does not move the actual keyboard focus away from the input.
*/
blur: () => void;
};
export type StandardControlOptions<Value extends DefaultControlValue = DefaultControlValue> = {
/**
* The initial value of the base control.
*/
defaultValue?: Value | null | undefined;
/**
* A callback function that is triggered when the base control is focused.
* Use this to delegate focus to a custom input.
*/
onFocus?: () => void;
};
export type CheckedControlOptions = {
/**
* Whether the base control should be checked by default.
*/
defaultChecked?: boolean | undefined;
/**
* The value of a checkbox or radio control when checked.
*/
value?: string;
/**
* A callback function that is triggered when the base control is focused.
* Use this to delegate focus to a custom input.
*/
onFocus?: () => void;
};
export type CustomControlOptions<Value = unknown, DefaultValue = Value> = {
/**
* Initial value used to seed the control.
* For structural controls, this is the payload used to render hidden inputs.
*/
defaultValue?: DefaultValue | null | undefined;
/**
* Payload parser applied to the current payload snapshot.
*
* Use this to coerce unknown DOM-derived data into a typed shape.
* Any thrown error is surfaced to the caller.
*/
parse: (payload: unknown) => Value | null;
/**
* Optional serializer to convert the parsed payload back to a form value for populating the base control(s).
*/
serialize?: (value: Value) => FormValue;
/**
* A callback function that is triggered when the base control is focused.
* Use this to delegate focus to a custom input.
*/
onFocus?: () => void;
};
export type ControlOptions = StandardControlOptions | CheckedControlOptions | CustomControlOptions;
export type Selector<FormValue, Result> = (formData: FormValue, lastResult: Result | undefined) => Result;
export type UseFormDataOptions<Value = undefined> = {
/**
* 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;
/**
* The fallback value to return when the form element is not available (e.g., on SSR or initial client render).
* If not provided, the hook returns `undefined` when the form is unavailable.
*/
fallback?: Value;
};
export type DefaultValue<Shape> = Shape extends Record<string, any> ? {
[Key in keyof Shape]?: DefaultValue<Shape[Key]>;
} | null | undefined : Shape extends Array<infer Item> ? Array<DefaultValue<Item>> | null | undefined : Shape extends File | File[] ? null | undefined : Shape | string | null | undefined;
export type FormState<ErrorShape = any> = {
/** Unique identifier that changes on form reset to trigger reset side effects */
resetKey: string;
/** Initial form values */
defaultValue: Record<string, unknown>;
/** Form values that will be synced to the DOM */
targetValue: Record<string, unknown> | null;
/** Form values from server actions, or submitted values when no server intent exists */
serverValue: 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 = any, Intent extends UnknownIntent | null | undefined = UnknownIntent | null, Context = {}> = SubmissionResult<ErrorShape> & {
type: 'initialize' | 'server' | 'client';
intent: Intent;
ctx: Context;
};
/**
* Augment this interface to customize schema type inference for your schema library.
*
* **Example:**
* ```ts
* import type { ZodTypeAny, input, output } from 'zod';
* import type { ZodSchemaOptions } from '@conform-to/zod/v3/future';
*
* declare module '@conform-to/react/future' {
* interface CustomSchemaTypes<Schema> {
* input: Schema extends ZodTypeAny ? input<Schema> : never;
* output: Schema extends ZodTypeAny ? output<Schema> : never;
* options: Schema extends ZodTypeAny ? ZodSchemaOptions : never;
* }
* }
* ```
*/
export interface CustomSchemaTypes<Schema = unknown> {
}
/**
* Infer schema options type.
* Uses CustomSchemaTypes if augmented, otherwise returns never.
*/
export type InferOptions<Schema> = [Schema] extends [undefined] ? never : CustomSchemaTypes<Schema> extends {
options: infer T;
} ? T : never;
/**
* Marker type for conditional field metadata properties.
* Used to indicate that a property should only be present when FieldShape matches a condition.
*/
export type ConditionalFieldMetadata<T, Condition> = T & {
'~condition': Condition;
};
/**
* Check if T is a FieldName type by checking if '~shape' is a known key.
* Plain strings don't have '~shape' in their keyof, but FieldName<T> does.
*/
type IsFieldName<T> = '~shape' extends keyof T ? true : false;
/**
* Transforms a single value, restoring FieldName<unknown> to FieldName<FieldShape>.
*/
type RestoreFieldShapeValue<T, FieldShape> = T extends ConditionalFieldMetadata<infer Inner, infer Condition> ? FieldShape extends Condition ? Inner : never : IsFieldName<T> extends true ? FieldName<FieldShape> : T;
/**
* Restores FieldShape-dependent types that were inferred as `unknown`.
* This is needed because TypeScript infers generic return types with unresolved type parameters.
* Also handles conditional field metadata by checking if FieldShape extends the condition.
*
* Transforms up to 2 levels deep to handle cases like `textFieldProps.name`.
*/
export type RestoreFieldShape<T, FieldShape> = {
[K in keyof T]: RestoreFieldShapeValue<T[K], FieldShape> extends infer V ? V extends Record<string, unknown> ? {
[P in keyof V]: RestoreFieldShapeValue<V[P], FieldShape>;
} : V : never;
};
/**
* Type guard function for conditional field metadata.
* Used to specify when a custom field metadata property should be available.
*/
export type FieldShapeGuard<Condition> = (shape: unknown) => shape is Condition;
/**
* Function type for creating conditional field metadata based on shape constraints.
*/
export type DefineConditionalField = <FieldShape, ErrorShape, Metadata>(metadata: BaseFieldMetadata<unknown, ErrorShape>, shape: FieldShapeGuard<FieldShape>, fn: (metadata: BaseFieldMetadata<FieldShape, ErrorShape>) => Metadata) => ConditionalFieldMetadata<Metadata, FieldShape>;
/**
* Extract the condition type from a FieldShapeGuard.
*/
export type ExtractFieldCondition<T> = T extends FieldShapeGuard<infer C> ? C : never;
/**
* Extract conditions from a record of field shape guards.
*/
export type ExtractFieldConditions<T extends Record<string, FieldShapeGuard<any>>> = {
[K in keyof T]: ExtractFieldCondition<T[K]>;
};
/**
* Resolved configuration from configureForms factory.
* Properties with defaults are required, others remain optional.
*/
export type FormsConfig<BaseErrorShape, BaseSchema, SchemaErrorShape, CustomFormMetadata extends Record<string, unknown>, CustomFieldMetadata extends Record<string, unknown>> = {
/**
* The name of the submit button field that indicates the submission intent.
* @default "__intent__"
*/
intentName: string;
/**
* A custom serializer for converting form values.
*/
serialize?: CustomSerialize | undefined;
/**
* Determines when validation should run for the first time on a field.
* @default "onSubmit"
*/
shouldValidate: 'onSubmit' | 'onBlur' | 'onInput';
/**
* Determines when validation should run again after the field has been validated once.
* @default Same as shouldValidate
*/
shouldRevalidate: 'onSubmit' | 'onBlur' | 'onInput';
/**
* Runtime type guard to check if a value is a schema.
* Used to determine if the first argument to useForm is a schema or options object.
*
* **Example:**
* ```ts
* import { configureForms } from '@conform-to/react/future';
* import {
* isSchema,
* validateSchema,
* getConstraints,
* } from '@conform-to/zod/v3/future';
*
* const { useForm } = configureForms({
* isSchema,
* validateSchema,
* getConstraints,
* });
* ```
*/
isSchema: (schema: unknown) => schema is BaseSchema;
/**
* Validates a schema against form payload.
*/
validateSchema: <Schema extends BaseSchema>(schema: Schema, payload: Record<string, FormValue>, options?: InferOptions<Schema>) => MaybePromise<{
error: FormError<SchemaErrorShape> | null;
value?: InferOutput<Schema>;
}>;
/**
* A type guard function to specify the shape of error objects.
*/
isError?: (error: unknown) => error is BaseErrorShape;
/**
* Extracts HTML validation constraints from a schema.
*/
getConstraints?: <Schema extends BaseSchema>(schema: Schema) => Record<string, ValidationAttributes> | undefined;
/**
* Extends form metadata with custom properties.
*/
extendFormMetadata?: <ErrorShape extends BaseErrorShape>(metadata: BaseFormMetadata<ErrorShape>) => CustomFormMetadata;
/**
* Extends field metadata with custom properties.
* Use `when` for properties that depend on the field shape.
*/
extendFieldMetadata?: <FieldShape, ErrorShape extends BaseErrorShape>(metadata: BaseFieldMetadata<FieldShape, ErrorShape>, ctx: {
form: BaseFormMetadata<ErrorShape>;
when: DefineConditionalField;
}) => CustomFieldMetadata;
};
export type NonPartial<T> = {
[K in keyof Required<T>]: T[K];
};
export type RequireKey<T, K extends keyof T> = Prettify<Omit<T, K> & {
[P in K]-?: Exclude<T[P], undefined>;
}>;
export type BaseSchemaType = StandardSchemaV1<any, any>;
/**
* Infer schema input type.
* For StandardSchemaV1 schemas (zod, valibot, etc.), uses StandardSchemaV1.InferInput.
* For other schemas, uses CustomSchemaTypes if augmented.
*/
export type InferInput<Schema> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<Schema> : CustomSchemaTypes<Schema> extends {
input: infer T;
} ? T : Record<string, any>;
export type InferFormShape<Schema> = InferInput<Schema> extends Record<string, any> ? InferInput<Schema> : Record<string, any>;
/**
* Infer schema output type.
* For StandardSchemaV1 schemas (zod, valibot, etc.), uses StandardSchemaV1.InferOutput.
* For other schemas, uses CustomSchemaTypes if augmented.
*/
export type InferOutput<Schema> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Schema> : CustomSchemaTypes<Schema> extends {
output: infer T;
} ? T : undefined;
export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = any, Value = undefined, Schema = unknown, SchemaErrorShape = ErrorShape> = {
/** Optional form identifier. If not provided, a unique ID is automatically generated. */
id?: string | undefined;
/** Optional key for form state reset. When the key changes, the form resets to its initial state. */
key?: string | undefined;
/** Server-side submission result for form state synchronization. */
lastResult?: SubmissionResult<ErrorShape> | null | undefined;
/** Form submission handler called when the form is submitted with no validation errors. */
onSubmit?: SubmitHandler<FormShape, NoInfer<ErrorShape>, NoInfer<Value>> | undefined;
/** Initial form values. Can be a partial object matching your form structure. */
defaultValue?: DefaultValue<FormShape> | undefined;
/**
* Override serialization for specific fields on this form and delegate the rest
* to the configured global serializer.
*/
serialize?: CustomSerialize | undefined;
/** HTML validation attributes for fields (required, minLength, pattern, etc.). */
constraint?: Record<string, ValidationAttributes> | undefined;
/**
* Schema-specific validation options (e.g., Zod's errorMap).
* The available options depend on the schema library configured in `configureForms`.
*/
schemaOptions?: InferOptions<Schema>;
/**
* Determines when validation should run for the first time on a field.
* Overrides the default configured through `configureForms()` if provided.
*
* @default Inherits from `configureForms()`, or "onSubmit" if not configured
*/
shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
/**
* Determines when validation should run again after the field has been validated once.
* Overrides the default configured through `configureForms()` if provided.
*
* @default Inherits from `configureForms()`, or same as shouldValidate
*/
shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
/** Error handling callback triggered when validation errors occur. By default, it focuses the first invalid field. */
onError?: ErrorHandler<NoInfer<ErrorShape>> | undefined;
/** Input event handler for custom input event logic. */
onInput?: InputHandler | undefined;
/** Blur event handler for custom focus handling logic. */
onBlur?: BlurHandler | undefined;
/** Custom validation handler. Can be skipped when a schema is passed as the first argument, or combined with schema validation to customize errors. */
onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>, SchemaErrorShape> | undefined;
};
/**
* The object returned by `useForm()`, containing form-level metadata,
* typed field metadata, and the intent dispatcher for programmatic updates.
*/
export type FormHandle<FormShape extends Record<string, any>, ErrorShape, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}> = {
/** Form-level metadata and helpers. */
form: FormMetadata<ErrorShape, CustomFormMetadata, CustomFieldMetadata>;
/** Field metadata mapped from the form shape. */
fields: Fieldset<FormShape, ErrorShape, CustomFieldMetadata>;
/** Intent dispatcher for validate, reset, insert, remove, reorder, and update actions. */
intent: IntentDispatcher<FormShape>;
};
export interface FormContext<ErrorShape = any> {
/** The form's unique identifier */
formId: string;
/** Internal form state with validation results and field data */
state: FormState<ErrorShape>;
/** Serializer used to derive field defaults and sync values for this form. */
serialize: Serialize;
/** 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;
}
export type UnknownIntent = {
type: string;
payload?: unknown;
};
export type UnknownArgs<Args extends any[]> = {
[Key in keyof Args]: unknown;
};
export interface IntentDispatcher<FormShape extends Record<string, any> = Record<string, any>> {
/**
* Validate the whole form or a specific field?
*/
validate(name?: string): void;
/**
* Reset the form to a specific default value.
*
* @param options.defaultValue - The value to reset the form to. Pass `null` to clear all fields, or omit to reset to the initial default value from `useForm`.
*
* **Example:**
* ```tsx
* // Reset to initial default value
* intent.reset()
*
* // Clear all fields
* intent.reset({ defaultValue: null })
*
* // Restore to a specific snapshot
* intent.reset({ defaultValue: snapshotValue })
* ```
*/
reset(options?: {
/**
* The value to reset the form to. If not provided, resets to the default value from `useForm`. Pass `null` to clear all fields instead.
*/
defaultValue?: DefaultValue<FormShape>;
}): void;
/**
* Update a field or a fieldset.
* If you provide a fieldset name, it will update all fields within that fieldset
*/
update<FieldShape = FormShape>(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?: undefined;
/**
* The new value for the field or fieldset.
*/
value: DefaultValue<FieldShape>;
} | {
/**
* 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: number;
/**
* The new value for the field or fieldset.
* When index is specified, this should be the item type, not the array type.
*/
value: unknown extends FieldShape ? any : FieldShape extends Array<infer ItemShape> ? ItemShape : any;
}): void;
/**
* Insert a new item into an array field.
*/
insert<FieldShape extends Array<any> | null | undefined>(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?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
/**
* The name of a field to read the value from.
* When specified, the value is read from this field, validated,
* and if valid, inserted into the array and the source field is cleared.
* If validation fails, the error is shown on the source field instead.
* Requires the validation error to be available synchronously.
*/
from?: string;
/**
* What to do when the insert causes a validation error on the array.
* - 'revert': Don't insert, keep original array state.
* Requires the validation error to be available synchronously.
*/
onInvalid?: 'revert';
}): void;
/**
* Remove an item from an array field.
*/
remove<FieldShape extends Array<any> | null | undefined>(options: {
/**
* The name of the array field to remove from.
*/
name: FieldName<FieldShape>;
/**
* The index of the item to remove.
*/
index: number;
/**
* What to do when the remove causes a validation error on the array.
* - 'revert': Don't remove, keep original item as-is.
* - 'insert': Remove the item but insert a new blank item at the end.
* Requires the validation error to be available synchronously.
*/
onInvalid?: 'revert' | 'insert';
/**
* The default value for the new item when onInvalid is 'insert'.
*/
defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
}): 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 IntentHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
validate?(...args: UnknownArgs<Parameters<Signature>>): boolean;
resolve?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
apply?<ErrorShape>(result: SubmissionResult<ErrorShape>, ...args: Parameters<Signature>): SubmissionResult<ErrorShape>;
update?<ErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
type: string;
payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
}, {
reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
cancelled?: boolean;
}>): 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];
};
export type SatisfyComponentProps<ElementType extends React.ElementType, CustomProps extends React.ComponentPropsWithoutRef<ElementType>> = CustomProps;
/** Field metadata object containing field state, validation attributes, and nested field access methods. */
export type FieldMetadata<FieldShape, ErrorShape = any, CustomFieldMetadata extends Record<string, unknown> = {}> = Readonly<Prettify<ValidationAttributes & {
/** Unique key for React list rendering (for array fields). */
key: string | undefined;
/** The field name path exactly as provided. */
name: FieldName<FieldShape>;
/** 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.
*
* Returns an empty string `''` when:
* - No default value is set (field value is `null` or `undefined`)
* - The field value cannot be serialized to a string (e.g., objects or arrays)
*/
defaultValue: string;
/**
* Default selected options for multi-select fields or checkbox group.
*
* Returns an empty array `[]` when:
* - No default options are set (field value is `null` or `undefined`)
* - The field value cannot be serialized to a string array (e.g., nested objects or arrays of objects)
*/
defaultOptions: string[];
/**
* Default checked state for checkbox inputs. Returns `true` if the field value is `'on'`.
*
* For radio buttons, compare the field's `defaultValue` with the radio button's value attribute instead.
*/
defaultChecked: boolean;
/**
* The normalized default payload at this field path.
*
* This is useful for non-native field shapes that need to render a set of
* hidden inputs before user interaction.
*/
defaultPayload: unknown;
/** 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;
/** Validation error for this field. */
errors: ErrorShape | undefined;
/** Object containing validation errors for all touched subfields. */
fieldErrors: Record<string, ErrorShape>;
/** Boolean value for the `aria-invalid` attribute. Indicates whether the field has validation errors for screen readers. */
ariaInvalid: boolean | undefined;
/** String value for the `aria-describedby` attribute. Contains the errorId when invalid, undefined otherwise. Merge with descriptionId manually if needed (e.g. `${metadata.descriptionId} ${metadata.ariaDescribedBy}`). */
ariaDescribedBy: string | undefined;
/** Method to get nested fieldset for object fields under this field. */
getFieldset<FieldsetShape = keyof NonNullable<FieldShape> extends never ? unknown : FieldShape>(): Fieldset<FieldsetShape, ErrorShape, CustomFieldMetadata>;
/** Method to get array of fields for list/array fields under this field. */
getFieldList<FieldItemShape = [FieldShape] extends [
Array<infer ItemShape> | null | undefined
] ? ItemShape : unknown>(): Array<FieldMetadata<FieldItemShape, ErrorShape, CustomFieldMetadata>>;
} & RestoreFieldShape<CustomFieldMetadata, FieldShape>>>;
/**
* Field metadata without custom extensions. This is the type received in `extendFieldMetadata`.
* Equivalent to `FieldMetadata<FieldShape, ErrorShape, {}>`.
*/
export type BaseFieldMetadata<FieldShape, ErrorShape = any> = FieldMetadata<FieldShape, ErrorShape, {}>;
/** Fieldset object containing all form fields as properties with their respective field metadata. */
export type Fieldset<FieldShape, ErrorShape = any, CustomFieldMetadata extends Record<string, unknown> = {}> = {
[Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], ErrorShape, CustomFieldMetadata>;
};
/** Form-level metadata and state object containing validation status, errors, and field access methods. */
export type FormMetadata<ErrorShape = any, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}> = 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 error, if any exists. */
errors: ErrorShape | undefined;
/** Object containing validation errors for all touched fields. */
fieldErrors: Record<string, ErrorShape>;
/** The form's initial default values. */
defaultValue: Record<string, unknown>;
/** 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, ErrorShape, CustomFieldMetadata>;
/** Method to get a fieldset object for nested object fields. */
getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<keyof NonNullable<FieldShape> extends never ? unknown : FieldShape, ErrorShape, CustomFieldMetadata>;
/** 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, ErrorShape, CustomFieldMetadata>>;
} & CustomFormMetadata>;
/**
* Form metadata without custom extensions. This is the type received in `extendFormMetadata`.
* Equivalent to `FormMetadata<ErrorShape, {}, CustomFieldMetadata>`.
*/
export type BaseFormMetadata<ErrorShape = any, CustomFieldMetadata extends Record<string, unknown> = {}> = FormMetadata<ErrorShape, {}, CustomFieldMetadata>;
export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
error: FormError<ErrorShape> | null;
value?: Value;
};
export type ValidateContext<SchemaValue, SchemaErrorShape> = {
/**
* The submitted values mapped by field name.
* Supports nested names like `user.email` and indexed names like `items[0].id`.
*/
payload: Record<string, FormValue>;
/**
* Form error object. Initially empty, but populated with schema validation
* errors when a schema is provided and validation fails.
*/
error: FormError<SchemaErrorShape>;
/**
* The submission intent derived from the button that triggered the form submission.
*/
intent: UnknownIntent | null;
/**
* The raw FormData object of the submission.
*/
formData: FormData;
/**
* Reference to the HTML form element that triggered the submission.
*/
formElement: HTMLFormElement;
/**
* The specific element (button/input) that triggered the form submission.
*/
submitter: HTMLElement | null;
/**
* The validated value from schema validation. Only defined when a schema is provided
* and the validation succeeds. Undefined if no schema is provided or validation fails.
*/
schemaValue: SchemaValue;
};
export type ValidateHandler<ErrorShape, Value, SchemaValue = undefined, SchemaErrorShape = ErrorShape> = (ctx: ValidateContext<SchemaValue, SchemaErrorShape>) => 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 | HTMLFieldSetElement);
}
export interface FormFocusEvent extends React.FormEvent<HTMLFormElement> {
currentTarget: EventTarget & HTMLFormElement;
relatedTarget: EventTarget | null;
target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement);
}
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<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = any, Value = undefined> = {
formData: FormData;
value: Value;
update: (options: {
error?: Partial<FormError<ErrorShape>> | null | undefined;
value?: FormShape | null | undefined;
reset?: boolean | undefined;
}) => void;
};
export type SubmitHandler<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = any, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<FormShape, ErrorShape, Value>) => void | Promise<void>;
/**
* Infer the base error shape from a FormsConfig.
*
* **Example:**
* ```ts
* const { config } = configureForms({ isError: shape<{ message: string }>() });
* type ErrorShape = InferBaseErrorShape<typeof config>; // { message: string }
* ```
*/
export type InferBaseErrorShape<Config> = Config extends FormsConfig<infer ErrorShape, any, any, any, any> ? ErrorShape : string;
/**
* Infer the custom form metadata extension from a FormsConfig.
* Use this to compose with FormMetadata, FieldMetadata, or Fieldset types.
*
* **Example:**
* ```ts
* const { config } = configureForms({
* extendFormMetadata: (meta) => ({ customProp: meta.id })
* });
* type MyFormMetadata = FormMetadata<
* InferBaseErrorShape<typeof config>,
* InferCustomFormMetadata<typeof config>,
* InferCustomFieldMetadata<typeof config>
* >;
* ```
*/
export type InferCustomFormMetadata<Config> = Config extends FormsConfig<any, any, any, infer CustomFormMetadata, any> ? CustomFormMetadata : {};
/**
* Infer the custom field metadata extension from a FormsConfig.
* Use this to compose with FieldMetadata or Fieldset types.
*
* **Example:**
* ```ts
* const { config } = configureForms({
* extendFieldMetadata: (meta) => ({ inputProps: { name: meta.name } })
* });
* type MyFieldMetadata<T> = FieldMetadata<T, InferBaseErrorShape<typeof config>, InferCustomFieldMetadata<typeof config>>;
* type MyFieldset<T> = Fieldset<T, InferBaseErrorShape<typeof config>, InferCustomFieldMetadata<typeof config>>;
* ```
*/
export type InferCustomFieldMetadata<Config> = Config extends FormsConfig<any, any, any, any, infer CustomFieldMetadata> ? CustomFieldMetadata : {};
/**
* Transform a type to make specific keys conditional based on FieldShape.
* Keys in ConditionalKeys will only be present when FieldShape extends the specified type.
* Uses ConditionalFieldMetadata wrapper that RestoreFieldShape will detect and evaluate.
*
* **Example:**
* ```ts
* type Result = MakeConditional<
* { textFieldProps: {...}, dateRangePickerProps: {...} },
* { dateRangePickerProps: { start: string; end: string } }
* >;
* // dateRangePickerProps is wrapped with ConditionalFieldMetadata
* // and will only be present when FieldShape extends { start: string; end: string }
* ```
*/
export type MakeConditional<T, ConditionalKeys extends Record<string, unknown>> = Omit<T, keyof ConditionalKeys> & {
[K in keyof ConditionalKeys]: K extends keyof T ? ConditionalFieldMetadata<T[K], ConditionalKeys[K]> : never;
};
export type MaybePromise<T> = T | Promise<T>;
type BaseFieldsetProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'fieldset'>, 'children' | 'defaultValue'>, 'name'> & {
/**
* Renders a hidden `<fieldset>` base control with nested hidden `<input>` elements
* derived from `defaultValue`.
*/
type: 'fieldset';
/**
* Structured default value used to render nested hidden inputs.
*/
defaultValue: unknown;
};
type BaseSelectProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'select'>, 'children' | 'value'>, 'name' | 'defaultValue'> & {
/**
* Renders a hidden `<select>` base control.
*/
type: 'select';
};
type BaseTextareaProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'textarea'>, 'children' | 'value'>, 'name' | 'defaultValue'> & {
/**
* Renders a hidden `<textarea>` base control.
*/
type: 'textarea';
};
type BaseCheckedInputProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'input'>, 'children' | 'type' | 'checked'>, 'name' | 'defaultChecked'> & {
/**
* Renders a hidden checkbox or radio base control.
*/
type: 'checkbox' | 'radio';
};
type BaseFileInputProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'input'>, 'children' | 'type' | 'value' | 'checked'>, 'name'> & {
/**
* Renders a hidden `<input type="file">` base control.
*/
type: 'file';
};
type BaseInputProps = RequireKey<Omit<React.ComponentPropsWithoutRef<'input'>, 'children' | 'type' | 'value' | 'checked'>, 'name' | 'defaultValue'> & {
/**
* Renders a hidden `<input type="...">` base control.
*/
type?: 'color' | 'date' | 'datetime-local' | 'email' | 'hidden' | 'month' | 'number' | 'password' | 'range' | 'search' | 'tel' | 'text' | 'time' | 'url' | 'week';
};
export type BaseControlProps = BaseFieldsetProps | BaseSelectProps | BaseTextareaProps | BaseCheckedInputProps | BaseFileInputProps | BaseInputProps;
export {};
//# sourceMappingURL=types.d.ts.map