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