@conform-to/react 0.6.1 → 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/base.d.ts +0 -0
- package/helpers.d.ts +2 -1
- package/helpers.js +3 -8
- package/hooks.d.ts +63 -12
- package/hooks.js +341 -218
- package/index.d.ts +2 -2
- package/index.js +1 -4
- package/module/helpers.js +2 -1
- package/module/hooks.js +337 -220
- package/module/index.js +2 -2
- package/package.json +2 -2
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,16 +129,15 @@ var react = require('react');
|
|
|
13
129
|
* @see https://conform.guide/api/react#useform
|
|
14
130
|
*/
|
|
15
131
|
function useForm() {
|
|
16
|
-
var
|
|
132
|
+
var _ref, _config$lastSubmissio2;
|
|
17
133
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
18
|
-
var configRef =
|
|
19
|
-
var
|
|
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
142
|
var initialError = react.useMemo(() => {
|
|
28
143
|
var submission = config.lastSubmission;
|
|
@@ -30,87 +145,37 @@ function useForm() {
|
|
|
30
145
|
return {};
|
|
31
146
|
}
|
|
32
147
|
var scope = dom.getScope(submission.intent);
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
result[name] = message;
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}, {});
|
|
148
|
+
return scope === null ? submission.error : {
|
|
149
|
+
[scope]: submission.error[scope]
|
|
150
|
+
};
|
|
40
151
|
}, [config.lastSubmission]);
|
|
41
|
-
var ref = (_config$ref = config.ref) !== null && _config$ref !== void 0 ? _config$ref : formRef;
|
|
42
152
|
var fieldset = useFieldset(ref, {
|
|
43
|
-
defaultValue: (
|
|
153
|
+
defaultValue: (_ref = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref !== void 0 ? _ref : config.defaultValue,
|
|
44
154
|
initialError,
|
|
45
155
|
constraint: config.constraint,
|
|
46
156
|
form: config.id
|
|
47
157
|
});
|
|
48
|
-
var [noValidate, setNoValidate] = react.useState(config.noValidate || !config.fallbackNative);
|
|
49
|
-
useSafeLayoutEffect(() => {
|
|
50
|
-
configRef.current = config;
|
|
51
|
-
});
|
|
52
|
-
react.useEffect(() => {
|
|
53
|
-
setNoValidate(true);
|
|
54
|
-
}, []);
|
|
55
|
-
react.useEffect(() => {
|
|
56
|
-
var form = ref.current;
|
|
57
|
-
var submission = config.lastSubmission;
|
|
58
|
-
if (!form || !submission) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
var listCommand = dom.parseListCommand(submission.intent);
|
|
62
|
-
if (listCommand) {
|
|
63
|
-
form.dispatchEvent(new CustomEvent('conform/list', {
|
|
64
|
-
detail: submission.intent
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
setLastSubmission(submission);
|
|
68
|
-
}, [ref, config.lastSubmission]);
|
|
69
158
|
react.useEffect(() => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
dom.reportSubmission(form, lastSubmission);
|
|
75
|
-
}, [ref, lastSubmission]);
|
|
76
|
-
react.useEffect(() => {
|
|
77
|
-
// Revalidate the form when input value is changed
|
|
78
|
-
var handleInput = event => {
|
|
79
|
-
var field = event.target;
|
|
80
|
-
var form = ref.current;
|
|
81
|
-
var formConfig = configRef.current;
|
|
82
|
-
var {
|
|
83
|
-
initialReport = 'onSubmit',
|
|
84
|
-
shouldValidate = initialReport === 'onChange' ? 'onInput' : initialReport,
|
|
85
|
-
shouldRevalidate = 'onInput'
|
|
86
|
-
} = formConfig;
|
|
87
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (field.dataset.conformTouched ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
|
|
91
|
-
dom.requestIntent(form, dom.validate(field.name));
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
var handleBlur = event => {
|
|
159
|
+
// custom validate handler
|
|
160
|
+
var createValidateHandler = name => event => {
|
|
95
161
|
var field = event.target;
|
|
96
162
|
var form = ref.current;
|
|
97
|
-
var formConfig = configRef.current;
|
|
98
163
|
var {
|
|
99
164
|
initialReport = 'onSubmit',
|
|
100
165
|
shouldValidate = initialReport === 'onChange' ? 'onInput' : initialReport,
|
|
101
166
|
shouldRevalidate = 'onInput'
|
|
102
|
-
} =
|
|
103
|
-
if (!form || !dom.
|
|
167
|
+
} = configRef.current;
|
|
168
|
+
if (!form || !dom.isFocusableFormControl(field) || field.form !== form) {
|
|
104
169
|
return;
|
|
105
170
|
}
|
|
106
|
-
if (field.dataset.conformTouched ? shouldRevalidate ===
|
|
171
|
+
if (field.dataset.conformTouched ? shouldRevalidate === name : shouldValidate === name) {
|
|
107
172
|
dom.requestIntent(form, dom.validate(field.name));
|
|
108
173
|
}
|
|
109
174
|
};
|
|
110
175
|
var handleInvalid = event => {
|
|
111
176
|
var form = ref.current;
|
|
112
177
|
var field = event.target;
|
|
113
|
-
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) {
|
|
114
179
|
return;
|
|
115
180
|
}
|
|
116
181
|
event.preventDefault();
|
|
@@ -125,14 +190,14 @@ function useForm() {
|
|
|
125
190
|
}
|
|
126
191
|
|
|
127
192
|
// Reset all field state
|
|
128
|
-
for (var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
field.setCustomValidity('');
|
|
132
|
-
}
|
|
193
|
+
for (var element of dom.getFormControls(form)) {
|
|
194
|
+
delete element.dataset.conformTouched;
|
|
195
|
+
element.setCustomValidity('');
|
|
133
196
|
}
|
|
134
197
|
setErrors([]);
|
|
135
198
|
};
|
|
199
|
+
var handleInput = createValidateHandler('onInput');
|
|
200
|
+
var handleBlur = createValidateHandler('onBlur');
|
|
136
201
|
document.addEventListener('input', handleInput, true);
|
|
137
202
|
document.addEventListener('blur', handleBlur, true);
|
|
138
203
|
document.addEventListener('invalid', handleInvalid, true);
|
|
@@ -143,7 +208,7 @@ function useForm() {
|
|
|
143
208
|
document.removeEventListener('invalid', handleInvalid, true);
|
|
144
209
|
document.removeEventListener('reset', handleReset);
|
|
145
210
|
};
|
|
146
|
-
}, [ref]);
|
|
211
|
+
}, [ref, configRef]);
|
|
147
212
|
var form = {
|
|
148
213
|
ref,
|
|
149
214
|
error: errors[0],
|
|
@@ -159,34 +224,32 @@ function useForm() {
|
|
|
159
224
|
return;
|
|
160
225
|
}
|
|
161
226
|
try {
|
|
162
|
-
var _config$onValidate;
|
|
227
|
+
var _config$onValidate, _config$onValidate2;
|
|
163
228
|
var formData = dom.getFormData(form, submitter);
|
|
164
|
-
var
|
|
165
|
-
var submission = getSubmission({
|
|
229
|
+
var submission = (_config$onValidate = (_config$onValidate2 = config.onValidate) === null || _config$onValidate2 === void 0 ? void 0 : _config$onValidate2.call(config, {
|
|
166
230
|
form,
|
|
167
231
|
formData
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
var [, message] =
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
detail: submission.intent
|
|
180
|
-
}));
|
|
181
|
-
}
|
|
182
|
-
setLastSubmission(submission);
|
|
232
|
+
})) !== null && _config$onValidate !== void 0 ? _config$onValidate : dom.parse(formData);
|
|
233
|
+
var messages = Object.entries(submission.error).reduce((messages, _ref2) => {
|
|
234
|
+
var [, message] = _ref2;
|
|
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);
|
|
183
243
|
event.preventDefault();
|
|
184
244
|
} else {
|
|
185
245
|
var _config$onSubmit;
|
|
186
|
-
(_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, {
|
|
187
247
|
formData,
|
|
188
|
-
submission
|
|
189
|
-
|
|
248
|
+
submission,
|
|
249
|
+
action: dom.getFormAction(nativeEvent),
|
|
250
|
+
encType: dom.getFormEncType(nativeEvent),
|
|
251
|
+
method: dom.getFormMethod(nativeEvent)
|
|
252
|
+
});
|
|
190
253
|
}
|
|
191
254
|
} catch (e) {
|
|
192
255
|
console.warn(e);
|
|
@@ -211,67 +274,10 @@ function useForm() {
|
|
|
211
274
|
*/
|
|
212
275
|
|
|
213
276
|
function useFieldset(ref, config) {
|
|
214
|
-
var
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!initialError) {
|
|
218
|
-
return {};
|
|
219
|
-
}
|
|
220
|
-
var result = {};
|
|
221
|
-
for (var [name, message] of Object.entries(initialError)) {
|
|
222
|
-
var [key, ...paths] = dom.getPaths(name);
|
|
223
|
-
if (typeof key === 'string' && paths.length === 0) {
|
|
224
|
-
result[key] = [].concat(message !== null && message !== void 0 ? message : []);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return result;
|
|
228
|
-
});
|
|
229
|
-
useSafeLayoutEffect(() => {
|
|
230
|
-
configRef.current = config;
|
|
277
|
+
var [error] = useFormError(ref, {
|
|
278
|
+
initialError: config.initialError,
|
|
279
|
+
name: config.name
|
|
231
280
|
});
|
|
232
|
-
react.useEffect(() => {
|
|
233
|
-
var invalidHandler = event => {
|
|
234
|
-
var _configRef$current$na;
|
|
235
|
-
var form = dom.getFormElement(ref.current);
|
|
236
|
-
var field = event.target;
|
|
237
|
-
var fieldsetName = (_configRef$current$na = configRef.current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
|
|
238
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
var [key, ...paths] = dom.getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name);
|
|
242
|
-
|
|
243
|
-
// Update the error only if the field belongs to the fieldset
|
|
244
|
-
if (typeof key === 'string' && paths.length === 0) {
|
|
245
|
-
if (field.dataset.conformTouched) {
|
|
246
|
-
setError(prev => {
|
|
247
|
-
var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[key]);
|
|
248
|
-
if (prevMessage === field.validationMessage) {
|
|
249
|
-
return prev;
|
|
250
|
-
}
|
|
251
|
-
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
|
|
252
|
-
[key]: dom.getErrors(field.validationMessage)
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
event.preventDefault();
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
var resetHandler = event => {
|
|
260
|
-
var form = dom.getFormElement(ref.current);
|
|
261
|
-
if (!form || event.target !== form) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
setError({});
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// The invalid event does not bubble and so listening on the capturing pharse is needed
|
|
268
|
-
document.addEventListener('invalid', invalidHandler, true);
|
|
269
|
-
document.addEventListener('reset', resetHandler);
|
|
270
|
-
return () => {
|
|
271
|
-
document.removeEventListener('invalid', invalidHandler, true);
|
|
272
|
-
document.removeEventListener('reset', resetHandler);
|
|
273
|
-
};
|
|
274
|
-
}, [ref]);
|
|
275
281
|
|
|
276
282
|
/**
|
|
277
283
|
* This allows us constructing the field at runtime as we have no information
|
|
@@ -287,8 +293,8 @@ function useFieldset(ref, config) {
|
|
|
287
293
|
var fieldsetConfig = config;
|
|
288
294
|
var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key];
|
|
289
295
|
var errors = error === null || error === void 0 ? void 0 : error[key];
|
|
290
|
-
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result,
|
|
291
|
-
var [name, message] =
|
|
296
|
+
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref3) => {
|
|
297
|
+
var [name, message] = _ref3;
|
|
292
298
|
var [field, ...paths] = dom.getPaths(name);
|
|
293
299
|
if (field === key) {
|
|
294
300
|
result[dom.getName(paths)] = message;
|
|
@@ -314,57 +320,22 @@ function useFieldset(ref, config) {
|
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
/**
|
|
317
|
-
* Returns a list of key and config
|
|
318
|
-
* configuring buttons for list manipulation
|
|
323
|
+
* Returns a list of key and field config.
|
|
319
324
|
*
|
|
320
325
|
* @see https://conform.guide/api/react#usefieldlist
|
|
321
326
|
*/
|
|
322
327
|
function useFieldList(ref, config) {
|
|
323
|
-
var configRef =
|
|
324
|
-
var [error, setError] =
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
var _config$initialError;
|
|
328
|
-
var [index, ...paths] = dom.getPaths(name);
|
|
329
|
-
if (typeof index === 'number' && paths.length === 0) {
|
|
330
|
-
initialError[index] = [].concat(message !== null && message !== void 0 ? message : []);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return initialError;
|
|
328
|
+
var configRef = useConfigRef(config);
|
|
329
|
+
var [error, setError] = useFormError(ref, {
|
|
330
|
+
initialError: config.initialError,
|
|
331
|
+
name: config.name
|
|
334
332
|
});
|
|
335
333
|
var [entries, setEntries] = react.useState(() => {
|
|
336
334
|
var _config$defaultValue;
|
|
337
335
|
return Object.entries((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : [undefined]);
|
|
338
336
|
});
|
|
339
|
-
useSafeLayoutEffect(() => {
|
|
340
|
-
configRef.current = config;
|
|
341
|
-
});
|
|
342
337
|
react.useEffect(() => {
|
|
343
|
-
var
|
|
344
|
-
var _configRef$current$na2;
|
|
345
|
-
var form = dom.getFormElement(ref.current);
|
|
346
|
-
var field = event.target;
|
|
347
|
-
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
348
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
|
|
352
|
-
|
|
353
|
-
// Update the error only if the field belongs to the fieldset
|
|
354
|
-
if (typeof index === 'number' && paths.length === 0) {
|
|
355
|
-
if (field.dataset.conformTouched) {
|
|
356
|
-
setError(prev => {
|
|
357
|
-
var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[index]);
|
|
358
|
-
if (prevMessage === field.validationMessage) {
|
|
359
|
-
return prev;
|
|
360
|
-
}
|
|
361
|
-
return [...prev.slice(0, index), dom.getErrors(field.validationMessage), ...prev.slice(index + 1)];
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
event.preventDefault();
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
var listHandler = event => {
|
|
338
|
+
var conformHandler = event => {
|
|
368
339
|
var form = dom.getFormElement(ref.current);
|
|
369
340
|
if (!form || event.target !== form) {
|
|
370
341
|
return;
|
|
@@ -393,20 +364,29 @@ function useFieldList(ref, config) {
|
|
|
393
364
|
}
|
|
394
365
|
});
|
|
395
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
|
+
}
|
|
396
373
|
switch (command.type) {
|
|
397
374
|
case 'append':
|
|
398
375
|
case 'prepend':
|
|
399
376
|
case 'replace':
|
|
400
|
-
|
|
377
|
+
errorList = dom.updateList(errorList, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
|
|
401
378
|
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
|
|
402
379
|
defaultValue: undefined
|
|
403
380
|
})
|
|
404
381
|
}));
|
|
382
|
+
break;
|
|
405
383
|
default:
|
|
406
384
|
{
|
|
407
|
-
|
|
385
|
+
errorList = dom.updateList(errorList, command);
|
|
386
|
+
break;
|
|
408
387
|
}
|
|
409
388
|
}
|
|
389
|
+
return Object.assign({}, errorList);
|
|
410
390
|
});
|
|
411
391
|
};
|
|
412
392
|
var resetHandler = event => {
|
|
@@ -416,26 +396,23 @@ function useFieldList(ref, config) {
|
|
|
416
396
|
return;
|
|
417
397
|
}
|
|
418
398
|
setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : [undefined]));
|
|
419
|
-
setError([]);
|
|
420
399
|
};
|
|
421
400
|
|
|
422
|
-
// @ts-expect-error Custom event: conform
|
|
423
|
-
document.addEventListener('conform
|
|
424
|
-
document.addEventListener('invalid', invalidHandler, true);
|
|
401
|
+
// @ts-expect-error Custom event: conform
|
|
402
|
+
document.addEventListener('conform', conformHandler, true);
|
|
425
403
|
document.addEventListener('reset', resetHandler);
|
|
426
404
|
return () => {
|
|
427
|
-
// @ts-expect-error Custom event: conform
|
|
428
|
-
document.removeEventListener('conform
|
|
429
|
-
document.removeEventListener('invalid', invalidHandler, true);
|
|
405
|
+
// @ts-expect-error Custom event: conform
|
|
406
|
+
document.removeEventListener('conform', conformHandler, true);
|
|
430
407
|
document.removeEventListener('reset', resetHandler);
|
|
431
408
|
};
|
|
432
|
-
}, [ref]);
|
|
433
|
-
return entries.map((
|
|
434
|
-
var _config$
|
|
435
|
-
var [key, defaultValue] =
|
|
409
|
+
}, [ref, configRef, setError]);
|
|
410
|
+
return entries.map((_ref4, index) => {
|
|
411
|
+
var _config$initialError, _config$defaultValue2;
|
|
412
|
+
var [key, defaultValue] = _ref4;
|
|
436
413
|
var errors = error[index];
|
|
437
|
-
var initialError = Object.entries((_config$
|
|
438
|
-
var [name, message] =
|
|
414
|
+
var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref5) => {
|
|
415
|
+
var [name, message] = _ref5;
|
|
439
416
|
var [field, ...paths] = dom.getPaths(name);
|
|
440
417
|
if (field === index) {
|
|
441
418
|
result[dom.getName(paths)] = message;
|
|
@@ -497,13 +474,10 @@ function setNativeValue(element, value) {
|
|
|
497
474
|
var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect;
|
|
498
475
|
function useInputEvent(options) {
|
|
499
476
|
var ref = react.useRef(null);
|
|
500
|
-
var optionsRef =
|
|
477
|
+
var optionsRef = useConfigRef(options);
|
|
501
478
|
var changeDispatched = react.useRef(false);
|
|
502
479
|
var focusDispatched = react.useRef(false);
|
|
503
480
|
var blurDispatched = react.useRef(false);
|
|
504
|
-
useSafeLayoutEffect(() => {
|
|
505
|
-
optionsRef.current = options;
|
|
506
|
-
});
|
|
507
481
|
useSafeLayoutEffect(() => {
|
|
508
482
|
var getInputElement = () => {
|
|
509
483
|
var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;
|
|
@@ -632,11 +606,160 @@ function useInputEvent(options) {
|
|
|
632
606
|
blurDispatched.current = false;
|
|
633
607
|
}
|
|
634
608
|
};
|
|
635
|
-
}, []);
|
|
609
|
+
}, [optionsRef]);
|
|
636
610
|
return [ref, control];
|
|
637
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
|
+
}
|
|
638
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;
|
|
639
761
|
exports.useFieldList = useFieldList;
|
|
640
762
|
exports.useFieldset = useFieldset;
|
|
641
763
|
exports.useForm = useForm;
|
|
642
764
|
exports.useInputEvent = useInputEvent;
|
|
765
|
+
exports.validateConstraint = validateConstraint;
|