@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 +1 -1
- package/dist/future/hooks.d.ts +13 -23
- package/dist/future/hooks.js +62 -48
- package/dist/future/hooks.mjs +61 -48
- package/dist/future/index.d.ts +2 -2
- package/dist/future/index.js +1 -0
- package/dist/future/index.mjs +1 -1
- package/dist/future/intent.js +16 -10
- package/dist/future/intent.mjs +16 -10
- package/dist/future/state.d.ts +14 -10
- package/dist/future/state.js +89 -49
- package/dist/future/state.mjs +89 -49
- package/dist/future/types.d.ts +152 -65
- package/dist/future/util.d.ts +0 -1
- package/dist/future/util.js +4 -32
- package/dist/future/util.mjs +6 -33
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
Version 1.
|
|
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
|
|
package/dist/future/hooks.d.ts
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
import { type Serialize, type SubmissionResult,
|
|
1
|
+
import { type Serialize, type SubmissionResult, createGlobalFormsObserver } from '@conform-to/dom/future';
|
|
2
2
|
import { useEffect } from 'react';
|
|
3
|
-
import type { FormContext,
|
|
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
|
|
6
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
|
81
|
+
export declare function useFormMetadata(options?: {
|
|
92
82
|
formId?: string;
|
|
93
|
-
}): FormMetadata
|
|
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.
|
package/dist/future/hooks.js
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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((
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
319
|
-
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 =
|
|
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 =
|
|
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:
|
|
414
|
-
|
|
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:
|
|
417
|
-
|
|
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
|
|
456
|
+
var globalOptions = react.useContext(GlobalFormOptionsContext);
|
|
446
457
|
var context = useFormContext(options.formId);
|
|
447
458
|
var formMetadata = react.useMemo(() => state.getFormMetadata(context, {
|
|
448
|
-
serialize:
|
|
449
|
-
|
|
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
|
|
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:
|
|
480
|
-
|
|
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
|
|
506
|
-
return react.useMemo(() => dom.createIntentDispatcher(() => dom.getFormElement(formRef),
|
|
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(
|
|
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(
|
|
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.
|
|
748
|
-
exports.
|
|
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;
|
package/dist/future/hooks.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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((
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
315
|
-
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 =
|
|
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 =
|
|
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:
|
|
410
|
-
|
|
418
|
+
serialize: globalOptions.serialize,
|
|
419
|
+
customize: globalOptions.defineCustomMetadata
|
|
420
|
+
}), [context, globalOptions.serialize, globalOptions.defineCustomMetadata]);
|
|
411
421
|
var fields = useMemo(() => getFieldset(context, {
|
|
412
|
-
serialize:
|
|
413
|
-
|
|
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
|
|
452
|
+
var globalOptions = useContext(GlobalFormOptionsContext);
|
|
442
453
|
var context = useFormContext(options.formId);
|
|
443
454
|
var formMetadata = useMemo(() => getFormMetadata(context, {
|
|
444
|
-
serialize:
|
|
445
|
-
|
|
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
|
|
483
|
+
var globalOptions = useContext(GlobalFormOptionsContext);
|
|
472
484
|
var context = useFormContext(options.formId);
|
|
473
485
|
var field = useMemo(() => getField(context, {
|
|
474
486
|
name,
|
|
475
|
-
serialize:
|
|
476
|
-
|
|
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
|
|
502
|
-
return useMemo(() => createIntentDispatcher(() => getFormElement(formRef),
|
|
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(
|
|
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(
|
|
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 {
|
|
756
|
+
export { FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
|
package/dist/future/index.d.ts
CHANGED
|
@@ -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
|
package/dist/future/index.js
CHANGED
|
@@ -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;
|
package/dist/future/index.mjs
CHANGED
|
@@ -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';
|