@conform-to/react 1.19.4 → 1.20.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.
@@ -1,4 +1,4 @@
1
- import type { CustomSerialize, FieldName, FormError, FormValue, Serialize, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future';
1
+ import type { CustomSerialize, FieldName, FormError, FormValue, Serialize, Submission, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future';
2
2
  import type { StandardSchemaV1 } from './standard-schema';
3
3
  export type Prettify<T> = {
4
4
  [K in keyof T]: T[K];
@@ -139,7 +139,7 @@ export type UseFormDataOptions<Value = undefined> = {
139
139
  export type DefaultValue<Shape> = Shape extends Record<string, any> ? {
140
140
  [Key in keyof Shape]?: DefaultValue<Shape[Key]>;
141
141
  } | null | undefined : Shape extends Array<infer Item> ? Array<DefaultValue<Item>> | null | undefined : Shape extends File | File[] ? null | undefined : Shape | string | null | undefined;
142
- export type FormState<ErrorShape = any> = {
142
+ export type FormState<ErrorShape = any, CustomState extends Record<string, unknown> = {}> = {
143
143
  /** Unique identifier that changes on form reset to trigger reset side effects */
144
144
  resetKey: string;
145
145
  /** Initial form values */
@@ -156,10 +156,13 @@ export type FormState<ErrorShape = any> = {
156
156
  touchedFields: string[];
157
157
  /** Mapping of array field names to their item keys for React list rendering */
158
158
  listKeys: Record<string, string[]>;
159
+ /** Current custom state for the form */
160
+ customState: CustomState;
159
161
  };
160
- export type FormAction<ErrorShape = any, Intent extends UnknownIntent | null | undefined = UnknownIntent | null, Context = {}> = SubmissionResult<ErrorShape> & {
161
- type: 'initialize' | 'server' | 'client';
162
+ export type FormAction<ErrorShape = any, Intent extends UnknownIntent = UnknownIntent, Context = {}> = {
163
+ type: 'initialize' | 'server' | 'client' | 'client:async';
162
164
  intent: Intent;
165
+ result: SubmissionResult<ErrorShape>;
163
166
  ctx: Context;
164
167
  };
165
168
  /**
@@ -239,7 +242,7 @@ export type ExtractFieldConditions<T extends Record<string, FieldShapeGuard<any>
239
242
  * Resolved configuration from configureForms factory.
240
243
  * Properties with defaults are required, others remain optional.
241
244
  */
242
- export type FormsConfig<BaseErrorShape, BaseSchema, SchemaErrorShape, CustomFormMetadata extends Record<string, unknown>, CustomFieldMetadata extends Record<string, unknown>> = {
245
+ export type FormsConfig<BaseErrorShape, BaseSchema, SchemaErrorShape, CustomFormMetadata extends Record<string, unknown>, CustomFieldMetadata extends Record<string, unknown>, CustomIntentHandlers extends Record<string, IntentHandler<any, any>>, CustomStateHandlers extends Record<string, CustomStateHandler<any, any, BaseErrorShape>>> = {
243
246
  /**
244
247
  * The name of the submit button field that indicates the submission intent.
245
248
  * @default "__intent__"
@@ -249,6 +252,14 @@ export type FormsConfig<BaseErrorShape, BaseSchema, SchemaErrorShape, CustomForm
249
252
  * A custom serializer for converting form values.
250
253
  */
251
254
  serialize?: CustomSerialize | undefined;
255
+ /**
256
+ * Intent handlers available to every form created by this factory.
257
+ */
258
+ intents?: CustomIntentHandlers;
259
+ /**
260
+ * Custom state available to every form created by this factory.
261
+ */
262
+ customState?: CustomStateDefinition<CustomStateHandlers, CustomIntentHandlers>;
252
263
  /**
253
264
  * Determines when validation should run for the first time on a field.
254
265
  * @default "onSubmit"
@@ -298,13 +309,13 @@ export type FormsConfig<BaseErrorShape, BaseSchema, SchemaErrorShape, CustomForm
298
309
  /**
299
310
  * Extends form metadata with custom properties.
300
311
  */
301
- extendFormMetadata?: <ErrorShape extends BaseErrorShape>(metadata: BaseFormMetadata<ErrorShape>) => CustomFormMetadata;
312
+ extendFormMetadata?: <ErrorShape extends BaseErrorShape>(metadata: BaseFormMetadata<ErrorShape, FormCustomState<CustomStateHandlers>>) => CustomFormMetadata;
302
313
  /**
303
314
  * Extends field metadata with custom properties.
304
315
  * Use `when` for properties that depend on the field shape.
305
316
  */
306
317
  extendFieldMetadata?: <FieldShape, ErrorShape extends BaseErrorShape>(metadata: BaseFieldMetadata<FieldShape, ErrorShape>, ctx: {
307
- form: BaseFormMetadata<ErrorShape>;
318
+ form: BaseFormMetadata<ErrorShape, FormCustomState<CustomStateHandlers>>;
308
319
  when: DefineConditionalField;
309
320
  }) => CustomFieldMetadata;
310
321
  };
@@ -332,7 +343,7 @@ export type InferFormShape<Schema> = InferInput<Schema> extends Record<string, a
332
343
  export type InferOutput<Schema> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Schema> : CustomSchemaTypes<Schema> extends {
333
344
  output: infer T;
334
345
  } ? T : undefined;
335
- export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = any, Value = undefined, Schema = unknown, SchemaErrorShape = ErrorShape> = {
346
+ export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = any, Value = undefined, Schema = unknown, SchemaErrorShape = ErrorShape, CustomIntentHandlers extends Record<string, IntentHandler<any, any>> = {}, GlobalIntentHandlers extends Record<string, IntentHandler<any, any>> = {}, CustomStateHandlers extends Record<string, CustomStateHandler<any, any, any>> = {}> = {
336
347
  /** Optional form identifier. If not provided, a unique ID is automatically generated. */
337
348
  id?: string | undefined;
338
349
  /** Optional key for form state reset. When the key changes, the form resets to its initial state. */
@@ -348,6 +359,14 @@ export type FormOptions<FormShape extends Record<string, any> = Record<string, a
348
359
  * to the configured global serializer.
349
360
  */
350
361
  serialize?: CustomSerialize | undefined;
362
+ /**
363
+ * Custom intent handlers available only to this form instance.
364
+ */
365
+ intents?: CustomIntentHandlers | undefined;
366
+ /**
367
+ * Custom state available only to this form instance.
368
+ */
369
+ customState?: CustomStateDefinition<CustomStateHandlers, GlobalIntentHandlers & CustomIntentHandlers> | undefined;
351
370
  /** HTML validation attributes for fields (required, minLength, pattern, etc.). */
352
371
  constraint?: Record<string, ValidationAttributes> | undefined;
353
372
  /**
@@ -376,25 +395,25 @@ export type FormOptions<FormShape extends Record<string, any> = Record<string, a
376
395
  /** Blur event handler for custom focus handling logic. */
377
396
  onBlur?: BlurHandler | undefined;
378
397
  /** Custom validation handler. Can be skipped when a schema is passed as the first argument, or combined with schema validation to customize errors. */
379
- onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>, SchemaErrorShape> | undefined;
398
+ onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>, SchemaErrorShape, FormIntent<FormShape, DefaultIntentHandlers & GlobalIntentHandlers & CustomIntentHandlers> | undefined> | undefined;
380
399
  };
381
400
  /**
382
401
  * The object returned by `useForm()`, containing form-level metadata,
383
402
  * typed field metadata, and the intent dispatcher for programmatic updates.
384
403
  */
385
- export type FormHandle<FormShape extends Record<string, any>, ErrorShape, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}> = {
404
+ export type FormHandle<FormShape extends Record<string, any>, ErrorShape, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}, CustomIntentHandlers extends Record<string, IntentHandler<any, any>> = {}, CustomState extends Record<string, unknown> = {}> = {
386
405
  /** Form-level metadata and helpers. */
387
- form: FormMetadata<ErrorShape, CustomFormMetadata, CustomFieldMetadata>;
406
+ form: FormMetadata<ErrorShape, CustomFormMetadata, CustomFieldMetadata, CustomState>;
388
407
  /** Field metadata mapped from the form shape. */
389
408
  fields: Fieldset<FormShape, ErrorShape, CustomFieldMetadata>;
390
409
  /** Intent dispatcher for validate, reset, insert, remove, reorder, and update actions. */
391
- intent: IntentDispatcher<FormShape>;
410
+ intent: IntentDispatcher<FormShape, CustomIntentHandlers>;
392
411
  };
393
- export interface FormContext<ErrorShape = any> {
412
+ export interface FormContext<ErrorShape = any, CustomState extends Record<string, unknown> = {}> {
394
413
  /** The form's unique identifier */
395
414
  formId: string;
396
415
  /** Internal form state with validation results and field data */
397
- state: FormState<ErrorShape>;
416
+ state: FormState<ErrorShape, CustomState>;
398
417
  /** Serializer used to derive field defaults and sync values for this form. */
399
418
  serialize: Serialize;
400
419
  /** HTML validation attributes for fields */
@@ -408,153 +427,134 @@ export interface FormContext<ErrorShape = any> {
408
427
  }
409
428
  export type UnknownIntent = {
410
429
  type: string;
411
- payload?: unknown;
430
+ payload: unknown;
431
+ };
432
+ export type TransportIntent = {
433
+ type: string;
434
+ args: unknown[];
412
435
  };
413
436
  export type UnknownArgs<Args extends any[]> = {
414
437
  [Key in keyof Args]: unknown;
415
438
  };
416
- export interface IntentDispatcher<FormShape extends Record<string, any> = Record<string, any>> {
417
- /**
418
- * Validate the whole form or a specific field?
419
- */
420
- validate(name?: string): void;
421
- /**
422
- * Reset the form to a specific default value.
423
- *
424
- * @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`.
425
- *
426
- * **Example:**
427
- * ```tsx
428
- * // Reset to initial default value
429
- * intent.reset()
430
- *
431
- * // Clear all fields
432
- * intent.reset({ defaultValue: null })
433
- *
434
- * // Restore to a specific snapshot
435
- * intent.reset({ defaultValue: snapshotValue })
436
- * ```
437
- */
438
- reset(options?: {
439
- /**
440
- * The value to reset the form to. If not provided, resets to the default value from `useForm`. Pass `null` to clear all fields instead.
441
- */
439
+ export interface TypedIntentDefinition {
440
+ readonly FormShape: unknown;
441
+ dispatch(...args: any[]): void;
442
+ }
443
+ export type IntentDefinition = ((...args: any[]) => void) | TypedIntentDefinition;
444
+ export type ExtractDispatchSignature<Intent extends IntentDefinition, FormShape extends Record<string, any>> = Intent extends (...args: any[]) => void ? Intent : Intent extends TypedIntentDefinition ? (Intent & {
445
+ readonly FormShape: FormShape;
446
+ })['dispatch'] : never;
447
+ export type IntentPayload<Intent extends IntentDefinition, FormShape extends Record<string, any> = Record<string, any>> = Parameters<ExtractDispatchSignature<Intent, FormShape>> extends [] ? undefined : Parameters<ExtractDispatchSignature<Intent, FormShape>>[0];
448
+ export type NormalizeIntentType<T> = T extends IntentDefinition ? T : (payload: T) => void;
449
+ export type ApplyStatus = 'applied' | 'reverted' | 'modified';
450
+ export type IntentDispatch<Intent extends IntentDefinition, FormShape extends Record<string, any> = Record<string, any>> = ExtractDispatchSignature<NormalizeIntentType<Intent>, FormShape>;
451
+ export type IntentHandlerPayload<Dispatch extends IntentDefinition, Payload, FormShape extends Record<string, any>> = [Payload] extends [never] ? IntentPayload<NormalizeIntentType<Dispatch>, FormShape> : Payload;
452
+ export type SubmitIntent = () => void;
453
+ export interface ResetIntent extends TypedIntentDefinition {
454
+ dispatch<FormShape extends Record<string, any> = this['FormShape'] extends Record<string, any> ? this['FormShape'] : never>(options?: {
442
455
  defaultValue?: DefaultValue<FormShape>;
443
456
  }): void;
444
- /**
445
- * Update a field or a fieldset.
446
- * If you provide a fieldset name, it will update all fields within that fieldset
447
- */
448
- update<FieldShape = FormShape>(options: {
449
- /**
450
- * The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
451
- */
457
+ }
458
+ export type ValidateIntent = (name?: string) => void;
459
+ export type IsUntypedFormShape<FormShape> = unknown extends FormShape ? true : string extends keyof FormShape ? true : false;
460
+ export interface UpdateIntent extends TypedIntentDefinition {
461
+ dispatch<FieldShape = this['FormShape']>(options: IsUntypedFormShape<this['FormShape']> extends true ? {
462
+ name?: string | undefined;
463
+ index?: number | undefined;
464
+ value: unknown;
465
+ } : {
452
466
  name?: FieldName<FieldShape>;
453
- /**
454
- * Specify the index of the item to update if the field is an array.
455
- */
456
467
  index?: undefined;
457
- /**
458
- * The new value for the field or fieldset.
459
- */
460
468
  value: DefaultValue<FieldShape>;
461
469
  } | {
462
- /**
463
- * The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
464
- */
465
- name: FieldName<FieldShape>;
466
- /**
467
- * Specify the index of the item to update if the field is an array.
468
- */
470
+ name: FieldName<FieldShape extends Array<any> | null | undefined ? FieldShape : never>;
469
471
  index: number;
470
- /**
471
- * The new value for the field or fieldset.
472
- * When index is specified, this should be the item type, not the array type.
473
- */
474
- value: unknown extends FieldShape ? any : FieldShape extends Array<infer ItemShape> ? ItemShape : any;
472
+ value: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
475
473
  }): void;
476
- /**
477
- * Insert a new item into an array field.
478
- */
479
- insert<FieldShape extends Array<any> | null | undefined>(options: {
480
- /**
481
- * The name of the array field to insert into.
482
- */
474
+ }
475
+ export interface InsertIntent extends TypedIntentDefinition {
476
+ dispatch<FieldShape extends Array<any> | null | undefined>(options: {
483
477
  name: FieldName<FieldShape>;
484
- /**
485
- * The index at which to insert the new item.
486
- * If not provided, it will be added to the end of the array.
487
- */
488
478
  index?: number;
489
- /**
490
- * The default value for the new item.
491
- */
492
479
  defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
493
- /**
494
- * The name of a field to read the value from.
495
- * When specified, the value is read from this field, validated,
496
- * and if valid, inserted into the array and the source field is cleared.
497
- * If validation fails, the error is shown on the source field instead.
498
- * Requires the validation error to be available synchronously.
499
- */
500
480
  from?: string;
501
- /**
502
- * What to do when the insert causes a validation error on the array.
503
- * - 'revert': Don't insert, keep original array state.
504
- * Requires the validation error to be available synchronously.
505
- */
506
481
  onInvalid?: 'revert';
507
482
  }): void;
508
- /**
509
- * Remove an item from an array field.
510
- */
511
- remove<FieldShape extends Array<any> | null | undefined>(options: {
512
- /**
513
- * The name of the array field to remove from.
514
- */
483
+ }
484
+ export interface RemoveIntent extends TypedIntentDefinition {
485
+ dispatch<FieldShape extends Array<any> | null | undefined>(options: {
515
486
  name: FieldName<FieldShape>;
516
- /**
517
- * The index of the item to remove.
518
- */
519
487
  index: number;
520
- /**
521
- * What to do when the remove causes a validation error on the array.
522
- * - 'revert': Don't remove, keep original item as-is.
523
- * - 'insert': Remove the item but insert a new blank item at the end.
524
- * Requires the validation error to be available synchronously.
525
- */
526
488
  onInvalid?: 'revert' | 'insert';
527
- /**
528
- * The default value for the new item when onInvalid is 'insert'.
529
- */
530
489
  defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
531
490
  }): void;
532
- /**
533
- * Reorder items in an array field.
534
- */
535
- reorder(options: {
536
- name: FieldName<Array<any>>;
537
- from: number;
538
- to: number;
539
- }): void;
540
491
  }
541
- export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> = {
542
- [Type in keyof Dispatcher]: Dispatcher[Type] extends (...args: infer Args) => void ? {
543
- type: Type;
544
- payload: Args extends [infer Payload] ? Payload : undefined;
492
+ export type ReorderIntent = (options: {
493
+ name: FieldName<Array<any>>;
494
+ from: number;
495
+ to: number;
496
+ }) => void;
497
+ export type DefaultIntentHandlers = {
498
+ submit: IntentHandler<SubmitIntent>;
499
+ reset: IntentHandler<ResetIntent>;
500
+ validate: IntentHandler<ValidateIntent>;
501
+ update: IntentHandler<UpdateIntent>;
502
+ insert: IntentHandler<InsertIntent>;
503
+ remove: IntentHandler<RemoveIntent>;
504
+ reorder: IntentHandler<ReorderIntent>;
505
+ };
506
+ export type IntentDispatcher<FormShape extends Record<string, any>, CustomIntentHandlers extends Record<string, IntentHandler<any, any>> = {}> = {
507
+ [Type in keyof (DefaultIntentHandlers & ([CustomIntentHandlers] extends [never] ? {} : CustomIntentHandlers))]: (DefaultIntentHandlers & ([CustomIntentHandlers] extends [never] ? {} : CustomIntentHandlers))[Type] extends IntentHandler<infer Dispatch, any> ? IntentDispatch<Dispatch, FormShape> : never;
508
+ };
509
+ export type FormIntent<FormShape extends Record<string, any>, Handlers extends Record<string, IntentHandler<any, any>>> = {
510
+ [K in keyof ([Handlers] extends [never] ? {} : Handlers)]: ([
511
+ Handlers
512
+ ] extends [never] ? {} : Handlers)[K] extends IntentHandler<infer Dispatch, infer Payload> ? {
513
+ type: K & string;
514
+ payload: IntentHandlerPayload<Dispatch, Payload, FormShape>;
545
515
  } : never;
546
- }[keyof Dispatcher];
547
- export type IntentHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
548
- validate?(...args: UnknownArgs<Parameters<Signature>>): boolean;
549
- resolve?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
550
- apply?<ErrorShape>(result: SubmissionResult<ErrorShape>, ...args: Parameters<Signature>): SubmissionResult<ErrorShape>;
551
- update?<ErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
552
- type: string;
553
- payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
554
- }, {
555
- reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
556
- cancelled?: boolean;
557
- }>): FormState<ErrorShape>;
516
+ }[keyof ([Handlers] extends [never] ? {} : Handlers)];
517
+ export type IntentHandler<Dispatch extends IntentDefinition = (payload: any) => void, Payload = never> = {
518
+ parse<FormShape extends Record<string, any>>(...args: Parameters<IntentDispatch<Dispatch, FormShape>>): IntentHandlerPayload<Dispatch, Payload, FormShape>;
519
+ resolve?<FormShape extends Record<string, any>>(ctx: {
520
+ value: Record<string, FormValue>;
521
+ payload: IntentHandlerPayload<Dispatch, Payload, FormShape>;
522
+ }): Record<string, FormValue> | undefined;
523
+ apply?<FormShape extends Record<string, any>, ErrorShape>(ctx: {
524
+ result: SubmissionResult<ErrorShape>;
525
+ payload: IntentHandlerPayload<Dispatch, Payload, FormShape>;
526
+ }): SubmissionResult<ErrorShape>;
527
+ touch?<FormShape extends Record<string, any>>(ctx: {
528
+ name: string;
529
+ payload: IntentHandlerPayload<Dispatch, Payload, FormShape>;
530
+ }): boolean;
531
+ move?<FormShape extends Record<string, any>>(ctx: {
532
+ name: string;
533
+ status: ApplyStatus;
534
+ targetValue: Record<string, FormValue> | undefined;
535
+ payload: IntentHandlerPayload<Dispatch, Payload, FormShape>;
536
+ }): string | null;
537
+ };
538
+ export type CustomStateHandler<State, CustomIntentHandlers extends Record<string, IntentHandler<any, any>> = {}, ErrorShape = any> = {
539
+ initialize(): State;
540
+ reset?: boolean | ((state: State, ctx: {
541
+ result: SubmissionResult<ErrorShape> | undefined;
542
+ }) => State);
543
+ handleIntent?(state: State, ctx: {
544
+ intent: FormIntent<any, DefaultIntentHandlers & CustomIntentHandlers>;
545
+ submission: Submission;
546
+ }): State;
547
+ handleResult?(state: State, ctx: {
548
+ intent: FormIntent<any, DefaultIntentHandlers & CustomIntentHandlers>;
549
+ result: SubmissionResult<ErrorShape>;
550
+ phase: 'client' | 'server';
551
+ }): State;
552
+ };
553
+ export type FormCustomState<Handlers extends Record<string, CustomStateHandler<any, any, any>>> = {
554
+ [Key in keyof Handlers]: Handlers[Key] extends CustomStateHandler<infer State, any, any> ? State : never;
555
+ };
556
+ type CustomStateDefinition<CustomStateHandlers extends Record<string, CustomStateHandler<any, any, any>>, IntentHandlers extends Record<string, IntentHandler<any, any>>> = {
557
+ [Key in keyof CustomStateHandlers]: CustomStateHandlers[Key] extends CustomStateHandler<any, infer RequiredIntents, any> ? Exclude<keyof RequiredIntents, keyof IntentHandlers> extends never ? RequiredIntents extends Pick<IntentHandlers, Extract<keyof RequiredIntents, keyof IntentHandlers>> ? CustomStateHandlers[Key] : never : never : never;
558
558
  };
559
559
  type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
560
560
  export type Combine<T> = {
@@ -635,7 +635,7 @@ export type Fieldset<FieldShape, ErrorShape = any, CustomFieldMetadata extends R
635
635
  [Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], ErrorShape, CustomFieldMetadata>;
636
636
  };
637
637
  /** Form-level metadata and state object containing validation status, errors, and field access methods. */
638
- export type FormMetadata<ErrorShape = any, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}> = Readonly<{
638
+ export type FormMetadata<ErrorShape = any, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}, CustomState extends Record<string, unknown> = {}> = Readonly<{
639
639
  /** Unique identifier that changes on form reset */
640
640
  key: string;
641
641
  /** The form's unique identifier. */
@@ -656,6 +656,8 @@ export type FormMetadata<ErrorShape = any, CustomFormMetadata extends Record<str
656
656
  fieldErrors: Record<string, ErrorShape>;
657
657
  /** The form's initial default values. */
658
658
  defaultValue: Record<string, unknown>;
659
+ /** Current custom state for the form. */
660
+ customState: CustomState;
659
661
  /** Form props object for spreading onto the <form> element. */
660
662
  props: Readonly<{
661
663
  id: string;
@@ -665,7 +667,7 @@ export type FormMetadata<ErrorShape = any, CustomFormMetadata extends Record<str
665
667
  noValidate: boolean;
666
668
  }>;
667
669
  /** The current state of the form */
668
- context: FormContext<ErrorShape>;
670
+ context: FormContext<ErrorShape, CustomState>;
669
671
  /** Method to get metadata for a specific field by name. */
670
672
  getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, ErrorShape, CustomFieldMetadata>;
671
673
  /** Method to get a fieldset object for nested object fields. */
@@ -679,12 +681,18 @@ export type FormMetadata<ErrorShape = any, CustomFormMetadata extends Record<str
679
681
  * Form metadata without custom extensions. This is the type received in `extendFormMetadata`.
680
682
  * Equivalent to `FormMetadata<ErrorShape, {}, CustomFieldMetadata>`.
681
683
  */
682
- export type BaseFormMetadata<ErrorShape = any, CustomFieldMetadata extends Record<string, unknown> = {}> = FormMetadata<ErrorShape, {}, CustomFieldMetadata>;
684
+ export type BaseFormMetadata<ErrorShape = any, CustomState extends Record<string, unknown> = {}> = FormMetadata<ErrorShape, {}, {}, CustomState>;
683
685
  export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
684
686
  error: FormError<ErrorShape> | null;
685
687
  value?: Value;
686
688
  };
687
- export type ValidateContext<SchemaValue, SchemaErrorShape> = {
689
+ export type StagedValidateResult<ErrorShape, Value> = {
690
+ /** The validation result available immediately. */
691
+ result: ValidateResult<ErrorShape, Value>;
692
+ /** A complete validation result that replaces the immediate result when resolved. */
693
+ pending: Promise<ValidateResult<ErrorShape, Value>>;
694
+ };
695
+ export type ValidateContext<SchemaValue, SchemaErrorShape, Intent extends UnknownIntent | undefined = UnknownIntent | undefined> = {
688
696
  /**
689
697
  * The submitted values mapped by field name.
690
698
  * Supports nested names like `user.email` and indexed names like `items[0].id`.
@@ -698,7 +706,7 @@ export type ValidateContext<SchemaValue, SchemaErrorShape> = {
698
706
  /**
699
707
  * The submission intent derived from the button that triggered the form submission.
700
708
  */
701
- intent: UnknownIntent | null;
709
+ intent: Intent;
702
710
  /**
703
711
  * The raw FormData object of the submission.
704
712
  */
@@ -717,10 +725,7 @@ export type ValidateContext<SchemaValue, SchemaErrorShape> = {
717
725
  */
718
726
  schemaValue: SchemaValue;
719
727
  };
720
- export type ValidateHandler<ErrorShape, Value, SchemaValue = undefined, SchemaErrorShape = ErrorShape> = (ctx: ValidateContext<SchemaValue, SchemaErrorShape>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
721
- ValidateResult<ErrorShape, Value> | undefined,
722
- Promise<ValidateResult<ErrorShape, Value>> | undefined
723
- ] | undefined;
728
+ export type ValidateHandler<ErrorShape, Value, SchemaValue = undefined, SchemaErrorShape = ErrorShape, Intent extends UnknownIntent | undefined = UnknownIntent | undefined> = (ctx: ValidateContext<SchemaValue, SchemaErrorShape, Intent>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | StagedValidateResult<ErrorShape, Value> | undefined;
724
729
  export interface FormInputEvent extends React.FormEvent<HTMLFormElement> {
725
730
  currentTarget: EventTarget & HTMLFormElement;
726
731
  target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFieldSetElement);
@@ -733,7 +738,7 @@ export interface FormFocusEvent extends React.FormEvent<HTMLFormElement> {
733
738
  export type ErrorContext<ErrorShape> = {
734
739
  formElement: HTMLFormElement;
735
740
  error: FormError<ErrorShape>;
736
- intent: UnknownIntent | null;
741
+ intent: UnknownIntent | undefined;
737
742
  };
738
743
  export type ErrorHandler<ErrorShape> = (ctx: ErrorContext<ErrorShape>) => void;
739
744
  export type InputHandler = (event: FormInputEvent) => void;
@@ -757,7 +762,7 @@ export type SubmitHandler<FormShape extends Record<string, any> = Record<string,
757
762
  * type ErrorShape = InferBaseErrorShape<typeof config>; // { message: string }
758
763
  * ```
759
764
  */
760
- export type InferBaseErrorShape<Config> = Config extends FormsConfig<infer ErrorShape, any, any, any, any> ? ErrorShape : string;
765
+ export type InferBaseErrorShape<Config> = Config extends FormsConfig<infer ErrorShape, any, any, any, any, any, any> ? ErrorShape : string;
761
766
  /**
762
767
  * Infer the custom form metadata extension from a FormsConfig.
763
768
  * Use this to compose with FormMetadata, FieldMetadata, or Fieldset types.
@@ -774,7 +779,7 @@ export type InferBaseErrorShape<Config> = Config extends FormsConfig<infer Error
774
779
  * >;
775
780
  * ```
776
781
  */
777
- export type InferCustomFormMetadata<Config> = Config extends FormsConfig<any, any, any, infer CustomFormMetadata, any> ? CustomFormMetadata : {};
782
+ export type InferCustomFormMetadata<Config> = Config extends FormsConfig<any, any, any, infer CustomFormMetadata, any, any, any> ? CustomFormMetadata : {};
778
783
  /**
779
784
  * Infer the custom field metadata extension from a FormsConfig.
780
785
  * Use this to compose with FieldMetadata or Fieldset types.
@@ -788,7 +793,11 @@ export type InferCustomFormMetadata<Config> = Config extends FormsConfig<any, an
788
793
  * type MyFieldset<T> = Fieldset<T, InferBaseErrorShape<typeof config>, InferCustomFieldMetadata<typeof config>>;
789
794
  * ```
790
795
  */
791
- export type InferCustomFieldMetadata<Config> = Config extends FormsConfig<any, any, any, any, infer CustomFieldMetadata> ? CustomFieldMetadata : {};
796
+ export type InferCustomFieldMetadata<Config> = Config extends FormsConfig<any, any, any, any, infer CustomFieldMetadata, any, any> ? CustomFieldMetadata : {};
797
+ /**
798
+ * Infer the configured custom-state shape from a FormsConfig.
799
+ */
800
+ export type InferCustomState<Config> = Config extends FormsConfig<any, any, any, any, any, any, infer CustomStateHandlers> ? FormCustomState<CustomStateHandlers> : {};
792
801
  /**
793
802
  * Transform a type to make specific keys conditional based on FieldShape.
794
803
  * Keys in ConditionalKeys will only be present when FieldShape extends the specified type.
@@ -13,10 +13,10 @@ export declare function getPathArray<Type>(formValue: Record<string, Type> | nul
13
13
  */
14
14
  export declare function updatePathValue<Data>(data: Record<string, Data>, name: string, value: Data | Record<string, Data>): Record<string, Data>;
15
15
  /**
16
- * Creates a function that updates array indices in field paths.
17
- * Returns null to remove fields, or updated path with new index.
16
+ * Updates array indices in a field path.
17
+ * Returns null to remove fields, or the updated path with a new index.
18
18
  */
19
- export declare function createPathIndexUpdater(listName: string, update: (index: number) => number | null): (name: string) => string | null;
19
+ export declare function updatePathIndex(name: string, listName: string, update: (index: number) => number | null): string | null;
20
20
  export declare function resolveSerialize(customSerialize: CustomSerialize | undefined, defaultSerialize: Serialize): Serialize;
21
21
  export declare function normalizeValidateResult<ErrorShape, Value>(result: ValidateResult<ErrorShape, Value>): {
22
22
  error: FormError<ErrorShape> | null;
@@ -25,7 +25,7 @@ export declare function normalizeValidateResult<ErrorShape, Value>(result: Valid
25
25
  /**
26
26
  * Handles different validation result formats:
27
27
  * - Promise: async validation only
28
- * - Array: [syncResult, asyncPromise]
28
+ * - Staged result: immediate result with a pending result
29
29
  * - Object: sync validation only
30
30
  */
31
31
  export declare function resolveValidateResult<ErrorShape, Value>(result: ReturnType<ValidateHandler<ErrorShape, Value>>): {
@@ -59,10 +59,6 @@ export declare function transformKeys<Value>(obj: Record<string, Value>, fn: (ke
59
59
  * Returns original array if item exists, new array if added.
60
60
  */
61
61
  export declare function appendUniqueItem<Item>(list: Array<Item>, item: Item): Item[];
62
- /**
63
- * Maps over array and filters out null results.
64
- */
65
- export declare function compactMap<Item>(list: Array<NonNullable<Item>>, fn: (value: Item) => Item | null): Array<Item>;
66
62
  export declare function generateUniqueKey(): string;
67
63
  /**
68
64
  * Creates a type-only marker for TypeScript inference.