@conform-to/react 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ import type { FormError, FormValue, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future';
2
+ import { StandardSchemaV1 } from '@standard-schema/spec';
3
+ export type Prettify<T> = {
4
+ [K in keyof T]: T[K];
5
+ } & {};
6
+ /** Reference to a form element. Can be either a React ref object or a form ID string. */
7
+ export type FormRef = React.RefObject<HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null> | string;
8
+ export type InputSnapshot = {
9
+ value?: string;
10
+ options?: string[];
11
+ checked?: boolean;
12
+ files?: File[];
13
+ };
14
+ export type Control = {
15
+ /**
16
+ * Current value of the base input. Undefined if the registered input
17
+ * is a multi-select, file input, or checkbox group.
18
+ */
19
+ value: string | undefined;
20
+ /**
21
+ * Selected options of the base input. Defined only when the registered input
22
+ * is a multi-select or checkbox group.
23
+ */
24
+ checked: boolean | undefined;
25
+ /**
26
+ * Checked state of the base input. Defined only when the registered input
27
+ * is a single checkbox or radio input.
28
+ */
29
+ options: string[] | undefined;
30
+ /**
31
+ * Selected files of the base input. Defined only when the registered input
32
+ * is a file input.
33
+ */
34
+ files: File[] | undefined;
35
+ /**
36
+ * Registers the base input element(s). Accepts a single input or an array for groups.
37
+ */
38
+ register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLCollectionOf<HTMLInputElement> | NodeListOf<HTMLInputElement> | null | undefined) => void;
39
+ /**
40
+ * Programmatically updates the input value and emits
41
+ * both [change](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) and
42
+ * [input](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) events.
43
+ */
44
+ change(value: string | string[] | boolean | File | File[] | FileList | null): void;
45
+ /**
46
+ * Emits [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) and
47
+ * [focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event) events.
48
+ * Does not actually move focus.
49
+ */
50
+ focus(): void;
51
+ /**
52
+ * Emits [focus](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event) and
53
+ * [focusin](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event) events.
54
+ * This does not move the actual keyboard focus to the input. Use `element.focus()` instead
55
+ * if you want to move focus to the input.
56
+ */
57
+ blur(): void;
58
+ };
59
+ export type Selector<FormValue, Result> = (formData: FormValue | null, lastResult: Result | undefined) => Result;
60
+ export type UseFormDataOptions = {
61
+ /**
62
+ * Set to `true` to preserve file inputs and receive a `FormData` object in the selector.
63
+ * If omitted or `false`, the selector receives a `URLSearchParams` object, where all values are coerced to strings.
64
+ */
65
+ acceptFiles?: boolean;
66
+ };
67
+ 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 ? {
68
+ [Key in keyof FormShape]?: DefaultValue<FormShape[Key]>;
69
+ } | null | undefined : unknown;
70
+ export type FormState<ErrorShape> = {
71
+ /** Unique identifier that changes on form reset to trigger reset side effects */
72
+ resetKey: string;
73
+ /** Form values from user intent actions (validate, update, insert, remove, etc.) */
74
+ intendedValue: Record<string, unknown> | null;
75
+ /** Form values that have been validated on the server */
76
+ serverValidatedValue: Record<string, unknown> | null;
77
+ /** Validation errors from server-side processing */
78
+ serverError: FormError<ErrorShape> | null;
79
+ /** Validation errors from client-side validation */
80
+ clientError: FormError<ErrorShape> | null;
81
+ /** Array of field names that have been touched (validated) */
82
+ touchedFields: string[];
83
+ /** Mapping of array field names to their item keys for React list rendering */
84
+ listKeys: Record<string, string[]>;
85
+ };
86
+ export type FormAction<ErrorShape, Intent extends UnknownIntent | null | undefined = UnknownIntent | null, Context = {}> = SubmissionResult<ErrorShape> & {
87
+ type: 'initialize' | 'server' | 'client';
88
+ intent: Intent;
89
+ ctx: Context;
90
+ };
91
+ export interface FormOptions<FormShape, ErrorShape = string, Value = undefined> {
92
+ /** Optional form identifier. If not provided, a unique ID is automatically generated. */
93
+ id?: string;
94
+ /** Optional key for form state reset. When the key changes, the form resets to its initial state. */
95
+ key?: string;
96
+ /** Optional standard schema for validation (e.g., Zod, Valibot, Yup). Removes the need for manual onValidate setup. */
97
+ schema?: StandardSchemaV1<FormShape, Value>;
98
+ /** Initial form values. Can be a partial object matching your form structure. */
99
+ defaultValue?: NoInfer<DefaultValue<FormShape>>;
100
+ /** HTML validation attributes for fields (required, minLength, pattern, etc.). */
101
+ constraint?: Record<string, ValidationAttributes>;
102
+ /**
103
+ * Define when conform should start validation.
104
+ * Support "onSubmit", "onInput", "onBlur".
105
+ *
106
+ * @default "onSubmit"
107
+ */
108
+ shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput';
109
+ /**
110
+ * Define when conform should revalidate again.
111
+ * Support "onSubmit", "onInput", "onBlur".
112
+ *
113
+ * @default Same as shouldValidate, or "onSubmit" if shouldValidate is not provided.
114
+ */
115
+ shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput';
116
+ /** Server-side submission result for form state synchronization. */
117
+ lastResult?: SubmissionResult<NoInfer<ErrorShape>> | null;
118
+ /** Custom validation handler. Can be skipped if using the schema property, or combined with schema to customize validation errors. */
119
+ onValidate?: ValidateHandler<ErrorShape, Value>;
120
+ /** Error handling callback triggered when validation errors occur. By default, it focuses the first invalid field. */
121
+ onError?: ErrorHandler<ErrorShape>;
122
+ /** Form submission handler called when the form is submitted with no validation errors. */
123
+ onSubmit?: SubmitHandler<NoInfer<ErrorShape>, NoInfer<Value>>;
124
+ /** Input event handler for custom input event logic. */
125
+ onInput?: InputHandler;
126
+ /** Blur event handler for custom focus handling logic. */
127
+ onBlur?: BlurHandler;
128
+ }
129
+ export interface FormContext<ErrorShape = string> {
130
+ /** The form's unique identifier */
131
+ formId: string;
132
+ /** Internal form state with validation results and field data */
133
+ state: FormState<ErrorShape>;
134
+ /** Initial form values */
135
+ defaultValue: NonNullable<DefaultValue<Record<string, any>>> | null;
136
+ /** HTML validation attributes for fields */
137
+ constraint: Record<string, ValidationAttributes> | null;
138
+ /** Form submission event handler */
139
+ handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
140
+ /** Input event handler for form-wide input events */
141
+ handleInput: (event: React.FormEvent) => void;
142
+ /** Blur event handler for form-wide blur events */
143
+ handleBlur: (event: React.FocusEvent) => void;
144
+ }
145
+ /** The name of an input field with type information for TypeScript inference. */
146
+ export type FieldName<FieldShape> = string & {
147
+ '~shape'?: FieldShape;
148
+ };
149
+ export type UnknownIntent = {
150
+ type: string;
151
+ payload?: unknown;
152
+ };
153
+ export type UnknownArgs<Args extends any[]> = {
154
+ [Key in keyof Args]: unknown;
155
+ };
156
+ export interface IntentDispatcher {
157
+ /**
158
+ * Validate the whole form or a specific field?
159
+ */
160
+ validate(name?: string): void;
161
+ /**
162
+ * Reset the form to its initial state.
163
+ */
164
+ reset(): void;
165
+ /**
166
+ * Update a field or a fieldset.
167
+ * If you provide a fieldset name, it will update all fields within that fieldset
168
+ */
169
+ update<FieldShape>(options: {
170
+ /**
171
+ * The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
172
+ */
173
+ name?: FieldName<FieldShape>;
174
+ /**
175
+ * Specify the index of the item to update if the field is an array.
176
+ */
177
+ index?: [FieldShape] extends [Array<any> | null | undefined] ? number : never;
178
+ /**
179
+ * The new value for the field or fieldset.
180
+ */
181
+ value: Partial<FieldShape>;
182
+ }): void;
183
+ /**
184
+ * Insert a new item into an array field.
185
+ */
186
+ insert<FieldShape extends Array<any>>(options: {
187
+ /**
188
+ * The name of the array field to insert into.
189
+ */
190
+ name: FieldName<FieldShape>;
191
+ /**
192
+ * The index at which to insert the new item.
193
+ * If not provided, it will be added to the end of the array.
194
+ */
195
+ index?: number;
196
+ /**
197
+ * The default value for the new item.
198
+ */
199
+ defaultValue?: [FieldShape] extends [
200
+ Array<infer ItemShape> | null | undefined
201
+ ] ? Partial<ItemShape> : never;
202
+ }): void;
203
+ /**
204
+ * Remove an item from an array field.
205
+ */
206
+ remove(options: {
207
+ /**
208
+ * The name of the array field to remove from.
209
+ */
210
+ name: FieldName<Array<any>>;
211
+ /**
212
+ * The index of the item to remove.
213
+ */
214
+ index: number;
215
+ }): void;
216
+ /**
217
+ * Reorder items in an array field.
218
+ */
219
+ reorder(options: {
220
+ name: FieldName<Array<any>>;
221
+ from: number;
222
+ to: number;
223
+ }): void;
224
+ }
225
+ export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> = {
226
+ [Type in keyof Dispatcher]: Dispatcher[Type] extends (...args: infer Args) => void ? {
227
+ type: Type;
228
+ payload: Args extends [infer Payload] ? Payload : undefined;
229
+ } : never;
230
+ }[keyof Dispatcher];
231
+ export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
232
+ validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean;
233
+ onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | null;
234
+ onUpdate?<ErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
235
+ type: string;
236
+ payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
237
+ }>): FormState<ErrorShape>;
238
+ };
239
+ type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
240
+ export type Combine<T> = {
241
+ [K in keyof BaseCombine<T>]: BaseCombine<T>[K];
242
+ };
243
+ /** Field metadata object containing field state, validation attributes, and nested field access methods. */
244
+ export type Field<FieldShape, Metadata extends Record<string, unknown> = DefaultFieldMetadata<unknown>> = Readonly<Metadata & {
245
+ /** Unique key for React list rendering (for array fields). */
246
+ key: string | undefined;
247
+ /** The field name path exactly as provided. */
248
+ name: FieldName<FieldShape>;
249
+ /** Method to get nested fieldset for object fields under this field. */
250
+ getFieldset(): Fieldset<[
251
+ FieldShape
252
+ ] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, Metadata>;
253
+ /** Method to get array of fields for list/array fields under this field. */
254
+ getFieldList(): Array<Field<[
255
+ FieldShape
256
+ ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, Metadata>>;
257
+ }>;
258
+ /** Fieldset object containing all form fields as properties with their respective field metadata. */
259
+ export type Fieldset<FieldShape, // extends Record<string, unknown>,
260
+ FieldMetadata extends Record<string, unknown>> = {
261
+ [Key in keyof Combine<FieldShape>]-?: Field<Combine<FieldShape>[Key], FieldMetadata>;
262
+ };
263
+ /** Form-level metadata and state object containing validation status, errors, and field access methods. */
264
+ export type FormMetadata<ErrorShape, FieldMetadata extends Record<string, unknown> = DefaultFieldMetadata<ErrorShape>> = Readonly<{
265
+ /** The form's unique identifier. */
266
+ id: string;
267
+ /** Whether any field in the form has been touched (through intent.validate() or the shouldValidate option). */
268
+ touched: boolean;
269
+ /** Whether the form currently has any validation errors. */
270
+ invalid: boolean;
271
+ /** Form-level validation errors, if any exist. */
272
+ errors: ErrorShape[] | undefined;
273
+ /** Object containing field-specific validation errors for all validated fields. */
274
+ fieldErrors: Record<string, ErrorShape[]>;
275
+ /** Form props object for spreading onto the <form> element. */
276
+ props: Readonly<{
277
+ id: string;
278
+ onSubmit: React.FormEventHandler<HTMLFormElement>;
279
+ onBlur: React.FocusEventHandler<HTMLFormElement>;
280
+ onInput: React.FormEventHandler<HTMLFormElement>;
281
+ noValidate: boolean;
282
+ }>;
283
+ /** The current state of the form */
284
+ context: FormContext<ErrorShape>;
285
+ /** Method to get metadata for a specific field by name. */
286
+ getField<FieldShape>(name: FieldName<FieldShape>): Field<FieldShape, FieldMetadata>;
287
+ /** Method to get a fieldset object for nested object fields. */
288
+ getFieldset<FieldShape>(name: FieldName<FieldShape>): Fieldset<[
289
+ FieldShape
290
+ ] extends [Record<string, unknown> | null | undefined] ? FieldShape : unknown, FieldMetadata>;
291
+ /** Method to get an array of field objects for array fields. */
292
+ getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<Field<[
293
+ FieldShape
294
+ ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, FieldMetadata>>;
295
+ }>;
296
+ /** Default field metadata object containing field state, validation attributes, and accessibility IDs. */
297
+ export type DefaultFieldMetadata<ErrorShape> = Readonly<ValidationAttributes & {
298
+ /** The field's unique identifier, automatically generated as {formId}-{fieldName}. */
299
+ id: string;
300
+ /** Auto-generated ID for associating field descriptions via aria-describedby. */
301
+ descriptionId: string;
302
+ /** Auto-generated ID for associating field errors via aria-describedby. */
303
+ errorId: string;
304
+ /** The field's default value as a string. */
305
+ defaultValue: string | undefined;
306
+ /** Default selected options for multi-select fields or checkbox group. */
307
+ defaultOptions: string[] | undefined;
308
+ /** Default checked state for checkbox/radio inputs. */
309
+ defaultChecked: boolean | undefined;
310
+ /** Whether this field has been touched (through intent.validate() or the shouldValidate option). */
311
+ touched: boolean;
312
+ /** Whether this field currently has validation errors. */
313
+ invalid: boolean;
314
+ /** Array of validation error messages for this field. */
315
+ errors: ErrorShape[] | undefined;
316
+ }>;
317
+ export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
318
+ error: FormError<ErrorShape> | null;
319
+ value?: Value;
320
+ };
321
+ export type ValidateContext = {
322
+ payload: Record<string, FormValue>;
323
+ error: FormError<string>;
324
+ intent: UnknownIntent | null;
325
+ formData: FormData;
326
+ formElement: HTMLFormElement;
327
+ submitter: HTMLElement | null;
328
+ };
329
+ export type ValidateHandler<ErrorShape, Value> = (ctx: ValidateContext) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
330
+ ValidateResult<ErrorShape, Value> | undefined,
331
+ Promise<ValidateResult<ErrorShape, Value>> | undefined
332
+ ] | undefined;
333
+ export interface FormInputEvent extends React.FormEvent<HTMLFormElement> {
334
+ currentTarget: EventTarget & HTMLFormElement;
335
+ target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement);
336
+ }
337
+ export interface FormFocusEvent extends React.FormEvent<HTMLFormElement> {
338
+ currentTarget: EventTarget & HTMLFormElement;
339
+ relatedTarget: EventTarget | null;
340
+ target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement);
341
+ }
342
+ export type ErrorContext<ErrorShape> = {
343
+ formElement: HTMLFormElement;
344
+ error: FormError<ErrorShape>;
345
+ intent: UnknownIntent | null;
346
+ };
347
+ export type ErrorHandler<ErrorShape> = (ctx: ErrorContext<ErrorShape>) => void;
348
+ export type InputHandler = (event: FormInputEvent) => void;
349
+ export type BlurHandler = (event: FormFocusEvent) => void;
350
+ export type SubmitContext<ErrorShape = string, Value = undefined> = {
351
+ formData: FormData;
352
+ value: Value;
353
+ update: (options: {
354
+ error?: Partial<FormError<ErrorShape>> | null;
355
+ reset?: boolean;
356
+ }) => void;
357
+ };
358
+ export type SubmitHandler<ErrorShape = string, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<ErrorShape, Value>) => void | Promise<void>;
359
+ export {};
360
+ //# sourceMappingURL=types.d.ts.map
@@ -1,39 +1,68 @@
1
- export type FormRef = React.RefObject<HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null> | string;
2
- export declare function getFormElement(formRef: FormRef | undefined): HTMLFormElement | null;
3
- export declare function focusable(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
4
- export declare function initializeField(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, options: {
5
- defaultValue?: string | string[] | File | File[] | null;
6
- defaultChecked?: boolean;
7
- value?: string;
8
- } | undefined): void;
9
- export declare function getRadioGroupValue(inputs: Array<HTMLInputElement>): string | undefined;
10
- export declare function getCheckboxGroupValue(inputs: Array<HTMLInputElement>): string[] | undefined;
11
- export type InputSnapshot = {
12
- value?: string;
13
- options?: string[];
14
- checked?: boolean;
15
- files?: File[];
1
+ import type { FormError } from '@conform-to/dom/future';
2
+ import { StandardSchemaV1 } from '@standard-schema/spec';
3
+ import { ValidateHandler, ValidateResult } from './types';
4
+ export declare function isUndefined(value: unknown): value is undefined;
5
+ export declare function isString(value: unknown): value is string;
6
+ export declare function isNumber(value: unknown): value is number;
7
+ export declare function isOptional<T>(value: unknown, typeGuard: (value: unknown) => value is T): value is T | undefined;
8
+ export declare function getArrayAtPath<Type>(formValue: Record<string, Type> | null, name: string): Array<Type>;
9
+ /**
10
+ * Immutably updates a value at the specified path.
11
+ * Empty path replaces the entire object.
12
+ */
13
+ export declare function updateValueAtPath<Data>(data: Record<string, Data>, name: string, value: Data | Record<string, Data>): Record<string, Data>;
14
+ /**
15
+ * Creates a function that updates array indices in field paths.
16
+ * Returns null to remove fields, or updated path with new index.
17
+ */
18
+ export declare function createPathIndexUpdater(listName: string, update: (index: number) => number | null): (name: string) => string | null;
19
+ /**
20
+ * Returns null if error object has no actual error messages,
21
+ * otherwise returns the error as-is.
22
+ */
23
+ export declare function normalizeFormError<ErrorShape>(error: FormError<ErrorShape> | null): FormError<ErrorShape> | null;
24
+ export declare function normalizeValidateResult<ErrorShape, Value>(result: ValidateResult<ErrorShape, Value>): {
25
+ error: FormError<ErrorShape> | null;
26
+ value?: Value;
16
27
  };
17
- export declare function getInputSnapshot(input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): {
18
- files: File[] | undefined;
19
- value?: undefined;
20
- checked?: undefined;
21
- options?: undefined;
22
- } | {
23
- value: string;
24
- checked: boolean;
25
- files?: undefined;
26
- options?: undefined;
27
- } | {
28
- options: string[];
29
- files?: undefined;
30
- value?: undefined;
31
- checked?: undefined;
32
- } | {
33
- value: string;
34
- files?: undefined;
35
- checked?: undefined;
36
- options?: undefined;
28
+ /**
29
+ * Handles different validation result formats:
30
+ * - Promise: async validation only
31
+ * - Array: [syncResult, asyncPromise]
32
+ * - Object: sync validation only
33
+ */
34
+ export declare function resolveValidateResult<ErrorShape, Value>(result: ReturnType<ValidateHandler<ErrorShape, Value>>): {
35
+ syncResult: {
36
+ error: FormError<ErrorShape> | null;
37
+ value?: Value | undefined;
38
+ } | undefined;
39
+ asyncResult: Promise<{
40
+ error: FormError<ErrorShape> | null;
41
+ value?: Value | undefined;
42
+ }> | undefined;
37
43
  };
38
- export declare function getDefaultSnapshot(defaultValue: string | string[] | File | File[] | FileList | null | undefined, defaultChecked: boolean | undefined, value: string | undefined): InputSnapshot;
44
+ export declare function resolveStandardSchemaPath(issue: StandardSchemaV1.Issue): Array<string | number>;
45
+ export declare function resolveStandardSchemaResult<Value>(result: StandardSchemaV1.Result<Value>): {
46
+ error: FormError<string> | null;
47
+ value?: Value;
48
+ };
49
+ /**
50
+ * Create a copy of the object with the updated properties if there is any change
51
+ */
52
+ export declare function merge<Obj extends Record<string, any>>(obj: Obj, update: Partial<Obj>): Obj;
53
+ /**
54
+ * Transforms object keys using a mapping function.
55
+ * Keys mapped to null are filtered out.
56
+ */
57
+ export declare function transformKeys<Value>(obj: Record<string, Value>, fn: (key: string) => string | null): Record<string, Value>;
58
+ /**
59
+ * Appends item to array only if not already present.
60
+ * Returns original array if item exists, new array if added.
61
+ */
62
+ export declare function appendUniqueItem<Item>(list: Array<Item>, item: Item): Item[];
63
+ /**
64
+ * Maps over array and filters out null results.
65
+ */
66
+ export declare function compactMap<Item>(list: Array<NonNullable<Item>>, fn: (value: Item) => Item | null): Array<Item>;
67
+ export declare function generateUniqueKey(): string;
39
68
  //# sourceMappingURL=util.d.ts.map