@conform-to/react 1.15.1 → 1.17.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,6 +1,6 @@
1
1
  import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { getPathSegments, getRelativePath, appendPathSegment, deepEqual, getValueAtPath, formatPathSegments, serialize } from '@conform-to/dom/future';
3
- import { generateUniqueKey, merge, getArrayAtPath } from './util.mjs';
2
+ import { getPathSegments, getRelativePath, serialize, appendPathSegment, deepEqual, getValueAtPath, formatPathSegments } from '@conform-to/dom/future';
3
+ import { when, generateUniqueKey, merge, getArrayAtPath } from './util.mjs';
4
4
 
5
5
  function initializeState(options) {
6
6
  var _options$resetKey, _options$defaultValue;
@@ -53,12 +53,13 @@ function updateState(state, action) {
53
53
  type: 'validate'
54
54
  };
55
55
  var handler = (_action$ctx$handlers = action.ctx.handlers) === null || _action$ctx$handlers === void 0 ? void 0 : _action$ctx$handlers[intent.type];
56
- if (typeof (handler === null || handler === void 0 ? void 0 : handler.onUpdate) === 'function') {
57
- var _handler$validatePayl, _handler$validatePayl2;
58
- if ((_handler$validatePayl = (_handler$validatePayl2 = handler.validatePayload) === null || _handler$validatePayl2 === void 0 ? void 0 : _handler$validatePayl2.call(handler, intent.payload)) !== null && _handler$validatePayl !== void 0 ? _handler$validatePayl : true) {
59
- return handler.onUpdate(state, _objectSpread2(_objectSpread2({}, action), {}, {
56
+ if (typeof (handler === null || handler === void 0 ? void 0 : handler.update) === 'function') {
57
+ var _handler$validate, _handler$validate2;
58
+ if ((_handler$validate = (_handler$validate2 = handler.validate) === null || _handler$validate2 === void 0 ? void 0 : _handler$validate2.call(handler, intent.payload)) !== null && _handler$validate !== void 0 ? _handler$validate : true) {
59
+ return handler.update(state, _objectSpread2(_objectSpread2({}, action), {}, {
60
60
  ctx: {
61
- reset: action.ctx.reset
61
+ reset: action.ctx.reset,
62
+ cancelled: action.ctx.cancelled
62
63
  },
63
64
  intent: {
64
65
  type: intent.type,
@@ -237,7 +238,8 @@ function getConstraint(context, name) {
237
238
  return constraint;
238
239
  }
239
240
  function getFormMetadata(context, options) {
240
- return {
241
+ var _options$extendFormMe, _options$extendFormMe2;
242
+ var metadata = {
241
243
  key: context.state.resetKey,
242
244
  id: context.formId,
243
245
  errorId: "".concat(context.formId, "-form-error"),
@@ -270,31 +272,41 @@ function getFormMetadata(context, options) {
270
272
  return getField(context, {
271
273
  name,
272
274
  serialize: options === null || options === void 0 ? void 0 : options.serialize,
273
- customize: options === null || options === void 0 ? void 0 : options.customize
275
+ extendFieldMetadata: options === null || options === void 0 ? void 0 : options.extendFieldMetadata
274
276
  });
275
277
  },
276
278
  getFieldset(name) {
277
279
  return getFieldset(context, {
278
280
  name,
279
281
  serialize: options === null || options === void 0 ? void 0 : options.serialize,
280
- customize: options === null || options === void 0 ? void 0 : options.customize
282
+ extendFieldMetadata: options === null || options === void 0 ? void 0 : options.extendFieldMetadata
281
283
  });
282
284
  },
283
285
  getFieldList(name) {
284
286
  return getFieldList(context, {
285
287
  name,
286
288
  serialize: options === null || options === void 0 ? void 0 : options.serialize,
287
- customize: options === null || options === void 0 ? void 0 : options.customize
289
+ extendFieldMetadata: options === null || options === void 0 ? void 0 : options.extendFieldMetadata
288
290
  });
289
291
  }
290
292
  };
293
+ var customMetadata = (_options$extendFormMe = options === null || options === void 0 || (_options$extendFormMe2 = options.extendFormMetadata) === null || _options$extendFormMe2 === void 0 ? void 0 : _options$extendFormMe2.call(options, metadata)) !== null && _options$extendFormMe !== void 0 ? _options$extendFormMe : {};
294
+ var descriptors = Object.getOwnPropertyDescriptors(customMetadata);
295
+ var extended = Object.create(metadata);
296
+ Object.defineProperties(extended, descriptors);
297
+ return extended;
291
298
  }
292
299
  function getField(context, options) {
300
+ var _extendFieldMetadata;
293
301
  var {
294
302
  key,
295
303
  name,
296
304
  serialize: serialize$1 = serialize,
297
- customize
305
+ extendFieldMetadata,
306
+ form = getFormMetadata(context, {
307
+ serialize: serialize$1,
308
+ extendFieldMetadata
309
+ })
298
310
  } = options;
299
311
  var id = "".concat(context.formId, "-field-").concat(name.replace(/[^a-zA-Z0-9._-]/g, '_'));
300
312
  var constraint = getConstraint(context, name);
@@ -313,6 +325,7 @@ function getField(context, options) {
313
325
  max: constraint === null || constraint === void 0 ? void 0 : constraint.max,
314
326
  step: constraint === null || constraint === void 0 ? void 0 : constraint.step,
315
327
  multiple: constraint === null || constraint === void 0 ? void 0 : constraint.multiple,
328
+ accept: constraint === null || constraint === void 0 ? void 0 : constraint.accept,
316
329
  get defaultValue() {
317
330
  return getDefaultValue(context, name, serialize$1);
318
331
  },
@@ -347,40 +360,27 @@ function getField(context, options) {
347
360
  return getFieldset(context, {
348
361
  name: name,
349
362
  serialize: serialize$1,
350
- customize
363
+ extendFieldMetadata
351
364
  });
352
365
  },
366
+ // @ts-expect-error The return type includes CustomFieldMetadata which BaseFieldMetadata
367
+ // doesn't account for. This is a type-level limitation; runtime behavior is correct.
353
368
  getFieldList() {
354
369
  return getFieldList(context, {
355
- name: name,
370
+ name,
356
371
  serialize: serialize$1,
357
- customize
372
+ extendFieldMetadata
358
373
  });
359
374
  }
360
375
  };
361
- if (typeof customize !== 'function') {
362
- return metadata;
363
- }
364
- var customMetadata = null;
365
- return new Proxy(metadata, {
366
- get(target, prop, receiver) {
367
- var _customMetadata;
368
- if (Reflect.has(target, prop)) {
369
- return Reflect.get(target, prop, receiver);
370
- }
371
- (_customMetadata = customMetadata) !== null && _customMetadata !== void 0 ? _customMetadata : customMetadata = customize(metadata);
372
- if (Reflect.has(customMetadata, prop)) {
373
- return Reflect.get(customMetadata, prop, receiver);
374
- }
375
-
376
- // Allow React DevTools to inspect the object
377
- // without throwing errors for internal properties
378
- if (typeof prop === 'symbol' || prop === '$$typeof') {
379
- return undefined;
380
- }
381
- throw new Error("Property \"".concat(String(prop), "\" does not exist on field metadata. ") + "If you have defined the CustomMetadata interface to include \"".concat(String(prop), "\", make sure to also implement it through the \"defineCustomMetadata\" property on <FormOptionsProvider />."));
382
- }
383
- });
376
+ var customMetadata = (_extendFieldMetadata = extendFieldMetadata === null || extendFieldMetadata === void 0 ? void 0 : extendFieldMetadata(metadata, {
377
+ form,
378
+ when
379
+ })) !== null && _extendFieldMetadata !== void 0 ? _extendFieldMetadata : {};
380
+ var descriptors = Object.getOwnPropertyDescriptors(customMetadata);
381
+ var extended = Object.create(metadata);
382
+ Object.defineProperties(extended, descriptors);
383
+ return extended;
384
384
  }
385
385
 
386
386
  /**
@@ -390,10 +390,16 @@ function getFieldset(context, options) {
390
390
  return new Proxy({}, {
391
391
  get(target, name, receiver) {
392
392
  if (typeof name === 'string') {
393
+ var _options$form;
394
+ (_options$form = options.form) !== null && _options$form !== void 0 ? _options$form : options.form = getFormMetadata(context, {
395
+ serialize: options === null || options === void 0 ? void 0 : options.serialize,
396
+ extendFieldMetadata: options === null || options === void 0 ? void 0 : options.extendFieldMetadata
397
+ });
393
398
  return getField(context, {
394
399
  name: appendPathSegment(options === null || options === void 0 ? void 0 : options.name, name),
395
400
  serialize: options.serialize,
396
- customize: options.customize
401
+ extendFieldMetadata: options.extendFieldMetadata,
402
+ form: options.form
397
403
  });
398
404
  }
399
405
  return Reflect.get(target, name, receiver);
@@ -410,7 +416,7 @@ function getFieldList(context, options) {
410
416
  return getField(context, {
411
417
  name: appendPathSegment(options.name, index),
412
418
  serialize: options.serialize,
413
- customize: options.customize,
419
+ extendFieldMetadata: options.extendFieldMetadata,
414
420
  key
415
421
  });
416
422
  });
@@ -46,28 +46,33 @@ export type Control = {
46
46
  * both [change](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) and
47
47
  * [input](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) events.
48
48
  */
49
- change(value: string | string[] | boolean | File | File[] | FileList | null): void;
49
+ change: (value: string | string[] | boolean | File | File[] | FileList | null) => void;
50
50
  /**
51
51
  * Emits [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) and
52
52
  * [focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event) events.
53
53
  * Does not actually move focus.
54
54
  */
55
- focus(): void;
55
+ focus: () => void;
56
56
  /**
57
57
  * Emits [focus](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event) and
58
58
  * [focusin](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event) events.
59
59
  * This does not move the actual keyboard focus to the input. Use `element.focus()` instead
60
60
  * if you want to move focus to the input.
61
61
  */
62
- blur(): void;
62
+ blur: () => void;
63
63
  };
64
- export type Selector<FormValue, Result> = (formData: FormValue | null, lastResult: Result | undefined) => Result;
65
- export type UseFormDataOptions = {
64
+ export type Selector<FormValue, Result> = (formData: FormValue, lastResult: Result | undefined) => Result;
65
+ export type UseFormDataOptions<Value = undefined> = {
66
66
  /**
67
67
  * Set to `true` to preserve file inputs and receive a `FormData` object in the selector.
68
68
  * If omitted or `false`, the selector receives a `URLSearchParams` object, where all values are coerced to strings.
69
69
  */
70
70
  acceptFiles?: boolean;
71
+ /**
72
+ * The fallback value to return when the form element is not available (e.g., on SSR or initial client render).
73
+ * If not provided, the hook returns `undefined` when the form is unavailable.
74
+ */
75
+ fallback?: Value;
71
76
  };
72
77
  export type DefaultValue<Shape> = Shape extends Record<string, any> ? {
73
78
  [Key in keyof Shape]?: DefaultValue<Shape[Key]>;
@@ -95,6 +100,152 @@ export type FormAction<ErrorShape extends BaseErrorShape = DefaultErrorShape, In
95
100
  intent: Intent;
96
101
  ctx: Context;
97
102
  };
103
+ /**
104
+ * Augment this interface to customize schema type inference for your schema library.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * import type { ZodTypeAny, input, output } from 'zod';
109
+ * import type { ZodSchemaOptions } from '@conform-to/zod/v3/future';
110
+ *
111
+ * declare module '@conform-to/react/future' {
112
+ * interface CustomSchemaTypes<Schema> {
113
+ * input: Schema extends ZodTypeAny ? input<Schema> : never;
114
+ * output: Schema extends ZodTypeAny ? output<Schema> : never;
115
+ * options: Schema extends ZodTypeAny ? ZodSchemaOptions : never;
116
+ * }
117
+ * }
118
+ * ```
119
+ */
120
+ export interface CustomSchemaTypes<Schema = unknown> {
121
+ }
122
+ /**
123
+ * Infer schema options type.
124
+ * Uses CustomSchemaTypes if augmented, otherwise returns never.
125
+ */
126
+ export type InferOptions<Schema> = [Schema] extends [undefined] ? never : CustomSchemaTypes<Schema> extends {
127
+ options: infer T;
128
+ } ? T : never;
129
+ /**
130
+ * Marker type for conditional field metadata properties.
131
+ * Used to indicate that a property should only be present when FieldShape matches a condition.
132
+ */
133
+ export type ConditionalFieldMetadata<T, Condition> = T & {
134
+ '~condition': Condition;
135
+ };
136
+ /**
137
+ * Check if T is a FieldName type by checking if '~shape' is a known key.
138
+ * Plain strings don't have '~shape' in their keyof, but FieldName<T> does.
139
+ */
140
+ type IsFieldName<T> = '~shape' extends keyof T ? true : false;
141
+ /**
142
+ * Transforms a single value, restoring FieldName<unknown> to FieldName<FieldShape>.
143
+ */
144
+ type RestoreFieldShapeValue<T, FieldShape> = T extends ConditionalFieldMetadata<infer Inner, infer Condition> ? FieldShape extends Condition ? Inner : never : IsFieldName<T> extends true ? FieldName<FieldShape> : T;
145
+ /**
146
+ * Restores FieldShape-dependent types that were inferred as `unknown`.
147
+ * This is needed because TypeScript infers generic return types with unresolved type parameters.
148
+ * Also handles conditional field metadata by checking if FieldShape extends the condition.
149
+ *
150
+ * Transforms up to 2 levels deep to handle cases like `textFieldProps.name`.
151
+ */
152
+ export type RestoreFieldShape<T, FieldShape> = {
153
+ [K in keyof T]: RestoreFieldShapeValue<T[K], FieldShape> extends infer V ? V extends Record<string, unknown> ? {
154
+ [P in keyof V]: RestoreFieldShapeValue<V[P], FieldShape>;
155
+ } : V : never;
156
+ };
157
+ /**
158
+ * Type guard function for conditional field metadata.
159
+ * Used to specify when a custom field metadata property should be available.
160
+ */
161
+ export type FieldShapeGuard<Condition> = (shape: unknown) => shape is Condition;
162
+ /**
163
+ * Function type for creating conditional field metadata based on shape constraints.
164
+ */
165
+ export type DefineConditionalField = <FieldShape, ErrorShape, Metadata>(metadata: BaseFieldMetadata<unknown, ErrorShape>, shape: FieldShapeGuard<FieldShape>, fn: (metadata: BaseFieldMetadata<FieldShape, ErrorShape>) => Metadata) => ConditionalFieldMetadata<Metadata, FieldShape>;
166
+ /**
167
+ * Extract the condition type from a FieldShapeGuard.
168
+ */
169
+ export type ExtractFieldCondition<T> = T extends FieldShapeGuard<infer C> ? C : never;
170
+ /**
171
+ * Extract conditions from a record of field shape guards.
172
+ */
173
+ export type ExtractFieldConditions<T extends Record<string, FieldShapeGuard<any>>> = {
174
+ [K in keyof T]: ExtractFieldCondition<T[K]>;
175
+ };
176
+ /**
177
+ * Resolved configuration from configureForms factory.
178
+ * Properties with defaults are required, others remain optional.
179
+ */
180
+ export type FormsConfig<BaseErrorShape, BaseSchema, CustomFormMetadata extends Record<string, unknown>, CustomFieldMetadata extends Record<string, unknown>> = {
181
+ /**
182
+ * The name of the submit button field that indicates the submission intent.
183
+ * @default "__intent__"
184
+ */
185
+ intentName: string;
186
+ /**
187
+ * A custom serialization function for converting form data.
188
+ */
189
+ serialize: Serialize;
190
+ /**
191
+ * Determines when validation should run for the first time on a field.
192
+ * @default "onSubmit"
193
+ */
194
+ shouldValidate: 'onSubmit' | 'onBlur' | 'onInput';
195
+ /**
196
+ * Determines when validation should run again after the field has been validated once.
197
+ * @default Same as shouldValidate
198
+ */
199
+ shouldRevalidate: 'onSubmit' | 'onBlur' | 'onInput';
200
+ /**
201
+ * Runtime type guard to check if a value is a schema.
202
+ * Used to determine if the first argument to useForm is a schema or options object.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { configureForms } from '@conform-to/react/future';
207
+ * import {
208
+ * isSchema,
209
+ * validateSchema,
210
+ * getConstraints,
211
+ * } from '@conform-to/zod/v3/future';
212
+ *
213
+ * const { useForm } = configureForms({
214
+ * isSchema,
215
+ * validateSchema,
216
+ * getConstraints,
217
+ * });
218
+ * ```
219
+ */
220
+ isSchema: (schema: unknown) => schema is BaseSchema;
221
+ /**
222
+ * Validates a schema against form payload.
223
+ */
224
+ validateSchema: <Schema extends BaseSchema>(schema: Schema, payload: Record<string, FormValue>, options?: InferOptions<Schema>) => MaybePromise<{
225
+ error: FormError<string> | null;
226
+ value?: InferOutput<Schema>;
227
+ }>;
228
+ /**
229
+ * A type guard function to specify the shape of error objects.
230
+ */
231
+ isError?: (error: unknown) => error is BaseErrorShape;
232
+ /**
233
+ * Extracts HTML validation constraints from a schema.
234
+ */
235
+ getConstraints?: <Schema extends BaseSchema>(schema: Schema) => Record<string, ValidationAttributes> | undefined;
236
+ /**
237
+ * Extends form metadata with custom properties.
238
+ */
239
+ extendFormMetadata?: <ErrorShape extends BaseErrorShape>(metadata: BaseFormMetadata<ErrorShape>) => CustomFormMetadata;
240
+ /**
241
+ * Extends field metadata with custom properties.
242
+ * Use `when` for properties that depend on the field shape.
243
+ */
244
+ extendFieldMetadata?: <FieldShape, ErrorShape extends BaseErrorShape>(metadata: BaseFieldMetadata<FieldShape, ErrorShape>, ctx: {
245
+ form: BaseFormMetadata<ErrorShape>;
246
+ when: DefineConditionalField;
247
+ }) => CustomFieldMetadata;
248
+ };
98
249
  export type GlobalFormOptions = {
99
250
  /**
100
251
  * The name of the submit button field that indicates the submission intent.
@@ -129,9 +280,23 @@ export type NonPartial<T> = {
129
280
  };
130
281
  export type RequireKey<T, K extends keyof T> = Prettify<T & Pick<NonPartial<T>, K>>;
131
282
  export type BaseSchemaType = StandardSchemaV1<any, any>;
132
- export type InferInput<Schema> = Schema extends StandardSchemaV1<infer input, any> ? input : unknown;
133
- export type InferOutput<Schema> = Schema extends StandardSchemaV1<any, infer output> ? output : undefined;
134
- export type BaseFormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined> = {
283
+ /**
284
+ * Infer schema input type.
285
+ * For StandardSchemaV1 schemas (zod, valibot, etc.), uses StandardSchemaV1.InferInput.
286
+ * For other schemas, uses CustomSchemaTypes if augmented.
287
+ */
288
+ export type InferInput<Schema> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<Schema> : CustomSchemaTypes<Schema> extends {
289
+ input: infer T;
290
+ } ? T : Record<string, any>;
291
+ /**
292
+ * Infer schema output type.
293
+ * For StandardSchemaV1 schemas (zod, valibot, etc.), uses StandardSchemaV1.InferOutput.
294
+ * For other schemas, uses CustomSchemaTypes if augmented.
295
+ */
296
+ export type InferOutput<Schema> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Schema> : CustomSchemaTypes<Schema> extends {
297
+ output: infer T;
298
+ } ? T : undefined;
299
+ export type BaseFormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = unknown> = {
135
300
  /** Optional form identifier. If not provided, a unique ID is automatically generated. */
136
301
  id?: string | undefined;
137
302
  /** Optional key for form state reset. When the key changes, the form resets to its initial state. */
@@ -144,6 +309,11 @@ export type BaseFormOptions<FormShape extends Record<string, any> = Record<strin
144
309
  defaultValue?: DefaultValue<FormShape> | undefined;
145
310
  /** HTML validation attributes for fields (required, minLength, pattern, etc.). */
146
311
  constraint?: Record<string, ValidationAttributes> | undefined;
312
+ /**
313
+ * Schema-specific validation options (e.g., Zod's errorMap).
314
+ * The available options depend on the schema library configured in `configureForms`.
315
+ */
316
+ schemaOptions?: InferOptions<Schema>;
147
317
  /**
148
318
  * Determines when validation should run for the first time on a field.
149
319
  * Overrides the global default set by FormOptionsProvider if provided.
@@ -167,7 +337,7 @@ export type BaseFormOptions<FormShape extends Record<string, any> = Record<strin
167
337
  /** Custom validation handler. Can be skipped if using the schema property, or combined with schema to customize validation errors. */
168
338
  onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>> | undefined;
169
339
  };
170
- export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined, RequiredKeys extends keyof BaseFormOptions<FormShape, ErrorShape, Value, Schema> = never> = RequireKey<BaseFormOptions<FormShape, ErrorShape, Value, Schema>, RequiredKeys>;
340
+ export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = unknown, RequiredKeys extends keyof BaseFormOptions<FormShape, ErrorShape, Value, Schema> = never> = RequireKey<BaseFormOptions<FormShape, ErrorShape, Value, Schema>, RequiredKeys>;
171
341
  export interface FormContext<ErrorShape extends BaseErrorShape = DefaultErrorShape> {
172
342
  /** The form's unique identifier */
173
343
  formId: string;
@@ -252,7 +422,7 @@ export interface IntentDispatcher<FormShape extends Record<string, any> = Record
252
422
  /**
253
423
  * Insert a new item into an array field.
254
424
  */
255
- insert<FieldShape extends Array<any>>(options: {
425
+ insert<FieldShape extends Array<any> | null | undefined>(options: {
256
426
  /**
257
427
  * The name of the array field to insert into.
258
428
  */
@@ -265,20 +435,45 @@ export interface IntentDispatcher<FormShape extends Record<string, any> = Record
265
435
  /**
266
436
  * The default value for the new item.
267
437
  */
268
- defaultValue?: FieldShape extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
438
+ defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
439
+ /**
440
+ * The name of a field to read the value from.
441
+ * When specified, the value is read from this field, validated,
442
+ * and if valid, inserted into the array and the source field is cleared.
443
+ * If validation fails, the error is shown on the source field instead.
444
+ * Requires the validation error to be available synchronously.
445
+ */
446
+ from?: string;
447
+ /**
448
+ * What to do when the insert causes a validation error on the array.
449
+ * - 'revert': Don't insert, keep original array state.
450
+ * Requires the validation error to be available synchronously.
451
+ */
452
+ onInvalid?: 'revert';
269
453
  }): void;
270
454
  /**
271
455
  * Remove an item from an array field.
272
456
  */
273
- remove(options: {
457
+ remove<FieldShape extends Array<any> | null | undefined>(options: {
274
458
  /**
275
459
  * The name of the array field to remove from.
276
460
  */
277
- name: FieldName<Array<any>>;
461
+ name: FieldName<FieldShape>;
278
462
  /**
279
463
  * The index of the item to remove.
280
464
  */
281
465
  index: number;
466
+ /**
467
+ * What to do when the remove causes a validation error on the array.
468
+ * - 'revert': Don't remove, keep original item as-is.
469
+ * - 'insert': Remove the item but insert a new blank item at the end.
470
+ * Requires the validation error to be available synchronously.
471
+ */
472
+ onInvalid?: 'revert' | 'insert';
473
+ /**
474
+ * The default value for the new item when onInvalid is 'insert'.
475
+ */
476
+ defaultValue?: NonNullable<FieldShape> extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
282
477
  }): void;
283
478
  /**
284
479
  * Reorder items in an array field.
@@ -295,14 +490,16 @@ export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> =
295
490
  payload: Args extends [infer Payload] ? Payload : undefined;
296
491
  } : never;
297
492
  }[keyof Dispatcher];
298
- export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
299
- validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean;
300
- onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
301
- onUpdate?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
493
+ export type IntentHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
494
+ validate?(...args: UnknownArgs<Parameters<Signature>>): boolean;
495
+ resolve?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
496
+ apply?<ErrorShape extends BaseErrorShape>(result: SubmissionResult<ErrorShape>, ...args: Parameters<Signature>): SubmissionResult<ErrorShape>;
497
+ update?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
302
498
  type: string;
303
499
  payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
304
500
  }, {
305
501
  reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
502
+ cancelled?: boolean;
306
503
  }>): FormState<ErrorShape>;
307
504
  };
308
505
  type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
@@ -329,8 +526,16 @@ export type BaseErrorShape = CustomTypes extends {
329
526
  export type DefaultErrorShape = CustomTypes extends {
330
527
  errorShape: infer Shape;
331
528
  } ? Shape : string;
332
- /** Base field metadata object containing field state, validation attributes, and accessibility IDs. */
333
- export type BaseMetadata<FieldShape, ErrorShape extends BaseErrorShape> = ValidationAttributes & {
529
+ export type SatisfyComponentProps<ElementType extends React.ElementType, CustomProps extends React.ComponentPropsWithoutRef<ElementType>> = CustomProps;
530
+ /**
531
+ * Interface for extending field metadata with additional properties.
532
+ * @deprecated Use `configureForms()` with the `extendFieldMetadata` option for full type inference support.
533
+ */
534
+ export interface CustomMetadata<FieldShape = any, ErrorShape extends BaseErrorShape = DefaultErrorShape> {
535
+ }
536
+ export type DefaultCustomMetadata<FieldShape, ErrorShape> = keyof CustomMetadata<FieldShape, ErrorShape> extends never ? {} : CustomMetadata<FieldShape, ErrorShape>;
537
+ /** Field metadata object containing field state, validation attributes, and nested field access methods. */
538
+ export type FieldMetadata<FieldShape, ErrorShape extends BaseErrorShape = DefaultErrorShape, CustomFieldMetadata extends Record<string, unknown> = DefaultCustomMetadata<FieldShape, ErrorShape>> = Readonly<Prettify<ValidationAttributes & {
334
539
  /** Unique key for React list rendering (for array fields). */
335
540
  key: string | undefined;
336
541
  /** The field name path exactly as provided. */
@@ -380,27 +585,31 @@ export type BaseMetadata<FieldShape, ErrorShape extends BaseErrorShape> = Valida
380
585
  /** 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}`). */
381
586
  ariaDescribedBy: string | undefined;
382
587
  /** Method to get nested fieldset for object fields under this field. */
383
- getFieldset<FieldsetShape = keyof NonNullable<FieldShape> extends never ? unknown : FieldShape>(): Fieldset<FieldsetShape, ErrorShape>;
588
+ getFieldset<FieldsetShape = keyof NonNullable<FieldShape> extends never ? unknown : FieldShape>(): Fieldset<FieldsetShape, ErrorShape, CustomFieldMetadata>;
384
589
  /** Method to get array of fields for list/array fields under this field. */
385
590
  getFieldList<FieldItemShape = [FieldShape] extends [
386
591
  Array<infer ItemShape> | null | undefined
387
- ] ? ItemShape : unknown>(): Array<FieldMetadata<FieldItemShape, ErrorShape>>;
388
- };
389
- export type SatisfyComponentProps<ElementType extends React.ElementType, CustomProps extends React.ComponentPropsWithoutRef<ElementType>> = CustomProps;
592
+ ] ? ItemShape : unknown>(): Array<FieldMetadata<FieldItemShape, ErrorShape, CustomFieldMetadata>>;
593
+ } & RestoreFieldShape<CustomFieldMetadata, FieldShape>>>;
390
594
  /**
391
- * Interface for extending field metadata with additional properties.
595
+ * Field metadata without custom extensions. This is the type received in `extendFieldMetadata`.
596
+ * Equivalent to `FieldMetadata<FieldShape, ErrorShape, {}>`.
392
597
  */
393
- export interface CustomMetadata<FieldShape = any, ErrorShape extends BaseErrorShape = DefaultErrorShape> {
394
- }
395
- export type CustomMetadataDefinition = <FieldShape, ErrorShape extends BaseErrorShape>(metadata: BaseMetadata<FieldShape, ErrorShape>) => keyof CustomMetadata<FieldShape, ErrorShape> extends never ? {} : CustomMetadata<any, any>;
396
- /** Field metadata object containing field state, validation attributes, and nested field access methods. */
397
- export type FieldMetadata<FieldShape, ErrorShape extends BaseErrorShape = DefaultErrorShape> = Readonly<(keyof CustomMetadata<FieldShape, ErrorShape> extends never ? {} : CustomMetadata<FieldShape, ErrorShape>) & BaseMetadata<FieldShape, ErrorShape>>;
598
+ export type BaseFieldMetadata<FieldShape, ErrorShape extends BaseErrorShape> = FieldMetadata<FieldShape, ErrorShape, {}>;
599
+ /**
600
+ * @deprecated Renamed to `BaseFieldMetadata`. This will be removed in the next minor version.
601
+ */
602
+ export type BaseMetadata<FieldShape, ErrorShape extends BaseErrorShape> = BaseFieldMetadata<FieldShape, ErrorShape>;
603
+ /**
604
+ * @deprecated Use `configureForms()` with the `extendFieldMetadata` option instead.
605
+ */
606
+ export type CustomMetadataDefinition = <FieldShape, ErrorShape extends BaseErrorShape>(metadata: BaseFieldMetadata<FieldShape, ErrorShape>) => keyof CustomMetadata<FieldShape, ErrorShape> extends never ? {} : CustomMetadata<any, any>;
398
607
  /** Fieldset object containing all form fields as properties with their respective field metadata. */
399
- export type Fieldset<FieldShape, ErrorShape extends BaseErrorShape = DefaultErrorShape> = {
400
- [Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], ErrorShape>;
608
+ export type Fieldset<FieldShape, ErrorShape extends BaseErrorShape = DefaultErrorShape, CustomFieldMetadata extends Record<string, unknown> = DefaultCustomMetadata<FieldShape, ErrorShape>> = {
609
+ [Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], ErrorShape, CustomFieldMetadata>;
401
610
  };
402
611
  /** Form-level metadata and state object containing validation status, errors, and field access methods. */
403
- export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape> = Readonly<{
612
+ export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape, CustomFormMetadata extends Record<string, unknown> = {}, CustomFieldMetadata extends Record<string, unknown> = {}> = Readonly<{
404
613
  /** Unique identifier that changes on form reset */
405
614
  key: string;
406
615
  /** The form's unique identifier. */
@@ -432,14 +641,19 @@ export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape>
432
641
  /** The current state of the form */
433
642
  context: FormContext<ErrorShape>;
434
643
  /** Method to get metadata for a specific field by name. */
435
- getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, ErrorShape>;
644
+ getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, ErrorShape, CustomFieldMetadata>;
436
645
  /** Method to get a fieldset object for nested object fields. */
437
- getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<keyof NonNullable<FieldShape> extends never ? unknown : FieldShape, ErrorShape>;
646
+ getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<keyof NonNullable<FieldShape> extends never ? unknown : FieldShape, ErrorShape, CustomFieldMetadata>;
438
647
  /** Method to get an array of field objects for array fields. */
439
648
  getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
440
649
  FieldShape
441
- ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, ErrorShape>>;
442
- }>;
650
+ ] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, ErrorShape, CustomFieldMetadata>>;
651
+ } & CustomFormMetadata>;
652
+ /**
653
+ * Form metadata without custom extensions. This is the type received in `extendFormMetadata`.
654
+ * Equivalent to `FormMetadata<ErrorShape, {}, CustomFieldMetadata>`.
655
+ */
656
+ export type BaseFormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape, CustomFieldMetadata extends Record<string, unknown> = {}> = FormMetadata<ErrorShape, {}, CustomFieldMetadata>;
443
657
  export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
444
658
  error: FormError<ErrorShape> | null;
445
659
  value?: Value;
@@ -508,5 +722,65 @@ export type SubmitContext<FormShape extends Record<string, any> = Record<string,
508
722
  }) => void;
509
723
  };
510
724
  export type SubmitHandler<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<FormShape, ErrorShape, Value>) => void | Promise<void>;
725
+ /**
726
+ * Infer the base error shape from a FormsConfig.
727
+ *
728
+ * @example
729
+ * ```ts
730
+ * const { config } = configureForms({ isError: shape<{ message: string }>() });
731
+ * type ErrorShape = InferBaseErrorShape<typeof config>; // { message: string }
732
+ * ```
733
+ */
734
+ export type InferBaseErrorShape<Config> = Config extends FormsConfig<infer ErrorShape, any, any, any> ? ErrorShape : string;
735
+ /**
736
+ * Infer the custom form metadata extension from a FormsConfig.
737
+ * Use this to compose with FormMetadata, FieldMetadata, or Fieldset types.
738
+ *
739
+ * @example
740
+ * ```ts
741
+ * const { config } = configureForms({
742
+ * extendFormMetadata: (meta) => ({ customProp: meta.id })
743
+ * });
744
+ * type MyFormMetadata = FormMetadata<
745
+ * InferBaseErrorShape<typeof config>,
746
+ * InferCustomFormMetadata<typeof config>,
747
+ * InferCustomFieldMetadata<typeof config>
748
+ * >;
749
+ * ```
750
+ */
751
+ export type InferCustomFormMetadata<Config> = Config extends FormsConfig<any, any, infer CustomFormMetadata, any> ? CustomFormMetadata : {};
752
+ /**
753
+ * Infer the custom field metadata extension from a FormsConfig.
754
+ * Use this to compose with FieldMetadata or Fieldset types.
755
+ *
756
+ * @example
757
+ * ```ts
758
+ * const { config } = configureForms({
759
+ * extendFieldMetadata: (meta) => ({ inputProps: { name: meta.name } })
760
+ * });
761
+ * type MyFieldMetadata<T> = FieldMetadata<T, InferBaseErrorShape<typeof config>, InferCustomFieldMetadata<typeof config>>;
762
+ * type MyFieldset<T> = Fieldset<T, InferBaseErrorShape<typeof config>, InferCustomFieldMetadata<typeof config>>;
763
+ * ```
764
+ */
765
+ export type InferCustomFieldMetadata<Config> = Config extends FormsConfig<any, any, any, infer CustomFieldMetadata> ? CustomFieldMetadata : {};
766
+ /**
767
+ * Transform a type to make specific keys conditional based on FieldShape.
768
+ * Keys in ConditionalKeys will only be present when FieldShape extends the specified type.
769
+ * Uses ConditionalFieldMetadata wrapper that RestoreFieldShape will detect and evaluate.
770
+ *
771
+ * @example
772
+ * ```ts
773
+ * type Result = MakeConditional<
774
+ * { textFieldProps: {...}, dateRangePickerProps: {...} },
775
+ * { dateRangePickerProps: { start: string; end: string } }
776
+ * >;
777
+ * // dateRangePickerProps is wrapped with ConditionalFieldMetadata
778
+ * // and will only be present when FieldShape extends { start: string; end: string }
779
+ * ```
780
+ */
781
+ export type MakeConditional<T, ConditionalKeys extends Record<string, unknown>> = Omit<T, keyof ConditionalKeys> & {
782
+ [K in keyof ConditionalKeys]: K extends keyof T ? ConditionalFieldMetadata<T[K], ConditionalKeys[K]> : never;
783
+ };
784
+ export type MaybePromise<T> = T | Promise<T>;
511
785
  export {};
512
786
  //# sourceMappingURL=types.d.ts.map