@conform-to/react 0.7.4 → 0.8.0-pre.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 +4 -5
- package/helpers.d.ts +4 -4
- package/helpers.js +6 -4
- package/helpers.mjs +6 -4
- package/hooks.d.ts +9 -9
- package/hooks.js +49 -50
- package/hooks.mjs +48 -51
- package/index.d.ts +1 -1
- package/index.js +1 -0
- package/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -373,7 +373,6 @@ function Example() {
|
|
|
373
373
|
<input
|
|
374
374
|
{...conform.input(title, {
|
|
375
375
|
type: 'text',
|
|
376
|
-
ariaAttributes: true,
|
|
377
376
|
})}
|
|
378
377
|
/>
|
|
379
378
|
</form>
|
|
@@ -393,16 +392,16 @@ import { parse } from '@conform-to/react';
|
|
|
393
392
|
const formData = new FormData();
|
|
394
393
|
const submission = parse(formData, {
|
|
395
394
|
resolve({ email, password }) {
|
|
396
|
-
const error: Record<string, string> = {};
|
|
395
|
+
const error: Record<string, string[]> = {};
|
|
397
396
|
|
|
398
397
|
if (typeof email !== 'string') {
|
|
399
|
-
error.email = 'Email is required';
|
|
398
|
+
error.email = ['Email is required'];
|
|
400
399
|
} else if (!/^[^@]+@[^@]+$/.test(email)) {
|
|
401
|
-
error.email = 'Email is invalid';
|
|
400
|
+
error.email = ['Email is invalid'];
|
|
402
401
|
}
|
|
403
402
|
|
|
404
403
|
if (typeof password !== 'string') {
|
|
405
|
-
error.password = 'Password is required';
|
|
404
|
+
error.password = ['Password is required'];
|
|
406
405
|
}
|
|
407
406
|
|
|
408
407
|
if (error.email || error.password) {
|
package/helpers.d.ts
CHANGED
|
@@ -38,10 +38,10 @@ interface TextareaProps extends FormControlProps {
|
|
|
38
38
|
defaultValue?: string;
|
|
39
39
|
}
|
|
40
40
|
type BaseOptions = {
|
|
41
|
-
ariaAttributes?:
|
|
42
|
-
} | {
|
|
43
|
-
ariaAttributes: true;
|
|
41
|
+
ariaAttributes?: true;
|
|
44
42
|
description?: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
ariaAttributes: false;
|
|
45
45
|
};
|
|
46
46
|
type ControlOptions = BaseOptions & {
|
|
47
47
|
hidden?: boolean;
|
|
@@ -64,5 +64,5 @@ export declare function input<Schema extends File | File[]>(config: FieldConfig<
|
|
|
64
64
|
}): InputProps<Schema>;
|
|
65
65
|
export declare function select<Schema extends Primitive | Primitive[] | undefined | unknown>(config: FieldConfig<Schema>, options?: ControlOptions): SelectProps;
|
|
66
66
|
export declare function textarea<Schema extends Primitive | undefined | unknown>(config: FieldConfig<Schema>, options?: ControlOptions): TextareaProps;
|
|
67
|
-
export declare function fieldset<Schema extends Record<string,
|
|
67
|
+
export declare function fieldset<Schema extends Record<string, unknown> | undefined | unknown>(config: FieldConfig<Schema>, options?: BaseOptions): FormControlProps;
|
|
68
68
|
export { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED };
|
package/helpers.js
CHANGED
|
@@ -17,14 +17,16 @@ function cleanup(props) {
|
|
|
17
17
|
}
|
|
18
18
|
return props;
|
|
19
19
|
}
|
|
20
|
-
function getFormElementProps(config
|
|
21
|
-
var _config$error, _config$error2;
|
|
20
|
+
function getFormElementProps(config) {
|
|
21
|
+
var _options$ariaAttribut, _config$error, _config$error2;
|
|
22
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
23
|
+
var hasAriaAttributes = (_options$ariaAttribut = options.ariaAttributes) !== null && _options$ariaAttribut !== void 0 ? _options$ariaAttribut : true;
|
|
22
24
|
return cleanup({
|
|
23
25
|
id: config.id,
|
|
24
26
|
name: config.name,
|
|
25
27
|
form: config.form,
|
|
26
|
-
'aria-invalid':
|
|
27
|
-
'aria-describedby':
|
|
28
|
+
'aria-invalid': hasAriaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
|
|
29
|
+
'aria-describedby': hasAriaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options.ariaAttributes !== false && options.description ? config.descriptionId : undefined].reduce((result, id) => {
|
|
28
30
|
if (!result) {
|
|
29
31
|
return id;
|
|
30
32
|
}
|
package/helpers.mjs
CHANGED
|
@@ -13,14 +13,16 @@ function cleanup(props) {
|
|
|
13
13
|
}
|
|
14
14
|
return props;
|
|
15
15
|
}
|
|
16
|
-
function getFormElementProps(config
|
|
17
|
-
var _config$error, _config$error2;
|
|
16
|
+
function getFormElementProps(config) {
|
|
17
|
+
var _options$ariaAttribut, _config$error, _config$error2;
|
|
18
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
19
|
+
var hasAriaAttributes = (_options$ariaAttribut = options.ariaAttributes) !== null && _options$ariaAttribut !== void 0 ? _options$ariaAttribut : true;
|
|
18
20
|
return cleanup({
|
|
19
21
|
id: config.id,
|
|
20
22
|
name: config.name,
|
|
21
23
|
form: config.form,
|
|
22
|
-
'aria-invalid':
|
|
23
|
-
'aria-describedby':
|
|
24
|
+
'aria-invalid': hasAriaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
|
|
25
|
+
'aria-describedby': hasAriaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options.ariaAttributes !== false && options.description ? config.descriptionId : undefined].reduce((result, id) => {
|
|
24
26
|
if (!result) {
|
|
25
27
|
return id;
|
|
26
28
|
}
|
package/hooks.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface FieldConfig<Schema> extends FieldConstraint<Schema> {
|
|
|
5
5
|
id?: string;
|
|
6
6
|
name: string;
|
|
7
7
|
defaultValue?: FieldValue<Schema>;
|
|
8
|
-
initialError?: Record<string, string
|
|
8
|
+
initialError?: Record<string, string[]>;
|
|
9
9
|
form?: string;
|
|
10
10
|
descriptionId?: string;
|
|
11
11
|
errorId?: string;
|
|
@@ -26,6 +26,11 @@ type SubmissionResult = {
|
|
|
26
26
|
payload: Submission['payload'] | null;
|
|
27
27
|
error: Submission['error'];
|
|
28
28
|
};
|
|
29
|
+
interface ReportOptions {
|
|
30
|
+
formError?: string[];
|
|
31
|
+
resetForm?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function report(submission: Submission, options?: ReportOptions): SubmissionResult;
|
|
29
34
|
export interface FormConfig<Output extends Record<string, any>, Input extends Record<string, any> = Output> {
|
|
30
35
|
/**
|
|
31
36
|
* If the form id is provided, Id for label,
|
|
@@ -107,7 +112,7 @@ interface FormProps {
|
|
|
107
112
|
interface Form {
|
|
108
113
|
id?: string;
|
|
109
114
|
errorId?: string;
|
|
110
|
-
error: string;
|
|
115
|
+
error: string | undefined;
|
|
111
116
|
errors: string[];
|
|
112
117
|
ref: RefObject<HTMLFormElement>;
|
|
113
118
|
props: FormProps;
|
|
@@ -137,7 +142,7 @@ export interface FieldsetConfig<Schema extends Record<string, any> | undefined>
|
|
|
137
142
|
/**
|
|
138
143
|
* An object describing the initial error of each field
|
|
139
144
|
*/
|
|
140
|
-
initialError?: Record<string, string
|
|
145
|
+
initialError?: Record<string, string[]>;
|
|
141
146
|
/**
|
|
142
147
|
* An object describing the constraint of each field
|
|
143
148
|
*/
|
|
@@ -171,7 +176,6 @@ interface InputControl {
|
|
|
171
176
|
focus: () => void;
|
|
172
177
|
blur: () => void;
|
|
173
178
|
}
|
|
174
|
-
export declare function useEventListeners(type: string, ref: RefObject<FieldElement>): void;
|
|
175
179
|
/**
|
|
176
180
|
* Returns a ref object and a set of helpers that dispatch corresponding dom event.
|
|
177
181
|
*
|
|
@@ -196,11 +200,6 @@ export declare function validateConstraint(options: {
|
|
|
196
200
|
formData: FormData;
|
|
197
201
|
attributeValue: string;
|
|
198
202
|
}) => boolean>;
|
|
199
|
-
acceptMultipleErrors?: ({ name, intent, payload, }: {
|
|
200
|
-
name: string;
|
|
201
|
-
intent: string;
|
|
202
|
-
payload: Record<string, any>;
|
|
203
|
-
}) => boolean;
|
|
204
203
|
formatMessages?: ({ name, validity, constraint, defaultErrors, }: {
|
|
205
204
|
name: string;
|
|
206
205
|
validity: ValidityState;
|
|
@@ -208,6 +207,7 @@ export declare function validateConstraint(options: {
|
|
|
208
207
|
defaultErrors: string[];
|
|
209
208
|
}) => string[];
|
|
210
209
|
}): Submission;
|
|
210
|
+
export declare function getUniqueKey(): string;
|
|
211
211
|
export declare function reportSubmission(form: HTMLFormElement, submission: SubmissionResult): void;
|
|
212
212
|
export declare function getScope(intent: ReturnType<typeof parseIntent>): string | null;
|
|
213
213
|
export {};
|
package/hooks.js
CHANGED
|
@@ -6,20 +6,21 @@ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js
|
|
|
6
6
|
var dom = require('@conform-to/dom');
|
|
7
7
|
var react = require('react');
|
|
8
8
|
|
|
9
|
+
function report(submission, options) {
|
|
10
|
+
var _submission$error$;
|
|
11
|
+
return {
|
|
12
|
+
intent: submission.intent,
|
|
13
|
+
payload: options !== null && options !== void 0 && options.resetForm ? null : submission.payload,
|
|
14
|
+
error: options !== null && options !== void 0 && options.formError ? _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission.error), {}, {
|
|
15
|
+
'': options.formError.concat((_submission$error$ = submission.error['']) !== null && _submission$error$ !== void 0 ? _submission$error$ : [])
|
|
16
|
+
}) : submission.error
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
/**
|
|
10
21
|
* Properties to be applied to the form element
|
|
11
22
|
*/
|
|
12
23
|
|
|
13
|
-
/**
|
|
14
|
-
* Normalize error to an array of string.
|
|
15
|
-
*/
|
|
16
|
-
function normalizeError(error) {
|
|
17
|
-
if (!error) {
|
|
18
|
-
// This treat both empty string and undefined as no error.
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
return [].concat(error);
|
|
22
|
-
}
|
|
23
24
|
function useNoValidate(defaultNoValidate, validateBeforeHydrate) {
|
|
24
25
|
var [noValidate, setNoValidate] = react.useState(defaultNoValidate || !validateBeforeHydrate);
|
|
25
26
|
react.useEffect(() => {
|
|
@@ -78,42 +79,33 @@ function useFormError(ref, config) {
|
|
|
78
79
|
}
|
|
79
80
|
var result = {};
|
|
80
81
|
for (var [name, message] of Object.entries(config.initialError)) {
|
|
81
|
-
var
|
|
82
|
-
if (
|
|
83
|
-
result[
|
|
82
|
+
var [path, ...restPaths] = dom.getPaths(name);
|
|
83
|
+
if (typeof path !== 'undefined' && restPaths.length === 0) {
|
|
84
|
+
result[path] = message;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
return result;
|
|
87
88
|
});
|
|
88
89
|
react.useEffect(() => {
|
|
89
90
|
var handleInvalid = event => {
|
|
91
|
+
var _config$name;
|
|
90
92
|
var form = dom.getFormElement(ref.current);
|
|
91
93
|
var element = event.target;
|
|
92
|
-
|
|
94
|
+
var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : '';
|
|
95
|
+
if (!dom.isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) {
|
|
93
96
|
return;
|
|
94
97
|
}
|
|
95
|
-
var
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for (var i = 0; i <= scopePaths.length; i++) {
|
|
100
|
-
var path = fieldPaths[i];
|
|
101
|
-
if (i < scopePaths.length) {
|
|
102
|
-
// Skip if the field is not in the scope
|
|
103
|
-
if (path !== scopePaths[i]) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
key = path;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
98
|
+
var name = element.name.slice(prefix.length);
|
|
99
|
+
var [path, ...restPaths] = dom.getPaths(name);
|
|
100
|
+
if (typeof path === 'undefined' || restPaths.length > 0) {
|
|
101
|
+
return;
|
|
110
102
|
}
|
|
111
103
|
setError(prev => {
|
|
112
|
-
if (element.validationMessage === dom.getValidationMessage(prev[
|
|
104
|
+
if (element.validationMessage === dom.getValidationMessage(prev[path])) {
|
|
113
105
|
return prev;
|
|
114
106
|
}
|
|
115
107
|
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
|
|
116
|
-
[
|
|
108
|
+
[path]: dom.getErrors(element.validationMessage)
|
|
117
109
|
});
|
|
118
110
|
});
|
|
119
111
|
event.preventDefault();
|
|
@@ -141,31 +133,35 @@ function useFormError(ref, config) {
|
|
|
141
133
|
* @see https://conform.guide/api/react#useform
|
|
142
134
|
*/
|
|
143
135
|
function useForm() {
|
|
144
|
-
var _config$
|
|
136
|
+
var _config$lastSubmissio3, _config$lastSubmissio4;
|
|
145
137
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
146
138
|
var configRef = useConfigRef(config);
|
|
147
139
|
var ref = useFormRef(config.ref);
|
|
148
140
|
var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
|
|
149
141
|
var report = useFormReporter(ref, config.lastSubmission);
|
|
150
142
|
var [errors, setErrors] = react.useState(() => {
|
|
151
|
-
var _config$lastSubmissio;
|
|
152
|
-
return
|
|
143
|
+
var _config$lastSubmissio, _config$lastSubmissio2;
|
|
144
|
+
return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : [];
|
|
153
145
|
});
|
|
154
146
|
var initialError = react.useMemo(() => {
|
|
147
|
+
var _submission$error$sco;
|
|
155
148
|
var submission = config.lastSubmission;
|
|
156
149
|
if (!submission) {
|
|
157
150
|
return {};
|
|
158
151
|
}
|
|
159
152
|
var intent = dom.parseIntent(submission.intent);
|
|
160
153
|
var scope = getScope(intent);
|
|
161
|
-
|
|
162
|
-
|
|
154
|
+
if (typeof scope !== 'string') {
|
|
155
|
+
return submission.error;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
[scope]: (_submission$error$sco = submission.error[scope]) !== null && _submission$error$sco !== void 0 ? _submission$error$sco : []
|
|
163
159
|
};
|
|
164
160
|
}, [config.lastSubmission]);
|
|
165
161
|
// This payload from lastSubmission is only useful before hydration
|
|
166
162
|
// After hydration, any new payload on lastSubmission will be ignored
|
|
167
163
|
var [defaultValueFromLastSubmission, setDefaultValueFromLastSubmission] = react.useState( // @ts-expect-error defaultValue is not in Submission type
|
|
168
|
-
(_config$
|
|
164
|
+
(_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null);
|
|
169
165
|
var fieldset = useFieldset(ref, {
|
|
170
166
|
defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue,
|
|
171
167
|
initialError,
|
|
@@ -252,7 +248,7 @@ function useForm() {
|
|
|
252
248
|
shouldServerValidate
|
|
253
249
|
} = Object.entries(submission.error).reduce((result, _ref) => {
|
|
254
250
|
var [, error] = _ref;
|
|
255
|
-
for (var message of
|
|
251
|
+
for (var message of error) {
|
|
256
252
|
if (message === dom.VALIDATION_UNDEFINED) {
|
|
257
253
|
result.shouldServerValidate = true;
|
|
258
254
|
} else if (message !== dom.VALIDATION_SKIPPED) {
|
|
@@ -393,7 +389,7 @@ function useFieldList(ref, config) {
|
|
|
393
389
|
return dom.updateList(list, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
|
|
394
390
|
defaultValue: [
|
|
395
391
|
// Generate a random key to avoid conflicts
|
|
396
|
-
|
|
392
|
+
getUniqueKey(), intent.payload.defaultValue]
|
|
397
393
|
}));
|
|
398
394
|
default:
|
|
399
395
|
return dom.updateList(list, intent.payload);
|
|
@@ -475,7 +471,6 @@ function useFieldList(ref, config) {
|
|
|
475
471
|
* This basically makes it a no-op on server
|
|
476
472
|
*/
|
|
477
473
|
var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect;
|
|
478
|
-
|
|
479
474
|
/**
|
|
480
475
|
* Returns a ref object and a set of helpers that dispatch corresponding dom event.
|
|
481
476
|
*
|
|
@@ -641,12 +636,11 @@ function validateConstraint(options) {
|
|
|
641
636
|
return defaultErrors;
|
|
642
637
|
};
|
|
643
638
|
return dom.parse(formData, {
|
|
644
|
-
resolve(
|
|
639
|
+
resolve() {
|
|
645
640
|
var error = {};
|
|
646
641
|
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
|
|
647
642
|
var _loop = function _loop(_element3) {
|
|
648
643
|
if (dom.isFieldElement(_element3)) {
|
|
649
|
-
var _options$acceptMultip, _options$acceptMultip2;
|
|
650
644
|
var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : '';
|
|
651
645
|
var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => {
|
|
652
646
|
var [name, attributeValue = ''] = _ref6;
|
|
@@ -672,13 +666,8 @@ function validateConstraint(options) {
|
|
|
672
666
|
constraint,
|
|
673
667
|
defaultErrors: getDefaultErrors(_element3.validity, constraint)
|
|
674
668
|
});
|
|
675
|
-
var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 || (_options$acceptMultip2 = options.acceptMultipleErrors) === null || _options$acceptMultip2 === void 0 ? void 0 : _options$acceptMultip2.call(options, {
|
|
676
|
-
name,
|
|
677
|
-
payload,
|
|
678
|
-
intent
|
|
679
|
-
})) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
|
|
680
669
|
if (errors.length > 0) {
|
|
681
|
-
error[name] =
|
|
670
|
+
error[name] = errors;
|
|
682
671
|
}
|
|
683
672
|
}
|
|
684
673
|
};
|
|
@@ -691,10 +680,17 @@ function validateConstraint(options) {
|
|
|
691
680
|
}
|
|
692
681
|
});
|
|
693
682
|
}
|
|
683
|
+
function getUniqueKey() {
|
|
684
|
+
var [value] = crypto.getRandomValues(new Uint32Array(1));
|
|
685
|
+
if (!value) {
|
|
686
|
+
throw new Error('Fail to generate an unique key');
|
|
687
|
+
}
|
|
688
|
+
return value.toString(36);
|
|
689
|
+
}
|
|
694
690
|
function reportSubmission(form, submission) {
|
|
695
691
|
for (var [name, message] of Object.entries(submission.error)) {
|
|
696
692
|
// There is no need to create a placeholder button if all we want is to reset the error
|
|
697
|
-
if (message ===
|
|
693
|
+
if (message.length === 0) {
|
|
698
694
|
continue;
|
|
699
695
|
}
|
|
700
696
|
|
|
@@ -723,8 +719,9 @@ function reportSubmission(form, submission) {
|
|
|
723
719
|
var intent = dom.parseIntent(submission.intent);
|
|
724
720
|
var scope = getScope(intent);
|
|
725
721
|
for (var _element4 of dom.getFormControls(form)) {
|
|
722
|
+
var _submission$error$_el;
|
|
726
723
|
var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : '';
|
|
727
|
-
var messages =
|
|
724
|
+
var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : [];
|
|
728
725
|
if (scope === null || scope === _elementName) {
|
|
729
726
|
_element4.dataset.conformTouched = 'true';
|
|
730
727
|
}
|
|
@@ -752,6 +749,8 @@ function getScope(intent) {
|
|
|
752
749
|
|
|
753
750
|
exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
|
|
754
751
|
exports.getScope = getScope;
|
|
752
|
+
exports.getUniqueKey = getUniqueKey;
|
|
753
|
+
exports.report = report;
|
|
755
754
|
exports.reportSubmission = reportSubmission;
|
|
756
755
|
exports.useFieldList = useFieldList;
|
|
757
756
|
exports.useFieldset = useFieldset;
|
package/hooks.mjs
CHANGED
|
@@ -2,20 +2,21 @@ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHe
|
|
|
2
2
|
import { parseIntent, getFormData, parse, VALIDATION_UNDEFINED, VALIDATION_SKIPPED, getFormAction, getFormEncType, getFormMethod, getPaths, getName, isFieldElement, getErrors, getFormControls, getFormElement, updateList, getValidationMessage, focusFirstInvalidControl, isFocusableFormControl, requestIntent, validate } from '@conform-to/dom';
|
|
3
3
|
import { useState, useMemo, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
|
|
4
4
|
|
|
5
|
+
function report(submission, options) {
|
|
6
|
+
var _submission$error$;
|
|
7
|
+
return {
|
|
8
|
+
intent: submission.intent,
|
|
9
|
+
payload: options !== null && options !== void 0 && options.resetForm ? null : submission.payload,
|
|
10
|
+
error: options !== null && options !== void 0 && options.formError ? _objectSpread2(_objectSpread2({}, submission.error), {}, {
|
|
11
|
+
'': options.formError.concat((_submission$error$ = submission.error['']) !== null && _submission$error$ !== void 0 ? _submission$error$ : [])
|
|
12
|
+
}) : submission.error
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
/**
|
|
6
17
|
* Properties to be applied to the form element
|
|
7
18
|
*/
|
|
8
19
|
|
|
9
|
-
/**
|
|
10
|
-
* Normalize error to an array of string.
|
|
11
|
-
*/
|
|
12
|
-
function normalizeError(error) {
|
|
13
|
-
if (!error) {
|
|
14
|
-
// This treat both empty string and undefined as no error.
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
return [].concat(error);
|
|
18
|
-
}
|
|
19
20
|
function useNoValidate(defaultNoValidate, validateBeforeHydrate) {
|
|
20
21
|
var [noValidate, setNoValidate] = useState(defaultNoValidate || !validateBeforeHydrate);
|
|
21
22
|
useEffect(() => {
|
|
@@ -74,42 +75,33 @@ function useFormError(ref, config) {
|
|
|
74
75
|
}
|
|
75
76
|
var result = {};
|
|
76
77
|
for (var [name, message] of Object.entries(config.initialError)) {
|
|
77
|
-
var
|
|
78
|
-
if (
|
|
79
|
-
result[
|
|
78
|
+
var [path, ...restPaths] = getPaths(name);
|
|
79
|
+
if (typeof path !== 'undefined' && restPaths.length === 0) {
|
|
80
|
+
result[path] = message;
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
return result;
|
|
83
84
|
});
|
|
84
85
|
useEffect(() => {
|
|
85
86
|
var handleInvalid = event => {
|
|
87
|
+
var _config$name;
|
|
86
88
|
var form = getFormElement(ref.current);
|
|
87
89
|
var element = event.target;
|
|
88
|
-
|
|
90
|
+
var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : '';
|
|
91
|
+
if (!isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) {
|
|
89
92
|
return;
|
|
90
93
|
}
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
for (var i = 0; i <= scopePaths.length; i++) {
|
|
96
|
-
var path = fieldPaths[i];
|
|
97
|
-
if (i < scopePaths.length) {
|
|
98
|
-
// Skip if the field is not in the scope
|
|
99
|
-
if (path !== scopePaths[i]) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
key = path;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
94
|
+
var name = element.name.slice(prefix.length);
|
|
95
|
+
var [path, ...restPaths] = getPaths(name);
|
|
96
|
+
if (typeof path === 'undefined' || restPaths.length > 0) {
|
|
97
|
+
return;
|
|
106
98
|
}
|
|
107
99
|
setError(prev => {
|
|
108
|
-
if (element.validationMessage === getValidationMessage(prev[
|
|
100
|
+
if (element.validationMessage === getValidationMessage(prev[path])) {
|
|
109
101
|
return prev;
|
|
110
102
|
}
|
|
111
103
|
return _objectSpread2(_objectSpread2({}, prev), {}, {
|
|
112
|
-
[
|
|
104
|
+
[path]: getErrors(element.validationMessage)
|
|
113
105
|
});
|
|
114
106
|
});
|
|
115
107
|
event.preventDefault();
|
|
@@ -137,31 +129,35 @@ function useFormError(ref, config) {
|
|
|
137
129
|
* @see https://conform.guide/api/react#useform
|
|
138
130
|
*/
|
|
139
131
|
function useForm() {
|
|
140
|
-
var _config$
|
|
132
|
+
var _config$lastSubmissio3, _config$lastSubmissio4;
|
|
141
133
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
142
134
|
var configRef = useConfigRef(config);
|
|
143
135
|
var ref = useFormRef(config.ref);
|
|
144
136
|
var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
|
|
145
137
|
var report = useFormReporter(ref, config.lastSubmission);
|
|
146
138
|
var [errors, setErrors] = useState(() => {
|
|
147
|
-
var _config$lastSubmissio;
|
|
148
|
-
return
|
|
139
|
+
var _config$lastSubmissio, _config$lastSubmissio2;
|
|
140
|
+
return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : [];
|
|
149
141
|
});
|
|
150
142
|
var initialError = useMemo(() => {
|
|
143
|
+
var _submission$error$sco;
|
|
151
144
|
var submission = config.lastSubmission;
|
|
152
145
|
if (!submission) {
|
|
153
146
|
return {};
|
|
154
147
|
}
|
|
155
148
|
var intent = parseIntent(submission.intent);
|
|
156
149
|
var scope = getScope(intent);
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
if (typeof scope !== 'string') {
|
|
151
|
+
return submission.error;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
[scope]: (_submission$error$sco = submission.error[scope]) !== null && _submission$error$sco !== void 0 ? _submission$error$sco : []
|
|
159
155
|
};
|
|
160
156
|
}, [config.lastSubmission]);
|
|
161
157
|
// This payload from lastSubmission is only useful before hydration
|
|
162
158
|
// After hydration, any new payload on lastSubmission will be ignored
|
|
163
159
|
var [defaultValueFromLastSubmission, setDefaultValueFromLastSubmission] = useState( // @ts-expect-error defaultValue is not in Submission type
|
|
164
|
-
(_config$
|
|
160
|
+
(_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null);
|
|
165
161
|
var fieldset = useFieldset(ref, {
|
|
166
162
|
defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue,
|
|
167
163
|
initialError,
|
|
@@ -248,7 +244,7 @@ function useForm() {
|
|
|
248
244
|
shouldServerValidate
|
|
249
245
|
} = Object.entries(submission.error).reduce((result, _ref) => {
|
|
250
246
|
var [, error] = _ref;
|
|
251
|
-
for (var message of
|
|
247
|
+
for (var message of error) {
|
|
252
248
|
if (message === VALIDATION_UNDEFINED) {
|
|
253
249
|
result.shouldServerValidate = true;
|
|
254
250
|
} else if (message !== VALIDATION_SKIPPED) {
|
|
@@ -389,7 +385,7 @@ function useFieldList(ref, config) {
|
|
|
389
385
|
return updateList(list, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
|
|
390
386
|
defaultValue: [
|
|
391
387
|
// Generate a random key to avoid conflicts
|
|
392
|
-
|
|
388
|
+
getUniqueKey(), intent.payload.defaultValue]
|
|
393
389
|
}));
|
|
394
390
|
default:
|
|
395
391
|
return updateList(list, intent.payload);
|
|
@@ -471,7 +467,6 @@ function useFieldList(ref, config) {
|
|
|
471
467
|
* This basically makes it a no-op on server
|
|
472
468
|
*/
|
|
473
469
|
var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
474
|
-
|
|
475
470
|
/**
|
|
476
471
|
* Returns a ref object and a set of helpers that dispatch corresponding dom event.
|
|
477
472
|
*
|
|
@@ -637,12 +632,11 @@ function validateConstraint(options) {
|
|
|
637
632
|
return defaultErrors;
|
|
638
633
|
};
|
|
639
634
|
return parse(formData, {
|
|
640
|
-
resolve(
|
|
635
|
+
resolve() {
|
|
641
636
|
var error = {};
|
|
642
637
|
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
|
|
643
638
|
var _loop = function _loop(_element3) {
|
|
644
639
|
if (isFieldElement(_element3)) {
|
|
645
|
-
var _options$acceptMultip, _options$acceptMultip2;
|
|
646
640
|
var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : '';
|
|
647
641
|
var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => {
|
|
648
642
|
var [name, attributeValue = ''] = _ref6;
|
|
@@ -668,13 +662,8 @@ function validateConstraint(options) {
|
|
|
668
662
|
constraint,
|
|
669
663
|
defaultErrors: getDefaultErrors(_element3.validity, constraint)
|
|
670
664
|
});
|
|
671
|
-
var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 || (_options$acceptMultip2 = options.acceptMultipleErrors) === null || _options$acceptMultip2 === void 0 ? void 0 : _options$acceptMultip2.call(options, {
|
|
672
|
-
name,
|
|
673
|
-
payload,
|
|
674
|
-
intent
|
|
675
|
-
})) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
|
|
676
665
|
if (errors.length > 0) {
|
|
677
|
-
error[name] =
|
|
666
|
+
error[name] = errors;
|
|
678
667
|
}
|
|
679
668
|
}
|
|
680
669
|
};
|
|
@@ -687,10 +676,17 @@ function validateConstraint(options) {
|
|
|
687
676
|
}
|
|
688
677
|
});
|
|
689
678
|
}
|
|
679
|
+
function getUniqueKey() {
|
|
680
|
+
var [value] = crypto.getRandomValues(new Uint32Array(1));
|
|
681
|
+
if (!value) {
|
|
682
|
+
throw new Error('Fail to generate an unique key');
|
|
683
|
+
}
|
|
684
|
+
return value.toString(36);
|
|
685
|
+
}
|
|
690
686
|
function reportSubmission(form, submission) {
|
|
691
687
|
for (var [name, message] of Object.entries(submission.error)) {
|
|
692
688
|
// There is no need to create a placeholder button if all we want is to reset the error
|
|
693
|
-
if (message ===
|
|
689
|
+
if (message.length === 0) {
|
|
694
690
|
continue;
|
|
695
691
|
}
|
|
696
692
|
|
|
@@ -719,8 +715,9 @@ function reportSubmission(form, submission) {
|
|
|
719
715
|
var intent = parseIntent(submission.intent);
|
|
720
716
|
var scope = getScope(intent);
|
|
721
717
|
for (var _element4 of getFormControls(form)) {
|
|
718
|
+
var _submission$error$_el;
|
|
722
719
|
var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : '';
|
|
723
|
-
var messages =
|
|
720
|
+
var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : [];
|
|
724
721
|
if (scope === null || scope === _elementName) {
|
|
725
722
|
_element4.dataset.conformTouched = 'true';
|
|
726
723
|
}
|
|
@@ -746,4 +743,4 @@ function getScope(intent) {
|
|
|
746
743
|
return null;
|
|
747
744
|
}
|
|
748
745
|
|
|
749
|
-
export { FORM_ERROR_ELEMENT_NAME, getScope, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
|
|
746
|
+
export { FORM_ERROR_ELEMENT_NAME, getScope, getUniqueKey, report, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { type FieldsetConstraint, type Submission, parse, list, validate, requestIntent, isFieldElement, } from '@conform-to/dom';
|
|
2
|
-
export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, } from './hooks.js';
|
|
2
|
+
export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, report, } from './hooks.js';
|
|
3
3
|
export * as conform from './helpers.js';
|
package/index.js
CHANGED
|
@@ -28,6 +28,7 @@ Object.defineProperty(exports, 'validate', {
|
|
|
28
28
|
enumerable: true,
|
|
29
29
|
get: function () { return dom.validate; }
|
|
30
30
|
});
|
|
31
|
+
exports.report = hooks.report;
|
|
31
32
|
exports.useFieldList = hooks.useFieldList;
|
|
32
33
|
exports.useFieldset = hooks.useFieldset;
|
|
33
34
|
exports.useForm = hooks.useForm;
|
package/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { isFieldElement, list, parse, requestIntent, validate } from '@conform-to/dom';
|
|
2
|
-
export { useFieldList, useFieldset, useForm, useInputEvent, validateConstraint } from './hooks.mjs';
|
|
2
|
+
export { report, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint } from './hooks.mjs';
|
|
3
3
|
import * as helpers from './helpers.mjs';
|
|
4
4
|
export { helpers as conform };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Conform view adapter for react",
|
|
4
4
|
"homepage": "https://conform.guide",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.8.0-pre.1",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"module": "index.mjs",
|
|
9
9
|
"types": "index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"url": "https://github.com/edmundhung/conform/issues"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@conform-to/dom": "0.
|
|
33
|
+
"@conform-to/dom": "0.8.0-pre.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=16.8"
|