@conform-to/react 1.10.1 → 1.12.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.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.10.1 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.12.0 / 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, DefaultMetadata, 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,9 +55,9 @@ 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
- fields: Fieldset<FormShape, DefaultMetadata<ErrorShape>>;
60
+ fields: Fieldset<FormShape, ErrorShape>;
71
61
  intent: IntentDispatcher;
72
62
  };
73
63
  /**
@@ -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.
@@ -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
 
@@ -195,7 +207,8 @@ function useConform(formRef, options) {
195
207
  intent: submission.intent ? intent.deserializeIntent(submission.intent) : null,
196
208
  formElement,
197
209
  submitter: submitEvent.submitter,
198
- formData
210
+ formData,
211
+ schemaValue: undefined
199
212
  }) : {
200
213
  error: null
201
214
  };
@@ -218,9 +231,7 @@ function useConform(formRef, options) {
218
231
  // There is no need to flush the update in this case
219
232
  if (!abortController.signal.aborted) {
220
233
  submissionResult.error = error;
221
- handleSubmission(submissionResult, {
222
- type: 'server'
223
- });
234
+ handleSubmission('server', submissionResult);
224
235
 
225
236
  // If the form is meant to be submitted and there is no error
226
237
  if (error === null && !submission.intent) {
@@ -238,9 +249,7 @@ function useConform(formRef, options) {
238
249
  }
239
250
  });
240
251
  }
241
- handleSubmission(submissionResult, {
242
- type: 'client'
243
- });
252
+ handleSubmission('client', submissionResult);
244
253
  if (
245
254
  // If client validation happens
246
255
  (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && (
@@ -268,9 +277,7 @@ function useConform(formRef, options) {
268
277
  var _submissionResult = future.report(result.submission, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, {
269
278
  keepFiles: true
270
279
  }));
271
- handleSubmission(_submissionResult, {
272
- type: 'server'
273
- });
280
+ handleSubmission('server', _submissionResult);
274
281
  }
275
282
  }
276
283
  });
@@ -310,13 +317,14 @@ function useForm(options) {
310
317
  defaultValue,
311
318
  constraint
312
319
  } = options;
313
- var config = react.useContext(FormConfig);
320
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
314
321
  var optionsRef = useLatest(options);
322
+ var globalOptionsRef = useLatest(globalOptions);
315
323
  var fallbackId = react.useId();
316
324
  var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId);
317
325
  var [state$1, handleSubmit] = useConform(formId, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, {
318
- serialize: config.serialize,
319
- intentName: config.intentName,
326
+ serialize: globalOptions.serialize,
327
+ intentName: globalOptions.intentName,
320
328
  onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : dom.focusFirstInvalidField,
321
329
  onValidate(ctx) {
322
330
  var _options$onValidate, _options$onValidate2;
@@ -339,6 +347,7 @@ function useForm(options) {
339
347
  if (resolvedResult.error) {
340
348
  ctx.error = resolvedResult.error;
341
349
  }
350
+ ctx.schemaValue = resolvedResult.value;
342
351
  var validateResult = util.resolveValidateResult(options.onValidate(ctx));
343
352
  if (validateResult.syncResult) {
344
353
  var _validateResult$syncR, _validateResult$syncR2;
@@ -369,7 +378,7 @@ function useForm(options) {
369
378
  constraint: constraint !== null && constraint !== void 0 ? constraint : null,
370
379
  handleSubmit: handleSubmit,
371
380
  handleInput(event) {
372
- var _optionsRef$current$o5, _optionsRef$current4;
381
+ var _optionsRef$current$o5, _optionsRef$current4, _globalOptionsRef$cur;
373
382
  if (!future.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) {
374
383
  return;
375
384
  }
@@ -381,15 +390,15 @@ function useForm(options) {
381
390
  return;
382
391
  }
383
392
  var {
384
- shouldValidate = 'onSubmit',
385
- shouldRevalidate = shouldValidate
393
+ shouldValidate = globalOptionsRef.current.shouldValidate,
394
+ shouldRevalidate = (_globalOptionsRef$cur = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur !== void 0 ? _globalOptionsRef$cur : shouldValidate
386
395
  } = optionsRef.current;
387
396
  if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
388
397
  intent.validate(event.target.name);
389
398
  }
390
399
  },
391
400
  handleBlur(event) {
392
- var _optionsRef$current$o6, _optionsRef$current5;
401
+ var _optionsRef$current$o6, _optionsRef$current5, _globalOptionsRef$cur2;
393
402
  if (!future.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) {
394
403
  return;
395
404
  }
@@ -401,20 +410,22 @@ function useForm(options) {
401
410
  return;
402
411
  }
403
412
  var {
404
- shouldValidate = 'onSubmit',
405
- shouldRevalidate = shouldValidate
413
+ shouldValidate = globalOptionsRef.current.shouldValidate,
414
+ shouldRevalidate = (_globalOptionsRef$cur2 = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur2 !== void 0 ? _globalOptionsRef$cur2 : shouldValidate
406
415
  } = optionsRef.current;
407
416
  if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
408
417
  intent.validate(event.target.name);
409
418
  }
410
419
  }
411
- }), [formId, state$1, defaultValue, constraint, handleSubmit, intent, optionsRef]);
420
+ }), [formId, state$1, defaultValue, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
412
421
  var form = react.useMemo(() => state.getFormMetadata(context, {
413
- serialize: config.serialize
414
- }), [context, config.serialize]);
422
+ serialize: globalOptions.serialize,
423
+ customize: globalOptions.defineCustomMetadata
424
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
415
425
  var fields = react.useMemo(() => state.getFieldset(context, {
416
- serialize: config.serialize
417
- }), [context, config.serialize]);
426
+ serialize: globalOptions.serialize,
427
+ customize: globalOptions.defineCustomMetadata
428
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
418
429
  return {
419
430
  form,
420
431
  fields,
@@ -442,11 +453,12 @@ function useForm(options) {
442
453
  */
443
454
  function useFormMetadata() {
444
455
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
445
- var config = react.useContext(FormConfig);
456
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
446
457
  var context = useFormContext(options.formId);
447
458
  var formMetadata = react.useMemo(() => state.getFormMetadata(context, {
448
- serialize: config.serialize
449
- }), [context, config.serialize]);
459
+ serialize: globalOptions.serialize,
460
+ customize: globalOptions.defineCustomMetadata
461
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
450
462
  return formMetadata;
451
463
  }
452
464
 
@@ -472,12 +484,13 @@ function useFormMetadata() {
472
484
  */
473
485
  function useField(name) {
474
486
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
475
- var config = react.useContext(FormConfig);
487
+ var globalOptions = react.useContext(GlobalFormOptionsContext);
476
488
  var context = useFormContext(options.formId);
477
489
  var field = react.useMemo(() => state.getField(context, {
478
490
  name,
479
- serialize: config.serialize
480
- }), [context, name, config.serialize]);
491
+ serialize: globalOptions.serialize,
492
+ customize: globalOptions.defineCustomMetadata
493
+ }), [context, name, globalOptions.serialize, globalOptions.defineCustomMetadata]);
481
494
  return field;
482
495
  }
483
496
 
@@ -502,8 +515,8 @@ function useField(name) {
502
515
  * ```
503
516
  */
504
517
  function useIntent(formRef) {
505
- var config = react.useContext(FormConfig);
506
- 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]);
507
520
  }
508
521
 
509
522
  /**
@@ -519,7 +532,7 @@ function useIntent(formRef) {
519
532
  function useControl(options) {
520
533
  var {
521
534
  observer
522
- } = react.useContext(FormConfig);
535
+ } = react.useContext(GlobalFormOptionsContext);
523
536
  var inputRef = react.useRef(null);
524
537
  var eventDispatched = react.useRef({});
525
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);
@@ -691,7 +704,7 @@ function useControl(options) {
691
704
  function useFormData(formRef, select, options) {
692
705
  var {
693
706
  observer
694
- } = react.useContext(FormConfig);
707
+ } = react.useContext(GlobalFormOptionsContext);
695
708
  var valueRef = react.useRef();
696
709
  var formDataRef = react.useRef(null);
697
710
  var value = react.useSyncExternalStore(react.useCallback(callback => {
@@ -744,9 +757,10 @@ function useLatest(value) {
744
757
  return ref;
745
758
  }
746
759
 
747
- exports.Form = Form;
748
- exports.FormConfig = FormConfig;
760
+ exports.FormContextContext = FormContextContext;
761
+ exports.FormOptionsProvider = FormOptionsProvider;
749
762
  exports.FormProvider = FormProvider;
763
+ exports.GlobalFormOptionsContext = GlobalFormOptionsContext;
750
764
  exports.INITIAL_KEY = INITIAL_KEY;
751
765
  exports.useConform = useConform;
752
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
 
@@ -191,7 +203,8 @@ function useConform(formRef, options) {
191
203
  intent: submission.intent ? deserializeIntent(submission.intent) : null,
192
204
  formElement,
193
205
  submitter: submitEvent.submitter,
194
- formData
206
+ formData,
207
+ schemaValue: undefined
195
208
  }) : {
196
209
  error: null
197
210
  };
@@ -214,9 +227,7 @@ function useConform(formRef, options) {
214
227
  // There is no need to flush the update in this case
215
228
  if (!abortController.signal.aborted) {
216
229
  submissionResult.error = error;
217
- handleSubmission(submissionResult, {
218
- type: 'server'
219
- });
230
+ handleSubmission('server', submissionResult);
220
231
 
221
232
  // If the form is meant to be submitted and there is no error
222
233
  if (error === null && !submission.intent) {
@@ -234,9 +245,7 @@ function useConform(formRef, options) {
234
245
  }
235
246
  });
236
247
  }
237
- handleSubmission(submissionResult, {
238
- type: 'client'
239
- });
248
+ handleSubmission('client', submissionResult);
240
249
  if (
241
250
  // If client validation happens
242
251
  (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && (
@@ -264,9 +273,7 @@ function useConform(formRef, options) {
264
273
  var _submissionResult = report(result.submission, _objectSpread2(_objectSpread2({}, options), {}, {
265
274
  keepFiles: true
266
275
  }));
267
- handleSubmission(_submissionResult, {
268
- type: 'server'
269
- });
276
+ handleSubmission('server', _submissionResult);
270
277
  }
271
278
  }
272
279
  });
@@ -306,13 +313,14 @@ function useForm(options) {
306
313
  defaultValue,
307
314
  constraint
308
315
  } = options;
309
- var config = useContext(FormConfig);
316
+ var globalOptions = useContext(GlobalFormOptionsContext);
310
317
  var optionsRef = useLatest(options);
318
+ var globalOptionsRef = useLatest(globalOptions);
311
319
  var fallbackId = useId();
312
320
  var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId);
313
321
  var [state, handleSubmit] = useConform(formId, _objectSpread2(_objectSpread2({}, options), {}, {
314
- serialize: config.serialize,
315
- intentName: config.intentName,
322
+ serialize: globalOptions.serialize,
323
+ intentName: globalOptions.intentName,
316
324
  onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : focusFirstInvalidField,
317
325
  onValidate(ctx) {
318
326
  var _options$onValidate, _options$onValidate2;
@@ -335,6 +343,7 @@ function useForm(options) {
335
343
  if (resolvedResult.error) {
336
344
  ctx.error = resolvedResult.error;
337
345
  }
346
+ ctx.schemaValue = resolvedResult.value;
338
347
  var validateResult = resolveValidateResult(options.onValidate(ctx));
339
348
  if (validateResult.syncResult) {
340
349
  var _validateResult$syncR, _validateResult$syncR2;
@@ -365,7 +374,7 @@ function useForm(options) {
365
374
  constraint: constraint !== null && constraint !== void 0 ? constraint : null,
366
375
  handleSubmit: handleSubmit,
367
376
  handleInput(event) {
368
- var _optionsRef$current$o5, _optionsRef$current4;
377
+ var _optionsRef$current$o5, _optionsRef$current4, _globalOptionsRef$cur;
369
378
  if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
370
379
  return;
371
380
  }
@@ -377,15 +386,15 @@ function useForm(options) {
377
386
  return;
378
387
  }
379
388
  var {
380
- shouldValidate = 'onSubmit',
381
- shouldRevalidate = shouldValidate
389
+ shouldValidate = globalOptionsRef.current.shouldValidate,
390
+ shouldRevalidate = (_globalOptionsRef$cur = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur !== void 0 ? _globalOptionsRef$cur : shouldValidate
382
391
  } = optionsRef.current;
383
392
  if (isTouched(state, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
384
393
  intent.validate(event.target.name);
385
394
  }
386
395
  },
387
396
  handleBlur(event) {
388
- var _optionsRef$current$o6, _optionsRef$current5;
397
+ var _optionsRef$current$o6, _optionsRef$current5, _globalOptionsRef$cur2;
389
398
  if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
390
399
  return;
391
400
  }
@@ -397,20 +406,22 @@ function useForm(options) {
397
406
  return;
398
407
  }
399
408
  var {
400
- shouldValidate = 'onSubmit',
401
- shouldRevalidate = shouldValidate
409
+ shouldValidate = globalOptionsRef.current.shouldValidate,
410
+ shouldRevalidate = (_globalOptionsRef$cur2 = globalOptionsRef.current.shouldRevalidate) !== null && _globalOptionsRef$cur2 !== void 0 ? _globalOptionsRef$cur2 : shouldValidate
402
411
  } = optionsRef.current;
403
412
  if (isTouched(state, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
404
413
  intent.validate(event.target.name);
405
414
  }
406
415
  }
407
- }), [formId, state, defaultValue, constraint, handleSubmit, intent, optionsRef]);
416
+ }), [formId, state, defaultValue, constraint, handleSubmit, intent, optionsRef, globalOptionsRef]);
408
417
  var form = useMemo(() => getFormMetadata(context, {
409
- serialize: config.serialize
410
- }), [context, config.serialize]);
418
+ serialize: globalOptions.serialize,
419
+ customize: globalOptions.defineCustomMetadata
420
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
411
421
  var fields = useMemo(() => getFieldset(context, {
412
- serialize: config.serialize
413
- }), [context, config.serialize]);
422
+ serialize: globalOptions.serialize,
423
+ customize: globalOptions.defineCustomMetadata
424
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
414
425
  return {
415
426
  form,
416
427
  fields,
@@ -438,11 +449,12 @@ function useForm(options) {
438
449
  */
439
450
  function useFormMetadata() {
440
451
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
441
- var config = useContext(FormConfig);
452
+ var globalOptions = useContext(GlobalFormOptionsContext);
442
453
  var context = useFormContext(options.formId);
443
454
  var formMetadata = useMemo(() => getFormMetadata(context, {
444
- serialize: config.serialize
445
- }), [context, config.serialize]);
455
+ serialize: globalOptions.serialize,
456
+ customize: globalOptions.defineCustomMetadata
457
+ }), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
446
458
  return formMetadata;
447
459
  }
448
460
 
@@ -468,12 +480,13 @@ function useFormMetadata() {
468
480
  */
469
481
  function useField(name) {
470
482
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
471
- var config = useContext(FormConfig);
483
+ var globalOptions = useContext(GlobalFormOptionsContext);
472
484
  var context = useFormContext(options.formId);
473
485
  var field = useMemo(() => getField(context, {
474
486
  name,
475
- serialize: config.serialize
476
- }), [context, name, config.serialize]);
487
+ serialize: globalOptions.serialize,
488
+ customize: globalOptions.defineCustomMetadata
489
+ }), [context, name, globalOptions.serialize, globalOptions.defineCustomMetadata]);
477
490
  return field;
478
491
  }
479
492
 
@@ -498,8 +511,8 @@ function useField(name) {
498
511
  * ```
499
512
  */
500
513
  function useIntent(formRef) {
501
- var config = useContext(FormConfig);
502
- 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]);
503
516
  }
504
517
 
505
518
  /**
@@ -515,7 +528,7 @@ function useIntent(formRef) {
515
528
  function useControl(options) {
516
529
  var {
517
530
  observer
518
- } = useContext(FormConfig);
531
+ } = useContext(GlobalFormOptionsContext);
519
532
  var inputRef = useRef(null);
520
533
  var eventDispatched = useRef({});
521
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);
@@ -687,7 +700,7 @@ function useControl(options) {
687
700
  function useFormData(formRef, select, options) {
688
701
  var {
689
702
  observer
690
- } = useContext(FormConfig);
703
+ } = useContext(GlobalFormOptionsContext);
691
704
  var valueRef = useRef();
692
705
  var formDataRef = useRef(null);
693
706
  var value = useSyncExternalStore(useCallback(callback => {
@@ -740,4 +753,4 @@ function useLatest(value) {
740
753
  return ref;
741
754
  }
742
755
 
743
- 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';