@conform-to/react 0.6.0 → 0.6.2
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 +10 -2
- package/base.d.ts +0 -0
- package/helpers.d.ts +11 -12
- package/helpers.js +7 -9
- package/hooks.d.ts +80 -14
- package/hooks.js +372 -271
- package/index.d.ts +2 -2
- package/index.js +1 -4
- package/module/helpers.js +6 -2
- package/module/hooks.js +368 -273
- package/module/index.js +2 -2
- package/package.json +7 -3
package/hooks.js
CHANGED
|
@@ -6,6 +6,122 @@ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js
|
|
|
6
6
|
var dom = require('@conform-to/dom');
|
|
7
7
|
var react = require('react');
|
|
8
8
|
|
|
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
|
+
function useNoValidate(defaultNoValidate, validateBeforeHydrate) {
|
|
20
|
+
var [noValidate, setNoValidate] = react.useState(defaultNoValidate || !validateBeforeHydrate);
|
|
21
|
+
react.useEffect(() => {
|
|
22
|
+
setNoValidate(true);
|
|
23
|
+
}, []);
|
|
24
|
+
return noValidate;
|
|
25
|
+
}
|
|
26
|
+
function useFormRef(userProvidedRef) {
|
|
27
|
+
var formRef = react.useRef(null);
|
|
28
|
+
return userProvidedRef !== null && userProvidedRef !== void 0 ? userProvidedRef : formRef;
|
|
29
|
+
}
|
|
30
|
+
function useConfigRef(config) {
|
|
31
|
+
var ref = react.useRef(config);
|
|
32
|
+
useSafeLayoutEffect(() => {
|
|
33
|
+
ref.current = config;
|
|
34
|
+
});
|
|
35
|
+
return ref;
|
|
36
|
+
}
|
|
37
|
+
function useFormReporter(ref, lastSubmission) {
|
|
38
|
+
var [submission, setSubmission] = react.useState(lastSubmission);
|
|
39
|
+
var report = react.useCallback((form, submission) => {
|
|
40
|
+
var event = new CustomEvent('conform', {
|
|
41
|
+
detail: submission.intent
|
|
42
|
+
});
|
|
43
|
+
form.dispatchEvent(event);
|
|
44
|
+
setSubmission(submission);
|
|
45
|
+
}, []);
|
|
46
|
+
react.useEffect(() => {
|
|
47
|
+
var form = ref.current;
|
|
48
|
+
if (!form || !lastSubmission) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
report(form, lastSubmission);
|
|
52
|
+
}, [ref, lastSubmission, report]);
|
|
53
|
+
react.useEffect(() => {
|
|
54
|
+
var form = ref.current;
|
|
55
|
+
if (!form || !submission) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
reportSubmission(form, submission);
|
|
59
|
+
}, [ref, submission]);
|
|
60
|
+
return report;
|
|
61
|
+
}
|
|
62
|
+
function useFormError(ref, config) {
|
|
63
|
+
var [error, setError] = react.useState(() => {
|
|
64
|
+
if (!config.initialError) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
var result = {};
|
|
68
|
+
for (var [name, message] of Object.entries(config.initialError)) {
|
|
69
|
+
var paths = dom.getPaths(name);
|
|
70
|
+
if (paths.length === 1) {
|
|
71
|
+
result[paths[0]] = normalizeError(message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
});
|
|
76
|
+
react.useEffect(() => {
|
|
77
|
+
var handleInvalid = event => {
|
|
78
|
+
var form = dom.getFormElement(ref.current);
|
|
79
|
+
var element = event.target;
|
|
80
|
+
if (!dom.isFieldElement(element) || element.form !== form || !element.dataset.conformTouched) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
var key = element.name;
|
|
84
|
+
if (config.name) {
|
|
85
|
+
var scopePaths = dom.getPaths(config.name);
|
|
86
|
+
var fieldPaths = dom.getPaths(element.name);
|
|
87
|
+
for (var i = 0; i <= scopePaths.length; i++) {
|
|
88
|
+
var path = fieldPaths[i];
|
|
89
|
+
if (i < scopePaths.length) {
|
|
90
|
+
// Skip if the field is not in the scope
|
|
91
|
+
if (path !== scopePaths[i]) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
key = path;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
setError(prev => {
|
|
100
|
+
if (element.validationMessage === dom.getValidationMessage(prev[key])) {
|
|
101
|
+
return prev;
|
|
102
|
+
}
|
|
103
|
+
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
|
|
104
|
+
[key]: dom.getErrors(element.validationMessage)
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
};
|
|
109
|
+
var handleReset = event => {
|
|
110
|
+
var form = dom.getFormElement(ref.current);
|
|
111
|
+
if (form && event.target === form) {
|
|
112
|
+
setError({});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
document.addEventListener('reset', handleReset);
|
|
116
|
+
document.addEventListener('invalid', handleInvalid, true);
|
|
117
|
+
return () => {
|
|
118
|
+
document.removeEventListener('reset', handleReset);
|
|
119
|
+
document.removeEventListener('invalid', handleInvalid, true);
|
|
120
|
+
};
|
|
121
|
+
}, [ref, config.name]);
|
|
122
|
+
return [error, setError];
|
|
123
|
+
}
|
|
124
|
+
|
|
9
125
|
/**
|
|
10
126
|
* Returns properties required to hook into form events.
|
|
11
127
|
* Applied custom validation and define when error should be reported.
|
|
@@ -13,97 +129,53 @@ var react = require('react');
|
|
|
13
129
|
* @see https://conform.guide/api/react#useform
|
|
14
130
|
*/
|
|
15
131
|
function useForm() {
|
|
16
|
-
var _config$
|
|
132
|
+
var _ref, _config$lastSubmissio2;
|
|
17
133
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
18
|
-
var configRef =
|
|
19
|
-
var ref =
|
|
20
|
-
var
|
|
134
|
+
var configRef = useConfigRef(config);
|
|
135
|
+
var ref = useFormRef(config.ref);
|
|
136
|
+
var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
|
|
137
|
+
var report = useFormReporter(ref, config.lastSubmission);
|
|
21
138
|
var [errors, setErrors] = react.useState(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
return [].concat(config.lastSubmission.error['']);
|
|
139
|
+
var _config$lastSubmissio;
|
|
140
|
+
return normalizeError((_config$lastSubmissio = config.lastSubmission) === null || _config$lastSubmissio === void 0 ? void 0 : _config$lastSubmissio.error['']);
|
|
26
141
|
});
|
|
27
|
-
var
|
|
142
|
+
var initialError = react.useMemo(() => {
|
|
28
143
|
var submission = config.lastSubmission;
|
|
29
144
|
if (!submission) {
|
|
30
|
-
return {
|
|
31
|
-
defaultValue: config.defaultValue
|
|
32
|
-
};
|
|
145
|
+
return {};
|
|
33
146
|
}
|
|
34
147
|
var scope = dom.getScope(submission.intent);
|
|
35
|
-
return {
|
|
36
|
-
|
|
37
|
-
initialError: Object.entries(submission.error).reduce((result, _ref) => {
|
|
38
|
-
var [name, message] = _ref;
|
|
39
|
-
if (name !== '' && (scope === null || scope === name)) {
|
|
40
|
-
result[name] = message;
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
43
|
-
}, {})
|
|
148
|
+
return scope === null ? submission.error : {
|
|
149
|
+
[scope]: submission.error[scope]
|
|
44
150
|
};
|
|
45
|
-
});
|
|
46
|
-
var
|
|
151
|
+
}, [config.lastSubmission]);
|
|
152
|
+
var fieldset = useFieldset(ref, {
|
|
153
|
+
defaultValue: (_ref = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref !== void 0 ? _ref : config.defaultValue,
|
|
154
|
+
initialError,
|
|
47
155
|
constraint: config.constraint,
|
|
48
156
|
form: config.id
|
|
49
157
|
});
|
|
50
|
-
var fieldset = useFieldset(ref, fieldsetConfig);
|
|
51
|
-
var [noValidate, setNoValidate] = react.useState(config.noValidate || !config.fallbackNative);
|
|
52
158
|
react.useEffect(() => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
react.useEffect(() => {
|
|
56
|
-
setNoValidate(true);
|
|
57
|
-
}, []);
|
|
58
|
-
react.useEffect(() => {
|
|
59
|
-
var form = ref.current;
|
|
60
|
-
var submission = config.lastSubmission;
|
|
61
|
-
if (!form || !submission) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
var listCommand = dom.parseListCommand(submission.intent);
|
|
65
|
-
if (listCommand) {
|
|
66
|
-
form.dispatchEvent(new CustomEvent('conform/list', {
|
|
67
|
-
detail: submission.intent
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
setLastSubmission(submission);
|
|
71
|
-
}, [config.lastSubmission]);
|
|
72
|
-
react.useEffect(() => {
|
|
73
|
-
var form = ref.current;
|
|
74
|
-
if (!form || !lastSubmission) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
dom.reportSubmission(ref.current, lastSubmission);
|
|
78
|
-
}, [lastSubmission]);
|
|
79
|
-
react.useEffect(() => {
|
|
80
|
-
// Revalidate the form when input value is changed
|
|
81
|
-
var handleInput = event => {
|
|
159
|
+
// custom validate handler
|
|
160
|
+
var createValidateHandler = name => event => {
|
|
82
161
|
var field = event.target;
|
|
83
162
|
var form = ref.current;
|
|
84
|
-
var
|
|
85
|
-
|
|
163
|
+
var {
|
|
164
|
+
initialReport = 'onSubmit',
|
|
165
|
+
shouldValidate = initialReport === 'onChange' ? 'onInput' : initialReport,
|
|
166
|
+
shouldRevalidate = 'onInput'
|
|
167
|
+
} = configRef.current;
|
|
168
|
+
if (!form || !dom.isFocusableFormControl(field) || field.form !== form) {
|
|
86
169
|
return;
|
|
87
170
|
}
|
|
88
|
-
if (field.dataset.conformTouched
|
|
89
|
-
dom.requestIntent(form, dom.validate(field.name));
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
var handleBlur = event => {
|
|
93
|
-
var field = event.target;
|
|
94
|
-
var form = ref.current;
|
|
95
|
-
var formConfig = configRef.current;
|
|
96
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (formConfig.initialReport === 'onBlur' && !field.dataset.conformTouched) {
|
|
171
|
+
if (field.dataset.conformTouched ? shouldRevalidate === name : shouldValidate === name) {
|
|
100
172
|
dom.requestIntent(form, dom.validate(field.name));
|
|
101
173
|
}
|
|
102
174
|
};
|
|
103
175
|
var handleInvalid = event => {
|
|
104
|
-
var form =
|
|
176
|
+
var form = ref.current;
|
|
105
177
|
var field = event.target;
|
|
106
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !==
|
|
178
|
+
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== FORM_ERROR_ELEMENT_NAME) {
|
|
107
179
|
return;
|
|
108
180
|
}
|
|
109
181
|
event.preventDefault();
|
|
@@ -113,30 +185,19 @@ function useForm() {
|
|
|
113
185
|
};
|
|
114
186
|
var handleReset = event => {
|
|
115
187
|
var form = ref.current;
|
|
116
|
-
var formConfig = configRef.current;
|
|
117
188
|
if (!form || event.target !== form) {
|
|
118
189
|
return;
|
|
119
190
|
}
|
|
120
191
|
|
|
121
192
|
// Reset all field state
|
|
122
|
-
for (var
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
field.setCustomValidity('');
|
|
126
|
-
}
|
|
193
|
+
for (var element of dom.getFormControls(form)) {
|
|
194
|
+
delete element.dataset.conformTouched;
|
|
195
|
+
element.setCustomValidity('');
|
|
127
196
|
}
|
|
128
197
|
setErrors([]);
|
|
129
|
-
setUncontrolledState({
|
|
130
|
-
defaultValue: formConfig.defaultValue
|
|
131
|
-
});
|
|
132
198
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* The input event handler will be triggered in capturing phase in order to
|
|
136
|
-
* allow follow-up action in the bubble phase based on the latest validity
|
|
137
|
-
* E.g. `useFieldset` reset the error of valid field after checking the
|
|
138
|
-
* validity in the bubble phase.
|
|
139
|
-
*/
|
|
199
|
+
var handleInput = createValidateHandler('onInput');
|
|
200
|
+
var handleBlur = createValidateHandler('onBlur');
|
|
140
201
|
document.addEventListener('input', handleInput, true);
|
|
141
202
|
document.addEventListener('blur', handleBlur, true);
|
|
142
203
|
document.addEventListener('invalid', handleInvalid, true);
|
|
@@ -147,7 +208,7 @@ function useForm() {
|
|
|
147
208
|
document.removeEventListener('invalid', handleInvalid, true);
|
|
148
209
|
document.removeEventListener('reset', handleReset);
|
|
149
210
|
};
|
|
150
|
-
}, []);
|
|
211
|
+
}, [ref, configRef]);
|
|
151
212
|
var form = {
|
|
152
213
|
ref,
|
|
153
214
|
error: errors[0],
|
|
@@ -163,34 +224,32 @@ function useForm() {
|
|
|
163
224
|
return;
|
|
164
225
|
}
|
|
165
226
|
try {
|
|
166
|
-
var _config$onValidate;
|
|
227
|
+
var _config$onValidate, _config$onValidate2;
|
|
167
228
|
var formData = dom.getFormData(form, submitter);
|
|
168
|
-
var
|
|
169
|
-
var submission = getSubmission({
|
|
229
|
+
var submission = (_config$onValidate = (_config$onValidate2 = config.onValidate) === null || _config$onValidate2 === void 0 ? void 0 : _config$onValidate2.call(config, {
|
|
170
230
|
form,
|
|
171
231
|
formData
|
|
172
|
-
});
|
|
173
|
-
|
|
232
|
+
})) !== null && _config$onValidate !== void 0 ? _config$onValidate : dom.parse(formData);
|
|
233
|
+
var messages = Object.entries(submission.error).reduce((messages, _ref2) => {
|
|
174
234
|
var [, message] = _ref2;
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
detail: submission.intent
|
|
184
|
-
}));
|
|
185
|
-
}
|
|
186
|
-
setLastSubmission(submission);
|
|
235
|
+
return messages.concat(normalizeError(message));
|
|
236
|
+
}, []);
|
|
237
|
+
var shouldValidate = !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate);
|
|
238
|
+
var shouldFallbackToServer = messages.includes(VALIDATION_UNDEFINED);
|
|
239
|
+
var hasClientValidation = typeof config.onValidate !== 'undefined';
|
|
240
|
+
var isValid = messages.length === 0;
|
|
241
|
+
if (hasClientValidation && (dom.isSubmitting(submission.intent) ? shouldValidate && !isValid : !shouldFallbackToServer)) {
|
|
242
|
+
report(form, submission);
|
|
187
243
|
event.preventDefault();
|
|
188
244
|
} else {
|
|
189
245
|
var _config$onSubmit;
|
|
190
|
-
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event,
|
|
246
|
+
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
|
|
191
247
|
formData,
|
|
192
|
-
submission
|
|
193
|
-
|
|
248
|
+
submission,
|
|
249
|
+
action: dom.getFormAction(nativeEvent),
|
|
250
|
+
encType: dom.getFormEncType(nativeEvent),
|
|
251
|
+
method: dom.getFormMethod(nativeEvent)
|
|
252
|
+
});
|
|
194
253
|
}
|
|
195
254
|
} catch (e) {
|
|
196
255
|
console.warn(e);
|
|
@@ -202,10 +261,10 @@ function useForm() {
|
|
|
202
261
|
form.id = config.id;
|
|
203
262
|
form.errorId = "".concat(config.id, "-error");
|
|
204
263
|
form.props.id = form.id;
|
|
205
|
-
form.props['aria-describedby'] = form.errorId;
|
|
206
264
|
}
|
|
207
265
|
if (form.errorId && form.errors.length > 0) {
|
|
208
266
|
form.props['aria-invalid'] = 'true';
|
|
267
|
+
form.props['aria-describedby'] = form.errorId;
|
|
209
268
|
}
|
|
210
269
|
return [form, fieldset];
|
|
211
270
|
}
|
|
@@ -215,88 +274,10 @@ function useForm() {
|
|
|
215
274
|
*/
|
|
216
275
|
|
|
217
276
|
function useFieldset(ref, config) {
|
|
218
|
-
var
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
() => {
|
|
222
|
-
var _config$defaultValue;
|
|
223
|
-
var initialError = {};
|
|
224
|
-
for (var [name, message] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) {
|
|
225
|
-
var _config$initialError;
|
|
226
|
-
var [key, ...paths] = dom.getPaths(name);
|
|
227
|
-
if (typeof key === 'string') {
|
|
228
|
-
var scopedName = dom.getName(paths);
|
|
229
|
-
initialError[key] = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, initialError[key]), {}, {
|
|
230
|
-
[scopedName]: message
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return {
|
|
235
|
-
defaultValue: (_config$defaultValue = config === null || config === void 0 ? void 0 : config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : {},
|
|
236
|
-
initialError
|
|
237
|
-
};
|
|
238
|
-
});
|
|
239
|
-
var [error, setError] = react.useState(() => {
|
|
240
|
-
var result = {};
|
|
241
|
-
for (var [key, _error] of Object.entries(uncontrolledState.initialError)) {
|
|
242
|
-
var _error$;
|
|
243
|
-
result[key] = [].concat((_error$ = _error === null || _error === void 0 ? void 0 : _error['']) !== null && _error$ !== void 0 ? _error$ : []);
|
|
244
|
-
}
|
|
245
|
-
return result;
|
|
246
|
-
});
|
|
247
|
-
react.useEffect(() => {
|
|
248
|
-
configRef.current = config;
|
|
277
|
+
var [error] = useFormError(ref, {
|
|
278
|
+
initialError: config.initialError,
|
|
279
|
+
name: config.name
|
|
249
280
|
});
|
|
250
|
-
react.useEffect(() => {
|
|
251
|
-
var invalidHandler = event => {
|
|
252
|
-
var _configRef$current$na;
|
|
253
|
-
var form = dom.getFormElement(ref.current);
|
|
254
|
-
var field = event.target;
|
|
255
|
-
var fieldsetName = (_configRef$current$na = configRef.current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
|
|
256
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
var [key, ...paths] = dom.getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name);
|
|
260
|
-
|
|
261
|
-
// Update the error only if the field belongs to the fieldset
|
|
262
|
-
if (typeof key === 'string' && paths.length === 0) {
|
|
263
|
-
if (field.dataset.conformTouched) {
|
|
264
|
-
setError(prev => {
|
|
265
|
-
var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[key]);
|
|
266
|
-
if (prevMessage === field.validationMessage) {
|
|
267
|
-
return prev;
|
|
268
|
-
}
|
|
269
|
-
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
|
|
270
|
-
[key]: dom.getErrors(field.validationMessage)
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
event.preventDefault();
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
var resetHandler = event => {
|
|
278
|
-
var _fieldsetConfig$defau;
|
|
279
|
-
var form = dom.getFormElement(ref.current);
|
|
280
|
-
if (!form || event.target !== form) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
var fieldsetConfig = configRef.current;
|
|
284
|
-
setUncontrolledState({
|
|
285
|
-
// @ts-expect-error
|
|
286
|
-
defaultValue: (_fieldsetConfig$defau = fieldsetConfig === null || fieldsetConfig === void 0 ? void 0 : fieldsetConfig.defaultValue) !== null && _fieldsetConfig$defau !== void 0 ? _fieldsetConfig$defau : {},
|
|
287
|
-
initialError: {}
|
|
288
|
-
});
|
|
289
|
-
setError({});
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
// The invalid event does not bubble and so listening on the capturing pharse is needed
|
|
293
|
-
document.addEventListener('invalid', invalidHandler, true);
|
|
294
|
-
document.addEventListener('reset', resetHandler);
|
|
295
|
-
return () => {
|
|
296
|
-
document.removeEventListener('invalid', invalidHandler, true);
|
|
297
|
-
document.removeEventListener('reset', resetHandler);
|
|
298
|
-
};
|
|
299
|
-
}, [ref]);
|
|
300
281
|
|
|
301
282
|
/**
|
|
302
283
|
* This allows us constructing the field at runtime as we have no information
|
|
@@ -305,17 +286,25 @@ function useFieldset(ref, config) {
|
|
|
305
286
|
*/
|
|
306
287
|
return new Proxy({}, {
|
|
307
288
|
get(_target, key) {
|
|
308
|
-
var _fieldsetConfig$const;
|
|
289
|
+
var _fieldsetConfig$const, _fieldsetConfig$initi, _fieldsetConfig$defau;
|
|
309
290
|
if (typeof key !== 'string') {
|
|
310
291
|
return;
|
|
311
292
|
}
|
|
312
|
-
var fieldsetConfig = config
|
|
293
|
+
var fieldsetConfig = config;
|
|
313
294
|
var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key];
|
|
314
295
|
var errors = error === null || error === void 0 ? void 0 : error[key];
|
|
296
|
+
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref3) => {
|
|
297
|
+
var [name, message] = _ref3;
|
|
298
|
+
var [field, ...paths] = dom.getPaths(name);
|
|
299
|
+
if (field === key) {
|
|
300
|
+
result[dom.getName(paths)] = message;
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}, {});
|
|
315
304
|
var field = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, constraint), {}, {
|
|
316
305
|
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
|
|
317
|
-
defaultValue:
|
|
318
|
-
initialError
|
|
306
|
+
defaultValue: (_fieldsetConfig$defau = fieldsetConfig.defaultValue) === null || _fieldsetConfig$defau === void 0 ? void 0 : _fieldsetConfig$defau[key],
|
|
307
|
+
initialError,
|
|
319
308
|
error: errors === null || errors === void 0 ? void 0 : errors[0],
|
|
320
309
|
errors
|
|
321
310
|
});
|
|
@@ -323,6 +312,7 @@ function useFieldset(ref, config) {
|
|
|
323
312
|
field.form = fieldsetConfig.form;
|
|
324
313
|
field.id = "".concat(fieldsetConfig.form, "-").concat(field.name);
|
|
325
314
|
field.errorId = "".concat(field.id, "-error");
|
|
315
|
+
field.descriptionId = "".concat(field.id, "-description");
|
|
326
316
|
}
|
|
327
317
|
return field;
|
|
328
318
|
}
|
|
@@ -330,68 +320,22 @@ function useFieldset(ref, config) {
|
|
|
330
320
|
}
|
|
331
321
|
|
|
332
322
|
/**
|
|
333
|
-
* Returns a list of key and config
|
|
334
|
-
* configuring buttons for list manipulation
|
|
323
|
+
* Returns a list of key and field config.
|
|
335
324
|
*
|
|
336
325
|
* @see https://conform.guide/api/react#usefieldlist
|
|
337
326
|
*/
|
|
338
327
|
function useFieldList(ref, config) {
|
|
339
|
-
var configRef =
|
|
340
|
-
var [
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
for (var [name, message] of Object.entries((_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {})) {
|
|
344
|
-
var _config$initialError2;
|
|
345
|
-
var [index, ...paths] = dom.getPaths(name);
|
|
346
|
-
if (typeof index === 'number') {
|
|
347
|
-
var scopedName = dom.getName(paths);
|
|
348
|
-
initialError[index] = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, initialError[index]), {}, {
|
|
349
|
-
[scopedName]: message
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return {
|
|
354
|
-
defaultValue: (_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [],
|
|
355
|
-
initialError
|
|
356
|
-
};
|
|
328
|
+
var configRef = useConfigRef(config);
|
|
329
|
+
var [error, setError] = useFormError(ref, {
|
|
330
|
+
initialError: config.initialError,
|
|
331
|
+
name: config.name
|
|
357
332
|
});
|
|
358
|
-
var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => {
|
|
359
|
-
var _error$2;
|
|
360
|
-
return [].concat((_error$2 = error === null || error === void 0 ? void 0 : error['']) !== null && _error$2 !== void 0 ? _error$2 : []);
|
|
361
|
-
}));
|
|
362
333
|
var [entries, setEntries] = react.useState(() => {
|
|
363
|
-
var _config$
|
|
364
|
-
return Object.entries((_config$
|
|
365
|
-
});
|
|
366
|
-
react.useEffect(() => {
|
|
367
|
-
configRef.current = config;
|
|
334
|
+
var _config$defaultValue;
|
|
335
|
+
return Object.entries((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : [undefined]);
|
|
368
336
|
});
|
|
369
337
|
react.useEffect(() => {
|
|
370
|
-
var
|
|
371
|
-
var _configRef$current$na2;
|
|
372
|
-
var form = dom.getFormElement(ref.current);
|
|
373
|
-
var field = event.target;
|
|
374
|
-
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
375
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
|
|
379
|
-
|
|
380
|
-
// Update the error only if the field belongs to the fieldset
|
|
381
|
-
if (typeof index === 'number' && paths.length === 0) {
|
|
382
|
-
if (field.dataset.conformTouched) {
|
|
383
|
-
setError(prev => {
|
|
384
|
-
var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[index]);
|
|
385
|
-
if (prevMessage === field.validationMessage) {
|
|
386
|
-
return prev;
|
|
387
|
-
}
|
|
388
|
-
return [...prev.slice(0, index), dom.getErrors(field.validationMessage), ...prev.slice(index + 1)];
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
event.preventDefault();
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
var listHandler = event => {
|
|
338
|
+
var conformHandler = event => {
|
|
395
339
|
var form = dom.getFormElement(ref.current);
|
|
396
340
|
if (!form || event.target !== form) {
|
|
397
341
|
return;
|
|
@@ -420,55 +364,65 @@ function useFieldList(ref, config) {
|
|
|
420
364
|
}
|
|
421
365
|
});
|
|
422
366
|
setError(error => {
|
|
367
|
+
var errorList = [];
|
|
368
|
+
for (var [key, messages] of Object.entries(error)) {
|
|
369
|
+
if (typeof key === 'number') {
|
|
370
|
+
errorList[key] = messages;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
423
373
|
switch (command.type) {
|
|
424
374
|
case 'append':
|
|
425
375
|
case 'prepend':
|
|
426
376
|
case 'replace':
|
|
427
|
-
|
|
377
|
+
errorList = dom.updateList(errorList, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
|
|
428
378
|
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
|
|
429
379
|
defaultValue: undefined
|
|
430
380
|
})
|
|
431
381
|
}));
|
|
382
|
+
break;
|
|
432
383
|
default:
|
|
433
384
|
{
|
|
434
|
-
|
|
385
|
+
errorList = dom.updateList(errorList, command);
|
|
386
|
+
break;
|
|
435
387
|
}
|
|
436
388
|
}
|
|
389
|
+
return Object.assign({}, errorList);
|
|
437
390
|
});
|
|
438
391
|
};
|
|
439
392
|
var resetHandler = event => {
|
|
440
|
-
var
|
|
393
|
+
var _configRef$current$de;
|
|
441
394
|
var form = dom.getFormElement(ref.current);
|
|
442
395
|
if (!form || event.target !== form) {
|
|
443
396
|
return;
|
|
444
397
|
}
|
|
445
|
-
|
|
446
|
-
setUncontrolledState({
|
|
447
|
-
defaultValue: (_fieldConfig$defaultV = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV !== void 0 ? _fieldConfig$defaultV : [],
|
|
448
|
-
initialError: []
|
|
449
|
-
});
|
|
450
|
-
setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
|
|
451
|
-
setError([]);
|
|
398
|
+
setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : [undefined]));
|
|
452
399
|
};
|
|
453
400
|
|
|
454
|
-
// @ts-expect-error Custom event: conform
|
|
455
|
-
document.addEventListener('conform
|
|
456
|
-
document.addEventListener('invalid', invalidHandler, true);
|
|
401
|
+
// @ts-expect-error Custom event: conform
|
|
402
|
+
document.addEventListener('conform', conformHandler, true);
|
|
457
403
|
document.addEventListener('reset', resetHandler);
|
|
458
404
|
return () => {
|
|
459
|
-
// @ts-expect-error Custom event: conform
|
|
460
|
-
document.removeEventListener('conform
|
|
461
|
-
document.removeEventListener('invalid', invalidHandler, true);
|
|
405
|
+
// @ts-expect-error Custom event: conform
|
|
406
|
+
document.removeEventListener('conform', conformHandler, true);
|
|
462
407
|
document.removeEventListener('reset', resetHandler);
|
|
463
408
|
};
|
|
464
|
-
}, [ref]);
|
|
409
|
+
}, [ref, configRef, setError]);
|
|
465
410
|
return entries.map((_ref4, index) => {
|
|
411
|
+
var _config$initialError, _config$defaultValue2;
|
|
466
412
|
var [key, defaultValue] = _ref4;
|
|
467
413
|
var errors = error[index];
|
|
414
|
+
var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref5) => {
|
|
415
|
+
var [name, message] = _ref5;
|
|
416
|
+
var [field, ...paths] = dom.getPaths(name);
|
|
417
|
+
if (field === index) {
|
|
418
|
+
result[dom.getName(paths)] = message;
|
|
419
|
+
}
|
|
420
|
+
return result;
|
|
421
|
+
}, {});
|
|
468
422
|
var fieldConfig = {
|
|
469
423
|
name: "".concat(config.name, "[").concat(index, "]"),
|
|
470
|
-
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue :
|
|
471
|
-
initialError
|
|
424
|
+
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue2 = config.defaultValue) === null || _config$defaultValue2 === void 0 ? void 0 : _config$defaultValue2[index],
|
|
425
|
+
initialError,
|
|
472
426
|
error: errors === null || errors === void 0 ? void 0 : errors[0],
|
|
473
427
|
errors
|
|
474
428
|
};
|
|
@@ -476,6 +430,7 @@ function useFieldList(ref, config) {
|
|
|
476
430
|
fieldConfig.form = config.form;
|
|
477
431
|
fieldConfig.id = "".concat(config.form, "-").concat(config.name);
|
|
478
432
|
fieldConfig.errorId = "".concat(fieldConfig.id, "-error");
|
|
433
|
+
fieldConfig.descriptionId = "".concat(fieldConfig.id, "-description");
|
|
479
434
|
}
|
|
480
435
|
return _rollupPluginBabelHelpers.objectSpread2({
|
|
481
436
|
key
|
|
@@ -519,13 +474,10 @@ function setNativeValue(element, value) {
|
|
|
519
474
|
var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect;
|
|
520
475
|
function useInputEvent(options) {
|
|
521
476
|
var ref = react.useRef(null);
|
|
522
|
-
var optionsRef =
|
|
477
|
+
var optionsRef = useConfigRef(options);
|
|
523
478
|
var changeDispatched = react.useRef(false);
|
|
524
479
|
var focusDispatched = react.useRef(false);
|
|
525
480
|
var blurDispatched = react.useRef(false);
|
|
526
|
-
useSafeLayoutEffect(() => {
|
|
527
|
-
optionsRef.current = options;
|
|
528
|
-
});
|
|
529
481
|
useSafeLayoutEffect(() => {
|
|
530
482
|
var getInputElement = () => {
|
|
531
483
|
var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;
|
|
@@ -654,11 +606,160 @@ function useInputEvent(options) {
|
|
|
654
606
|
blurDispatched.current = false;
|
|
655
607
|
}
|
|
656
608
|
};
|
|
657
|
-
}, []);
|
|
609
|
+
}, [optionsRef]);
|
|
658
610
|
return [ref, control];
|
|
659
611
|
}
|
|
612
|
+
var VALIDATION_UNDEFINED = '__undefined__';
|
|
613
|
+
var VALIDATION_SKIPPED = '__skipped__';
|
|
614
|
+
var FORM_ERROR_ELEMENT_NAME = '__form__';
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Validate the form with the Constraint Validation API
|
|
618
|
+
* @see https://conform.guide/api/react#validateconstraint
|
|
619
|
+
*/
|
|
620
|
+
function validateConstraint(options) {
|
|
621
|
+
var _options$formData, _options$formatMessag;
|
|
622
|
+
var formData = (_options$formData = options === null || options === void 0 ? void 0 : options.formData) !== null && _options$formData !== void 0 ? _options$formData : new FormData(options.form);
|
|
623
|
+
var getDefaultErrors = (validity, result) => {
|
|
624
|
+
var errors = [];
|
|
625
|
+
if (validity.valueMissing) errors.push('required');
|
|
626
|
+
if (validity.typeMismatch || validity.badInput) errors.push('type');
|
|
627
|
+
if (validity.tooShort) errors.push('minLength');
|
|
628
|
+
if (validity.rangeUnderflow) errors.push('min');
|
|
629
|
+
if (validity.stepMismatch) errors.push('step');
|
|
630
|
+
if (validity.tooLong) errors.push('maxLength');
|
|
631
|
+
if (validity.rangeOverflow) errors.push('max');
|
|
632
|
+
if (validity.patternMismatch) errors.push('pattern');
|
|
633
|
+
for (var [constraintName, valid] of Object.entries(result)) {
|
|
634
|
+
if (!valid) {
|
|
635
|
+
errors.push(constraintName);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return errors;
|
|
639
|
+
};
|
|
640
|
+
var formatMessages = (_options$formatMessag = options === null || options === void 0 ? void 0 : options.formatMessages) !== null && _options$formatMessag !== void 0 ? _options$formatMessag : _ref6 => {
|
|
641
|
+
var {
|
|
642
|
+
defaultErrors
|
|
643
|
+
} = _ref6;
|
|
644
|
+
return defaultErrors;
|
|
645
|
+
};
|
|
646
|
+
return dom.parse(formData, {
|
|
647
|
+
resolve(payload, intent) {
|
|
648
|
+
var error = {};
|
|
649
|
+
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
|
|
650
|
+
var _loop = function _loop(element) {
|
|
651
|
+
if (dom.isFieldElement(element)) {
|
|
652
|
+
var _options$acceptMultip, _options$acceptMultip2;
|
|
653
|
+
var name = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
|
|
654
|
+
var constraint = Object.entries(element.dataset).reduce((result, _ref7) => {
|
|
655
|
+
var [name, attributeValue = ''] = _ref7;
|
|
656
|
+
if (constraintPattern.test(name)) {
|
|
657
|
+
var _options$constraint;
|
|
658
|
+
var constraintName = name.slice(10).toLowerCase();
|
|
659
|
+
var _validate = (_options$constraint = options.constraint) === null || _options$constraint === void 0 ? void 0 : _options$constraint[constraintName];
|
|
660
|
+
if (typeof _validate === 'function') {
|
|
661
|
+
result[constraintName] = _validate(element.value, {
|
|
662
|
+
formData,
|
|
663
|
+
attributeValue
|
|
664
|
+
});
|
|
665
|
+
} else {
|
|
666
|
+
console.warn("Found an \"".concat(constraintName, "\" constraint with undefined definition; Please specify it on the validateConstraint API."));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
}, {});
|
|
671
|
+
var errors = formatMessages({
|
|
672
|
+
name,
|
|
673
|
+
validity: element.validity,
|
|
674
|
+
constraint,
|
|
675
|
+
defaultErrors: getDefaultErrors(element.validity, constraint)
|
|
676
|
+
});
|
|
677
|
+
var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 ? void 0 : (_options$acceptMultip2 = options.acceptMultipleErrors) === null || _options$acceptMultip2 === void 0 ? void 0 : _options$acceptMultip2.call(options, {
|
|
678
|
+
name,
|
|
679
|
+
payload,
|
|
680
|
+
intent
|
|
681
|
+
})) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
|
|
682
|
+
if (errors.length > 0) {
|
|
683
|
+
error[name] = shouldAcceptMultipleErrors ? errors : errors[0];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
for (var element of options.form.elements) {
|
|
688
|
+
_loop(element);
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
error
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function reportSubmission(form, submission) {
|
|
697
|
+
for (var [name, message] of Object.entries(submission.error)) {
|
|
698
|
+
// There is no need to create a placeholder button if all we want is to reset the error
|
|
699
|
+
if (message === '') {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// We can't use empty string as button name
|
|
704
|
+
// As `form.element.namedItem('')` will always returns null
|
|
705
|
+
var elementName = name ? name : FORM_ERROR_ELEMENT_NAME;
|
|
706
|
+
var item = form.elements.namedItem(elementName);
|
|
707
|
+
if (item instanceof RadioNodeList) {
|
|
708
|
+
for (var field of item) {
|
|
709
|
+
if (field.type !== 'radio') {
|
|
710
|
+
console.warn('Repeated field name is not supported.');
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (item === null) {
|
|
716
|
+
// Create placeholder button to keep the error without contributing to the form data
|
|
717
|
+
var button = document.createElement('button');
|
|
718
|
+
button.name = elementName;
|
|
719
|
+
button.hidden = true;
|
|
720
|
+
button.dataset.conformTouched = 'true';
|
|
721
|
+
form.appendChild(button);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
var scope = dom.getScope(submission.intent);
|
|
725
|
+
for (var element of dom.getFormControls(form)) {
|
|
726
|
+
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
|
|
727
|
+
var messages = normalizeError(submission.error[_elementName]);
|
|
728
|
+
if (scope === null || scope === _elementName) {
|
|
729
|
+
element.dataset.conformTouched = 'true';
|
|
730
|
+
}
|
|
731
|
+
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
|
|
732
|
+
var invalidEvent = new Event('invalid', {
|
|
733
|
+
cancelable: true
|
|
734
|
+
});
|
|
735
|
+
element.setCustomValidity(dom.getValidationMessage(messages));
|
|
736
|
+
element.dispatchEvent(invalidEvent);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (dom.isSubmitting(submission.intent) || isFocusedOnIntentButton(form, submission.intent)) {
|
|
740
|
+
if (scope) {
|
|
741
|
+
dom.focusFormControl(form, scope);
|
|
742
|
+
} else {
|
|
743
|
+
dom.focusFirstInvalidControl(form);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Check if the current focus is on a intent button.
|
|
750
|
+
*/
|
|
751
|
+
function isFocusedOnIntentButton(form, intent) {
|
|
752
|
+
var element = document.activeElement;
|
|
753
|
+
return dom.isFieldElement(element) && element.type === 'submit' && element.form === form && element.name === dom.INTENT && element.value === intent;
|
|
754
|
+
}
|
|
660
755
|
|
|
756
|
+
exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
|
|
757
|
+
exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
|
|
758
|
+
exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
|
|
759
|
+
exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
|
|
760
|
+
exports.reportSubmission = reportSubmission;
|
|
661
761
|
exports.useFieldList = useFieldList;
|
|
662
762
|
exports.useFieldset = useFieldset;
|
|
663
763
|
exports.useForm = useForm;
|
|
664
764
|
exports.useInputEvent = useInputEvent;
|
|
765
|
+
exports.validateConstraint = validateConstraint;
|