@conform-to/react 1.11.0 → 1.12.1

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.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.11.0 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.12.1 / License MIT / Copyright (c) 2025 Edmund Hung
11
11
 
12
12
  Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards.
13
13
 
@@ -1,24 +1,11 @@
1
- import { type Serialize, type SubmissionResult, serialize } from '@conform-to/dom/future';
1
+ import { type Serialize, type SubmissionResult, createGlobalFormsObserver } from '@conform-to/dom/future';
2
2
  import { useEffect } from 'react';
3
- import type { FormContext, IntentDispatcher, FormMetadata, Fieldset, FormOptions, FieldName, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef } from './types';
3
+ import type { FormContext, IntentDispatcher, FormMetadata, Fieldset, GlobalFormOptions, FormOptions, FieldName, FieldMetadata, Control, Selector, UseFormDataOptions, ValidateHandler, ErrorHandler, SubmitHandler, FormState, FormRef, BaseErrorShape, DefaultErrorShape } from './types';
4
4
  export declare const INITIAL_KEY = "INITIAL_KEY";
5
- export declare const FormConfig: import("react").Context<{
6
- intentName: string;
7
- observer: {
8
- onFieldUpdate(callback: (event: {
9
- type: "input" | "reset" | "mutation";
10
- target: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
11
- }) => void): () => void;
12
- onFormUpdate(callback: (event: {
13
- type: "submit" | "input" | "reset" | "mutation";
14
- target: HTMLFormElement;
15
- submitter?: HTMLInputElement | HTMLButtonElement | null;
16
- }) => void): () => void;
17
- dispose(): void;
18
- };
19
- serialize: typeof serialize;
5
+ export declare const GlobalFormOptionsContext: import("react").Context<GlobalFormOptions & {
6
+ observer: ReturnType<typeof createGlobalFormsObserver>;
20
7
  }>;
21
- export declare const Form: import("react").Context<FormContext<string>[]>;
8
+ export declare const FormContextContext: import("react").Context<FormContext<string>[]>;
22
9
  /**
23
10
  * Provides form context to child components.
24
11
  * Stacks contexts to support nested forms, with latest context taking priority.
@@ -27,7 +14,10 @@ export declare function FormProvider(props: {
27
14
  context: FormContext;
28
15
  children: React.ReactNode;
29
16
  }): React.ReactElement;
30
- export declare function useFormContext(formId?: string): FormContext<any>;
17
+ export declare function FormOptionsProvider(props: Partial<GlobalFormOptions> & {
18
+ children: React.ReactNode;
19
+ }): React.ReactElement;
20
+ export declare function useFormContext(formId?: string): FormContext;
31
21
  /**
32
22
  * Core form hook that manages form state, validation, and submission.
33
23
  * Handles both sync and async validation, intent dispatching, and DOM updates.
@@ -65,7 +55,7 @@ export declare function useConform<ErrorShape, Value = undefined>(formRef: FormR
65
55
  * );
66
56
  * ```
67
57
  */
68
- export declare function useForm<FormShape extends Record<string, any> = Record<string, any>, ErrorShape = string, Value = undefined>(options: FormOptions<FormShape, ErrorShape, Value>): {
58
+ export declare function useForm<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined>(options: FormOptions<FormShape, ErrorShape, Value>): {
69
59
  form: FormMetadata<ErrorShape>;
70
60
  fields: Fieldset<FormShape, ErrorShape>;
71
61
  intent: IntentDispatcher;
@@ -88,9 +78,9 @@ export declare function useForm<FormShape extends Record<string, any> = Record<s
88
78
  * }
89
79
  * ```
90
80
  */
91
- export declare function useFormMetadata<ErrorShape = string[]>(options?: {
81
+ export declare function useFormMetadata(options?: {
92
82
  formId?: string;
93
- }): FormMetadata<ErrorShape>;
83
+ }): FormMetadata;
94
84
  /**
95
85
  * A React hook that provides access to a specific field's metadata and state.
96
86
  * Requires `FormProvider` context when used in child components.
@@ -111,9 +101,9 @@ export declare function useFormMetadata<ErrorShape = string[]>(options?: {
111
101
  * }
112
102
  * ```
113
103
  */
114
- export declare function useField<FieldShape = any, ErrorShape = string>(name: FieldName<FieldShape>, options?: {
104
+ export declare function useField<FieldShape = any>(name: FieldName<FieldShape>, options?: {
115
105
  formId?: string;
116
- }): FieldMetadata<FieldShape, ErrorShape>;
106
+ }): FieldMetadata<FieldShape>;
117
107
  /**
118
108
  * A React hook that provides an intent dispatcher for programmatic form actions.
119
109
  * Intent dispatchers allow you to trigger form operations like validation, field updates,
@@ -12,30 +12,44 @@ var intent = require('./intent.js');
12
12
  var dom = require('./dom.js');
13
13
  var jsxRuntime = require('react/jsx-runtime');
14
14
 
15
+ var _excluded = ["children"];
15
16
  var INITIAL_KEY = 'INITIAL_KEY';
16
- var FormConfig = /*#__PURE__*/react.createContext({
17
+ var GlobalFormOptionsContext = /*#__PURE__*/react.createContext({
17
18
  intentName: future.DEFAULT_INTENT_NAME,
18
19
  observer: future.createGlobalFormsObserver(),
19
- serialize: future.serialize
20
+ serialize: future.serialize,
21
+ shouldValidate: 'onSubmit'
20
22
  });
21
- var Form = /*#__PURE__*/react.createContext([]);
23
+ var FormContextContext = /*#__PURE__*/react.createContext([]);
22
24
 
23
25
  /**
24
26
  * Provides form context to child components.
25
27
  * Stacks contexts to support nested forms, with latest context taking priority.
26
28
  */
27
29
  function FormProvider(props) {
28
- var stack = react.useContext(Form);
30
+ var stack = react.useContext(FormContextContext);
29
31
  var value = react.useMemo(
30
32
  // Put the latest form context first to ensure that to be the first one found
31
33
  () => [props.context].concat(stack), [stack, props.context]);
32
- return /*#__PURE__*/jsxRuntime.jsx(Form.Provider, {
34
+ return /*#__PURE__*/jsxRuntime.jsx(FormContextContext.Provider, {
33
35
  value: value,
34
36
  children: props.children
35
37
  });
36
38
  }
39
+ function FormOptionsProvider(props) {
40
+ var {
41
+ children
42
+ } = props,
43
+ providedOptions = _rollupPluginBabelHelpers.objectWithoutProperties(props, _excluded);
44
+ var defaultOptions = react.useContext(GlobalFormOptionsContext);
45
+ var options = react.useMemo(() => _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, defaultOptions), providedOptions), [defaultOptions, providedOptions]);
46
+ return /*#__PURE__*/jsxRuntime.jsx(GlobalFormOptionsContext.Provider, {
47
+ value: options,
48
+ children: children
49
+ });
50
+ }
37
51
  function useFormContext(formId) {
38
- var contexts = react.useContext(Form);
52
+ var contexts = react.useContext(FormContextContext);
39
53
  var context = formId ? contexts.find(context => formId === context.formId) : contexts[0];
40
54
  if (!context) {
41
55
  throw new Error('No form context found; Have you render a <FormProvider /> with the corresponding form context?');
@@ -72,11 +86,11 @@ function useConform(formRef, options) {
72
86
  var lastIntentedValueRef = react.useRef();
73
87
  var lastAsyncResultRef = react.useRef(null);
74
88
  var abortControllerRef = react.useRef(null);
75
- var handleSubmission = react.useCallback((result, options) => {
89
+ var handleSubmission = react.useCallback((type, result) => {
76
90
  var _optionsRef$current$o, _optionsRef$current;
77
91
  var intent$1 = result.submission.intent ? intent.deserializeIntent(result.submission.intent) : null;
78
92
  setState(state$1 => state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, {
79
- type: options.type,
93
+ type,
80
94
  intent: intent$1,
81
95
  ctx: {
82
96
  handlers: intent.actionHandlers,
@@ -105,9 +119,7 @@ function useConform(formRef, options) {
105
119
  react.useEffect(() => {
106
120
  // To avoid re-applying the same result twice
107
121
  if (lastResult && lastResult !== lastResultRef.current) {
108
- handleSubmission(lastResult, {
109
- type: 'server'
110
- });
122
+ handleSubmission('server', lastResult);
111
123
  lastResultRef.current = lastResult;
112
124
  }
113
125
  }, [lastResult, handleSubmission]);
@@ -168,7 +180,7 @@ function useConform(formRef, options) {
168
180
  // Patch missing fields in the submission object
169
181
  for (var element of formElement.elements) {
170
182
  if (future.isFieldElement(element) && element.name) {
171
- util.appendUniqueItem(submission.fields, element.name);
183
+ submission.fields = util.appendUniqueItem(submission.fields, element.name);
172
184
  }
173
185
  }
174
186
 
@@ -219,9 +231,7 @@ function useConform(formRef, options) {
219
231
  // There is no need to flush the update in this case
220
232
  if (!abortController.signal.aborted) {
221
233
  submissionResult.error = error;
222
- handleSubmission(submissionResult, {
223
- type: 'server'
224
- });
234
+ handleSubmission('server', submissionResult);
225
235
 
226
236
  // If the form is meant to be submitted and there is no error
227
237
  if (error === null && !submission.intent) {
@@ -239,9 +249,7 @@ function useConform(formRef, options) {
239
249
  }
240
250
  });
241
251
  }
242
- handleSubmission(submissionResult, {
243
- type: 'client'
244
- });
252
+ handleSubmission('client', submissionResult);
245
253
  if (
246
254
  // If client validation happens
247
255
  (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && (
@@ -269,9 +277,7 @@ function useConform(formRef, options) {
269
277
  var _submissionResult = future.report(result.submission, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, {
270
278
  keepFiles: true
271
279
  }));
272
- handleSubmission(_submissionResult, {
273
- type: 'server'
274
- });
280
+ handleSubmission('server', _submissionResult);
275
281
  }
276
282
  }
277
283
  });
@@ -311,13 +317,14 @@ function useForm(options) {
311
317
  defaultValue,
312
318
  constraint
313
319
  } = options;
314
- var config = react.useContext(FormConfig);
320
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
315
321
  var optionsRef = useLatest(options);
322
+ var globalOptionsRef = useLatest(globalOptions);
316
323
  var fallbackId = react.useId();
317
324
  var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId);
318
325
  var [state$1, handleSubmit] = useConform(formId, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, {
319
- serialize: config.serialize,
320
- intentName: config.intentName,
326
+ serialize: globalOptions.serialize,
327
+ intentName: globalOptions.intentName,
321
328
  onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : dom.focusFirstInvalidField,
322
329
  onValidate(ctx) {
323
330
  var _options$onValidate, _options$onValidate2;
@@ -371,7 +378,7 @@ function useForm(options) {
371
378
  constraint: constraint !== null && constraint !== void 0 ? constraint : null,
372
379
  handleSubmit: handleSubmit,
373
380
  handleInput(event) {
374
- var _optionsRef$current$o5, _optionsRef$current4;
381
+ var _optionsRef$current$o5, _optionsRef$current4, _globalOptionsRef$cur;
375
382
  if (!future.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) {
376
383
  return;
377
384
  }
@@ -383,15 +390,15 @@ function useForm(options) {
383
390
  return;
384
391
  }
385
392
  var {
386
- shouldValidate = 'onSubmit',
387
- shouldRevalidate = shouldValidate
393
+ shouldValidate = globalOptionsRef.current.shouldValidate,
394
+ shouldRevalidate = (_globalOptionsRef$cur = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur !== void 0 ? _globalOptionsRef$cur : shouldValidate
388
395
  } = optionsRef.current;
389
396
  if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
390
397
  intent.validate(event.target.name);
391
398
  }
392
399
  },
393
400
  handleBlur(event) {
394
- var _optionsRef$current$o6, _optionsRef$current5;
401
+ var _optionsRef$current$o6, _optionsRef$current5, _globalOptionsRef$cur2;
395
402
  if (!future.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) {
396
403
  return;
397
404
  }
@@ -403,20 +410,22 @@ function useForm(options) {
403
410
  return;
404
411
  }
405
412
  var {
406
- shouldValidate = 'onSubmit',
407
- shouldRevalidate = shouldValidate
413
+ shouldValidate = globalOptionsRef.current.shouldValidate,
414
+ shouldRevalidate = (_globalOptionsRef$cur2 = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur2 !== void 0 ? _globalOptionsRef$cur2 : shouldValidate
408
415
  } = optionsRef.current;
409
416
  if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
410
417
  intent.validate(event.target.name);
411
418
  }
412
419
  }
413
- }), [formId, state$1, defaultValue, constraint, handleSubmit, intent, optionsRef]);
420
+ }), [formId, state$1, defaultValue, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
414
421
  var form = react.useMemo(() => state.getFormMetadata(context, {
415
- serialize: config.serialize
416
- }), [context, config.serialize]);
422
+ serialize: globalOptions.serialize,
423
+ customize: globalOptions.defineCustomMetadata
424
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
417
425
  var fields = react.useMemo(() => state.getFieldset(context, {
418
- serialize: config.serialize
419
- }), [context, config.serialize]);
426
+ serialize: globalOptions.serialize,
427
+ customize: globalOptions.defineCustomMetadata
428
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
420
429
  return {
421
430
  form,
422
431
  fields,
@@ -444,11 +453,12 @@ function useForm(options) {
444
453
  */
445
454
  function useFormMetadata() {
446
455
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
447
- var config = react.useContext(FormConfig);
456
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
448
457
  var context = useFormContext(options.formId);
449
458
  var formMetadata = react.useMemo(() => state.getFormMetadata(context, {
450
- serialize: config.serialize
451
- }), [context, config.serialize]);
459
+ serialize: globalOptions.serialize,
460
+ customize: globalOptions.defineCustomMetadata
461
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
452
462
  return formMetadata;
453
463
  }
454
464
 
@@ -474,12 +484,13 @@ function useFormMetadata() {
474
484
  */
475
485
  function useField(name) {
476
486
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
477
- var config = react.useContext(FormConfig);
487
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
478
488
  var context = useFormContext(options.formId);
479
489
  var field = react.useMemo(() => state.getField(context, {
480
490
  name,
481
- serialize: config.serialize
482
- }), [context, name, config.serialize]);
491
+ serialize: globalOptions.serialize,
492
+ customize: globalOptions.defineCustomMetadata
493
+ }), [context, name, globalOptions.serialize, globalOptions.defineCustomMetadata]);
483
494
  return field;
484
495
  }
485
496
 
@@ -504,8 +515,8 @@ function useField(name) {
504
515
  * ```
505
516
  */
506
517
  function useIntent(formRef) {
507
- var config = react.useContext(FormConfig);
508
- return react.useMemo(() => dom.createIntentDispatcher(() => dom.getFormElement(formRef), config.intentName), [formRef, config.intentName]);
518
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
519
+ return react.useMemo(() => dom.createIntentDispatcher(() => dom.getFormElement(formRef), globalOptions.intentName), [formRef, globalOptions.intentName]);
509
520
  }
510
521
 
511
522
  /**
@@ -521,7 +532,7 @@ function useIntent(formRef) {
521
532
  function useControl(options) {
522
533
  var {
523
534
  observer
524
- } = react.useContext(FormConfig);
535
+ } = react.useContext(GlobalFormOptionsContext);
525
536
  var inputRef = react.useRef(null);
526
537
  var eventDispatched = react.useRef({});
527
538
  var defaultSnapshot = dom.createDefaultSnapshot(options === null || options === void 0 ? void 0 : options.defaultValue, options === null || options === void 0 ? void 0 : options.defaultChecked, options === null || options === void 0 ? void 0 : options.value);
@@ -693,7 +704,7 @@ function useControl(options) {
693
704
  function useFormData(formRef, select, options) {
694
705
  var {
695
706
  observer
696
- } = react.useContext(FormConfig);
707
+ } = react.useContext(GlobalFormOptionsContext);
697
708
  var valueRef = react.useRef();
698
709
  var formDataRef = react.useRef(null);
699
710
  var value = react.useSyncExternalStore(react.useCallback(callback => {
@@ -746,9 +757,10 @@ function useLatest(value) {
746
757
  return ref;
747
758
  }
748
759
 
749
- exports.Form = Form;
750
- exports.FormConfig = FormConfig;
760
+ exports.FormContextContext = FormContextContext;
761
+ exports.FormOptionsProvider = FormOptionsProvider;
751
762
  exports.FormProvider = FormProvider;
763
+ exports.GlobalFormOptionsContext = GlobalFormOptionsContext;
752
764
  exports.INITIAL_KEY = INITIAL_KEY;
753
765
  exports.useConform = useConform;
754
766
  exports.useControl = useControl;
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
3
3
  import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, deepEqual, change, focus, blur, getFormData, parseSubmission, report, createSubmitEvent } from '@conform-to/dom/future';
4
4
  import { createContext, useContext, useMemo, useId, useRef, useEffect, useSyncExternalStore, useCallback, useState, useLayoutEffect } from 'react';
5
5
  import { resolveStandardSchemaResult, resolveValidateResult, appendUniqueItem } from './util.mjs';
@@ -8,30 +8,44 @@ import { deserializeIntent, actionHandlers, applyIntent } from './intent.mjs';
8
8
  import { focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, updateFormValue, getSubmitEvent } from './dom.mjs';
9
9
  import { jsx } from 'react/jsx-runtime';
10
10
 
11
+ var _excluded = ["children"];
11
12
  var INITIAL_KEY = 'INITIAL_KEY';
12
- var FormConfig = /*#__PURE__*/createContext({
13
+ var GlobalFormOptionsContext = /*#__PURE__*/createContext({
13
14
  intentName: DEFAULT_INTENT_NAME,
14
15
  observer: createGlobalFormsObserver(),
15
- serialize
16
+ serialize,
17
+ shouldValidate: 'onSubmit'
16
18
  });
17
- var Form = /*#__PURE__*/createContext([]);
19
+ var FormContextContext = /*#__PURE__*/createContext([]);
18
20
 
19
21
  /**
20
22
  * Provides form context to child components.
21
23
  * Stacks contexts to support nested forms, with latest context taking priority.
22
24
  */
23
25
  function FormProvider(props) {
24
- var stack = useContext(Form);
26
+ var stack = useContext(FormContextContext);
25
27
  var value = useMemo(
26
28
  // Put the latest form context first to ensure that to be the first one found
27
29
  () => [props.context].concat(stack), [stack, props.context]);
28
- return /*#__PURE__*/jsx(Form.Provider, {
30
+ return /*#__PURE__*/jsx(FormContextContext.Provider, {
29
31
  value: value,
30
32
  children: props.children
31
33
  });
32
34
  }
35
+ function FormOptionsProvider(props) {
36
+ var {
37
+ children
38
+ } = props,
39
+ providedOptions = _objectWithoutProperties(props, _excluded);
40
+ var defaultOptions = useContext(GlobalFormOptionsContext);
41
+ var options = useMemo(() => _objectSpread2(_objectSpread2({}, defaultOptions), providedOptions), [defaultOptions, providedOptions]);
42
+ return /*#__PURE__*/jsx(GlobalFormOptionsContext.Provider, {
43
+ value: options,
44
+ children: children
45
+ });
46
+ }
33
47
  function useFormContext(formId) {
34
- var contexts = useContext(Form);
48
+ var contexts = useContext(FormContextContext);
35
49
  var context = formId ? contexts.find(context => formId === context.formId) : contexts[0];
36
50
  if (!context) {
37
51
  throw new Error('No form context found; Have you render a <FormProvider /> with the corresponding form context?');
@@ -68,11 +82,11 @@ function useConform(formRef, options) {
68
82
  var lastIntentedValueRef = useRef();
69
83
  var lastAsyncResultRef = useRef(null);
70
84
  var abortControllerRef = useRef(null);
71
- var handleSubmission = useCallback((result, options) => {
85
+ var handleSubmission = useCallback((type, result) => {
72
86
  var _optionsRef$current$o, _optionsRef$current;
73
87
  var intent = result.submission.intent ? deserializeIntent(result.submission.intent) : null;
74
88
  setState(state => updateState(state, _objectSpread2(_objectSpread2({}, result), {}, {
75
- type: options.type,
89
+ type,
76
90
  intent,
77
91
  ctx: {
78
92
  handlers: actionHandlers,
@@ -101,9 +115,7 @@ function useConform(formRef, options) {
101
115
  useEffect(() => {
102
116
  // To avoid re-applying the same result twice
103
117
  if (lastResult && lastResult !== lastResultRef.current) {
104
- handleSubmission(lastResult, {
105
- type: 'server'
106
- });
118
+ handleSubmission('server', lastResult);
107
119
  lastResultRef.current = lastResult;
108
120
  }
109
121
  }, [lastResult, handleSubmission]);
@@ -164,7 +176,7 @@ function useConform(formRef, options) {
164
176
  // Patch missing fields in the submission object
165
177
  for (var element of formElement.elements) {
166
178
  if (isFieldElement(element) && element.name) {
167
- appendUniqueItem(submission.fields, element.name);
179
+ submission.fields = appendUniqueItem(submission.fields, element.name);
168
180
  }
169
181
  }
170
182
 
@@ -215,9 +227,7 @@ function useConform(formRef, options) {
215
227
  // There is no need to flush the update in this case
216
228
  if (!abortController.signal.aborted) {
217
229
  submissionResult.error = error;
218
- handleSubmission(submissionResult, {
219
- type: 'server'
220
- });
230
+ handleSubmission('server', submissionResult);
221
231
 
222
232
  // If the form is meant to be submitted and there is no error
223
233
  if (error === null && !submission.intent) {
@@ -235,9 +245,7 @@ function useConform(formRef, options) {
235
245
  }
236
246
  });
237
247
  }
238
- handleSubmission(submissionResult, {
239
- type: 'client'
240
- });
248
+ handleSubmission('client', submissionResult);
241
249
  if (
242
250
  // If client validation happens
243
251
  (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && (
@@ -265,9 +273,7 @@ function useConform(formRef, options) {
265
273
  var _submissionResult = report(result.submission, _objectSpread2(_objectSpread2({}, options), {}, {
266
274
  keepFiles: true
267
275
  }));
268
- handleSubmission(_submissionResult, {
269
- type: 'server'
270
- });
276
+ handleSubmission('server', _submissionResult);
271
277
  }
272
278
  }
273
279
  });
@@ -307,13 +313,14 @@ function useForm(options) {
307
313
  defaultValue,
308
314
  constraint
309
315
  } = options;
310
- var config = useContext(FormConfig);
316
+ var globalOptions = useContext(GlobalFormOptionsContext);
311
317
  var optionsRef = useLatest(options);
318
+ var globalOptionsRef = useLatest(globalOptions);
312
319
  var fallbackId = useId();
313
320
  var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId);
314
321
  var [state, handleSubmit] = useConform(formId, _objectSpread2(_objectSpread2({}, options), {}, {
315
- serialize: config.serialize,
316
- intentName: config.intentName,
322
+ serialize: globalOptions.serialize,
323
+ intentName: globalOptions.intentName,
317
324
  onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : focusFirstInvalidField,
318
325
  onValidate(ctx) {
319
326
  var _options$onValidate, _options$onValidate2;
@@ -367,7 +374,7 @@ function useForm(options) {
367
374
  constraint: constraint !== null && constraint !== void 0 ? constraint : null,
368
375
  handleSubmit: handleSubmit,
369
376
  handleInput(event) {
370
- var _optionsRef$current$o5, _optionsRef$current4;
377
+ var _optionsRef$current$o5, _optionsRef$current4, _globalOptionsRef$cur;
371
378
  if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
372
379
  return;
373
380
  }
@@ -379,15 +386,15 @@ function useForm(options) {
379
386
  return;
380
387
  }
381
388
  var {
382
- shouldValidate = 'onSubmit',
383
- shouldRevalidate = shouldValidate
389
+ shouldValidate = globalOptionsRef.current.shouldValidate,
390
+ shouldRevalidate = (_globalOptionsRef$cur = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur !== void 0 ? _globalOptionsRef$cur : shouldValidate
384
391
  } = optionsRef.current;
385
392
  if (isTouched(state, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
386
393
  intent.validate(event.target.name);
387
394
  }
388
395
  },
389
396
  handleBlur(event) {
390
- var _optionsRef$current$o6, _optionsRef$current5;
397
+ var _optionsRef$current$o6, _optionsRef$current5, _globalOptionsRef$cur2;
391
398
  if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
392
399
  return;
393
400
  }
@@ -399,20 +406,22 @@ function useForm(options) {
399
406
  return;
400
407
  }
401
408
  var {
402
- shouldValidate = 'onSubmit',
403
- shouldRevalidate = shouldValidate
409
+ shouldValidate = globalOptionsRef.current.shouldValidate,
410
+ shouldRevalidate = (_globalOptionsRef$cur2 = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur2 !== void 0 ? _globalOptionsRef$cur2 : shouldValidate
404
411
  } = optionsRef.current;
405
412
  if (isTouched(state, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
406
413
  intent.validate(event.target.name);
407
414
  }
408
415
  }
409
- }), [formId, state, defaultValue, constraint, handleSubmit, intent, optionsRef]);
416
+ }), [formId, state, defaultValue, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
410
417
  var form = useMemo(() => getFormMetadata(context, {
411
- serialize: config.serialize
412
- }), [context, config.serialize]);
418
+ serialize: globalOptions.serialize,
419
+ customize: globalOptions.defineCustomMetadata
420
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
413
421
  var fields = useMemo(() => getFieldset(context, {
414
- serialize: config.serialize
415
- }), [context, config.serialize]);
422
+ serialize: globalOptions.serialize,
423
+ customize: globalOptions.defineCustomMetadata
424
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
416
425
  return {
417
426
  form,
418
427
  fields,
@@ -440,11 +449,12 @@ function useForm(options) {
440
449
  */
441
450
  function useFormMetadata() {
442
451
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
443
- var config = useContext(FormConfig);
452
+ var globalOptions = useContext(GlobalFormOptionsContext);
444
453
  var context = useFormContext(options.formId);
445
454
  var formMetadata = useMemo(() => getFormMetadata(context, {
446
- serialize: config.serialize
447
- }), [context, config.serialize]);
455
+ serialize: globalOptions.serialize,
456
+ customize: globalOptions.defineCustomMetadata
457
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
448
458
  return formMetadata;
449
459
  }
450
460
 
@@ -470,12 +480,13 @@ function useFormMetadata() {
470
480
  */
471
481
  function useField(name) {
472
482
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
473
- var config = useContext(FormConfig);
483
+ var globalOptions = useContext(GlobalFormOptionsContext);
474
484
  var context = useFormContext(options.formId);
475
485
  var field = useMemo(() => getField(context, {
476
486
  name,
477
- serialize: config.serialize
478
- }), [context, name, config.serialize]);
487
+ serialize: globalOptions.serialize,
488
+ customize: globalOptions.defineCustomMetadata
489
+ }), [context, name, globalOptions.serialize, globalOptions.defineCustomMetadata]);
479
490
  return field;
480
491
  }
481
492
 
@@ -500,8 +511,8 @@ function useField(name) {
500
511
  * ```
501
512
  */
502
513
  function useIntent(formRef) {
503
- var config = useContext(FormConfig);
504
- return useMemo(() => createIntentDispatcher(() => getFormElement(formRef), config.intentName), [formRef, config.intentName]);
514
+ var globalOptions = useContext(GlobalFormOptionsContext);
515
+ return useMemo(() => createIntentDispatcher(() => getFormElement(formRef), globalOptions.intentName), [formRef, globalOptions.intentName]);
505
516
  }
506
517
 
507
518
  /**
@@ -517,7 +528,7 @@ function useIntent(formRef) {
517
528
  function useControl(options) {
518
529
  var {
519
530
  observer
520
- } = useContext(FormConfig);
531
+ } = useContext(GlobalFormOptionsContext);
521
532
  var inputRef = useRef(null);
522
533
  var eventDispatched = useRef({});
523
534
  var defaultSnapshot = createDefaultSnapshot(options === null || options === void 0 ? void 0 : options.defaultValue, options === null || options === void 0 ? void 0 : options.defaultChecked, options === null || options === void 0 ? void 0 : options.value);
@@ -689,7 +700,7 @@ function useControl(options) {
689
700
  function useFormData(formRef, select, options) {
690
701
  var {
691
702
  observer
692
- } = useContext(FormConfig);
703
+ } = useContext(GlobalFormOptionsContext);
693
704
  var valueRef = useRef();
694
705
  var formDataRef = useRef(null);
695
706
  var value = useSyncExternalStore(useCallback(callback => {
@@ -742,4 +753,4 @@ function useLatest(value) {
742
753
  return ref;
743
754
  }
744
755
 
745
- export { Form, FormConfig, FormProvider, INITIAL_KEY, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
756
+ export { FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
@@ -1,6 +1,6 @@
1
1
  export type { FormError, FormValue, Submission, SubmissionResult, } from '@conform-to/dom/future';
2
2
  export { parseSubmission, report, isDirty } from '@conform-to/dom/future';
3
- export type { Control, DefaultValue, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, FieldName, Fieldset, IntentDispatcher, } from './types';
4
- export { FormProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
3
+ export type { Control, DefaultValue, BaseMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, FieldName, Fieldset, IntentDispatcher, } from './types';
4
+ export { FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
5
5
  export { memoize } from './memoize';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -20,6 +20,7 @@ Object.defineProperty(exports, 'report', {
20
20
  enumerable: true,
21
21
  get: function () { return future.report; }
22
22
  });
23
+ exports.FormOptionsProvider = hooks.FormOptionsProvider;
23
24
  exports.FormProvider = hooks.FormProvider;
24
25
  exports.useControl = hooks.useControl;
25
26
  exports.useField = hooks.useField;
@@ -1,3 +1,3 @@
1
1
  export { isDirty, parseSubmission, report } from '@conform-to/dom/future';
2
- export { FormProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
2
+ export { FormOptionsProvider, FormProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
3
3
  export { memoize } from './memoize.mjs';