@conform-to/react 0.9.1 → 1.0.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context.d.ts +56 -0
- package/context.js +154 -0
- package/context.mjs +143 -0
- package/helpers.d.ts +41 -29
- package/helpers.js +78 -67
- package/helpers.mjs +78 -56
- package/hooks.d.ts +45 -199
- package/hooks.js +125 -701
- package/hooks.mjs +124 -698
- package/index.d.ts +6 -3
- package/index.js +9 -14
- package/index.mjs +6 -2
- package/integrations.d.ts +23 -0
- package/integrations.js +145 -0
- package/integrations.mjs +141 -0
- package/intent.d.ts +38 -0
- package/intent.js +37 -0
- package/intent.mjs +32 -0
- package/package.json +3 -3
package/hooks.mjs
CHANGED
|
@@ -1,728 +1,154 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { createForm } from '@conform-to/dom';
|
|
2
|
+
import { useState, useRef, useEffect, useLayoutEffect, useId } from 'react';
|
|
3
|
+
import { useSubjectRef, useFormStore, useFormContext, getBaseMetadata, getFieldMetadata } from './context.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* useLayoutEffect is client-only.
|
|
7
|
+
* This basically makes it a no-op on server
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
setNoValidate(true);
|
|
13
|
-
}, []);
|
|
14
|
-
return noValidate;
|
|
15
|
-
}
|
|
16
|
-
function useFormRef(userProvidedRef) {
|
|
17
|
-
var formRef = useRef(null);
|
|
18
|
-
return userProvidedRef !== null && userProvidedRef !== void 0 ? userProvidedRef : formRef;
|
|
9
|
+
var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
10
|
+
function useFormId(preferredId) {
|
|
11
|
+
var id = useId();
|
|
12
|
+
return preferredId !== null && preferredId !== void 0 ? preferredId : id;
|
|
19
13
|
}
|
|
20
|
-
function
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
function useForm(options) {
|
|
15
|
+
var formId = useFormId(options.id);
|
|
16
|
+
var initializeForm = () => createForm(formId, options);
|
|
17
|
+
var [form, setForm] = useState(initializeForm);
|
|
18
|
+
|
|
19
|
+
// If id changes, reinitialize the form immediately
|
|
20
|
+
if (formId !== form.id) {
|
|
21
|
+
setForm(initializeForm);
|
|
22
|
+
}
|
|
23
|
+
var optionsRef = useRef(options);
|
|
24
|
+
var metadata = useFormMetadata({
|
|
25
|
+
formId,
|
|
26
|
+
context: form,
|
|
27
|
+
defaultNoValidate: options.defaultNoValidate
|
|
24
28
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
form.dispatchEvent(event);
|
|
34
|
-
setSubmission(submission);
|
|
35
|
-
}, []);
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
var form = ref.current;
|
|
38
|
-
if (!form || !lastSubmission) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (!lastSubmission.payload) {
|
|
42
|
-
// If the default value is empty, we can safely reset the form.
|
|
43
|
-
// This ensure the behavior is consistent with and without JS.
|
|
44
|
-
form.reset();
|
|
45
|
-
|
|
46
|
-
// There is no need to report the submission anymore.
|
|
29
|
+
var fields = useFieldset({
|
|
30
|
+
formId,
|
|
31
|
+
context: form
|
|
32
|
+
});
|
|
33
|
+
useSafeLayoutEffect(() => form.initialize(), [form]);
|
|
34
|
+
useSafeLayoutEffect(() => {
|
|
35
|
+
if (options.lastResult === optionsRef.current.lastResult) {
|
|
36
|
+
// If there is no change, do nothing
|
|
47
37
|
return;
|
|
48
38
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return;
|
|
39
|
+
if (options.lastResult) {
|
|
40
|
+
form.report(options.lastResult);
|
|
41
|
+
} else {
|
|
42
|
+
var _document$forms$named;
|
|
43
|
+
(_document$forms$named = document.forms.namedItem(form.id)) === null || _document$forms$named === void 0 ? void 0 : _document$forms$named.reset();
|
|
55
44
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
function useFormError(ref, config) {
|
|
61
|
-
var [error, setError] = useState(() => {
|
|
62
|
-
if (!config.initialError) {
|
|
63
|
-
return {};
|
|
64
|
-
}
|
|
65
|
-
var result = {};
|
|
66
|
-
for (var [name, message] of Object.entries(config.initialError)) {
|
|
67
|
-
var [path, ...restPaths] = getPaths(name);
|
|
68
|
-
if (typeof path !== 'undefined' && restPaths.length === 0) {
|
|
69
|
-
result[path] = message;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return result;
|
|
45
|
+
}, [form, options.lastResult]);
|
|
46
|
+
useSafeLayoutEffect(() => {
|
|
47
|
+
optionsRef.current = options;
|
|
48
|
+
form.update(options);
|
|
73
49
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : '';
|
|
80
|
-
if (!isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
var name = element.name.slice(prefix.length);
|
|
84
|
-
var [path, ...restPaths] = getPaths(name);
|
|
85
|
-
if (typeof path === 'undefined' || restPaths.length > 0) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
setError(prev => {
|
|
89
|
-
if (element.validationMessage === getValidationMessage(prev[path])) {
|
|
90
|
-
return prev;
|
|
91
|
-
}
|
|
92
|
-
return _objectSpread2(_objectSpread2({}, prev), {}, {
|
|
93
|
-
[path]: getErrors(element.validationMessage)
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
event.preventDefault();
|
|
97
|
-
};
|
|
98
|
-
var handleReset = event => {
|
|
99
|
-
var form = getFormElement(ref.current);
|
|
100
|
-
if (form && event.target === form) {
|
|
101
|
-
setError({});
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
document.addEventListener('reset', handleReset);
|
|
105
|
-
document.addEventListener('invalid', handleInvalid, true);
|
|
106
|
-
return () => {
|
|
107
|
-
document.removeEventListener('reset', handleReset);
|
|
108
|
-
document.removeEventListener('invalid', handleInvalid, true);
|
|
109
|
-
};
|
|
110
|
-
}, [ref, config.name]);
|
|
111
|
-
return [error, setError];
|
|
50
|
+
return {
|
|
51
|
+
context: form,
|
|
52
|
+
fields,
|
|
53
|
+
form: metadata
|
|
54
|
+
};
|
|
112
55
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
function useForm() {
|
|
121
|
-
var _config$lastSubmissio3, _config$lastSubmissio4;
|
|
122
|
-
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
123
|
-
var configRef = useConfigRef(config);
|
|
124
|
-
var ref = useFormRef(config.ref);
|
|
125
|
-
var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
|
|
126
|
-
var report = useFormReporter(ref, config.lastSubmission);
|
|
127
|
-
var [errors, setErrors] = useState(() => {
|
|
128
|
-
var _config$lastSubmissio, _config$lastSubmissio2;
|
|
129
|
-
return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : [];
|
|
56
|
+
function useFormMetadata(options) {
|
|
57
|
+
var _options$defaultNoVal;
|
|
58
|
+
var subjectRef = useSubjectRef();
|
|
59
|
+
var form = useFormStore(options.formId, options.context);
|
|
60
|
+
var context = useFormContext(form, subjectRef);
|
|
61
|
+
var metadata = getBaseMetadata(options.formId, context, {
|
|
62
|
+
subjectRef
|
|
130
63
|
});
|
|
131
|
-
var
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
(_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null);
|
|
150
|
-
var fieldset = useFieldset(ref, {
|
|
151
|
-
defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue,
|
|
152
|
-
initialError,
|
|
153
|
-
constraint: config.constraint,
|
|
154
|
-
form: config.id
|
|
155
|
-
});
|
|
156
|
-
useEffect(() => {
|
|
157
|
-
// custom validate handler
|
|
158
|
-
var createValidateHandler = type => event => {
|
|
159
|
-
var field = event.target;
|
|
160
|
-
var form = ref.current;
|
|
161
|
-
var {
|
|
162
|
-
shouldValidate = 'onSubmit',
|
|
163
|
-
shouldRevalidate = shouldValidate
|
|
164
|
-
} = configRef.current;
|
|
165
|
-
if (!form || !isFocusableFormControl(field) || field.form !== form || !field.name) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (field.dataset.conformTouched ? shouldRevalidate === type : shouldValidate === type) {
|
|
169
|
-
requestIntent(form, validate(field.name));
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
var handleInvalid = event => {
|
|
173
|
-
var form = ref.current;
|
|
174
|
-
var field = event.target;
|
|
175
|
-
if (!form || !isFieldElement(field) || field.form !== form || field.name !== FORM_ERROR_ELEMENT_NAME) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
event.preventDefault();
|
|
179
|
-
if (field.dataset.conformTouched) {
|
|
180
|
-
setErrors(getErrors(field.validationMessage));
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
var handleReset = event => {
|
|
184
|
-
var form = ref.current;
|
|
185
|
-
if (!form || event.target !== form) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Reset all field state
|
|
190
|
-
for (var _element of getFormControls(form)) {
|
|
191
|
-
delete _element.dataset.conformTouched;
|
|
192
|
-
_element.setCustomValidity('');
|
|
193
|
-
}
|
|
194
|
-
setErrors([]);
|
|
195
|
-
setDefaultValueFromLastSubmission(null);
|
|
196
|
-
};
|
|
197
|
-
var handleInput = createValidateHandler('onInput');
|
|
198
|
-
var handleBlur = createValidateHandler('onBlur');
|
|
199
|
-
document.addEventListener('input', handleInput, true);
|
|
200
|
-
document.addEventListener('blur', handleBlur, true);
|
|
201
|
-
document.addEventListener('invalid', handleInvalid, true);
|
|
202
|
-
document.addEventListener('reset', handleReset);
|
|
203
|
-
return () => {
|
|
204
|
-
document.removeEventListener('input', handleInput, true);
|
|
205
|
-
document.removeEventListener('blur', handleBlur, true);
|
|
206
|
-
document.removeEventListener('invalid', handleInvalid, true);
|
|
207
|
-
document.removeEventListener('reset', handleReset);
|
|
208
|
-
};
|
|
209
|
-
}, [ref, configRef]);
|
|
210
|
-
var form = {
|
|
211
|
-
ref,
|
|
212
|
-
error: errors[0],
|
|
213
|
-
errors,
|
|
214
|
-
props: {
|
|
215
|
-
ref,
|
|
216
|
-
noValidate,
|
|
217
|
-
onSubmit(event) {
|
|
218
|
-
var form = event.currentTarget;
|
|
219
|
-
var nativeEvent = event.nativeEvent;
|
|
220
|
-
var submitter = nativeEvent.submitter;
|
|
221
|
-
if (event.defaultPrevented) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
var _config$onValidate, _config$onValidate2;
|
|
226
|
-
var formData = getFormData(form, submitter);
|
|
227
|
-
var submission = (_config$onValidate = (_config$onValidate2 = config.onValidate) === null || _config$onValidate2 === void 0 ? void 0 : _config$onValidate2.call(config, {
|
|
228
|
-
form,
|
|
229
|
-
formData
|
|
230
|
-
})) !== null && _config$onValidate !== void 0 ? _config$onValidate : parse(formData);
|
|
231
|
-
var {
|
|
232
|
-
errors: _errors,
|
|
233
|
-
shouldServerValidate
|
|
234
|
-
} = Object.entries(submission.error).reduce((result, _ref) => {
|
|
235
|
-
var [, error] = _ref;
|
|
236
|
-
for (var message of error) {
|
|
237
|
-
if (message === VALIDATION_UNDEFINED) {
|
|
238
|
-
result.shouldServerValidate = true;
|
|
239
|
-
} else if (message !== VALIDATION_SKIPPED) {
|
|
240
|
-
result.errors.push(message);
|
|
241
|
-
}
|
|
64
|
+
var [noValidate, setNoValidate] = useState((_options$defaultNoVal = options.defaultNoValidate) !== null && _options$defaultNoVal !== void 0 ? _options$defaultNoVal : true);
|
|
65
|
+
useSafeLayoutEffect(() => {
|
|
66
|
+
// This is necessary to fix an issue in strict mode with related to our proxy setup
|
|
67
|
+
// It avoids the component from being rerendered without re-rendering the child
|
|
68
|
+
// Which reset the proxy but failed to capture its usage within child component
|
|
69
|
+
if (!noValidate) {
|
|
70
|
+
setNoValidate(true);
|
|
71
|
+
}
|
|
72
|
+
}, [noValidate]);
|
|
73
|
+
return new Proxy(metadata, {
|
|
74
|
+
get(target, key, receiver) {
|
|
75
|
+
switch (key) {
|
|
76
|
+
case 'onSubmit':
|
|
77
|
+
return event => {
|
|
78
|
+
var submitEvent = event.nativeEvent;
|
|
79
|
+
var result = form.submit(submitEvent);
|
|
80
|
+
if (submitEvent.defaultPrevented) {
|
|
81
|
+
event.preventDefault();
|
|
242
82
|
}
|
|
243
83
|
return result;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// has client validation
|
|
250
|
-
typeof config.onValidate !== 'undefined' &&
|
|
251
|
-
// not necessary to validate on the server
|
|
252
|
-
!shouldServerValidate && (
|
|
253
|
-
// client validation failed or non submit intent
|
|
254
|
-
!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && _errors.length > 0 || parseIntent(submission.intent) !== null)) {
|
|
255
|
-
report(form, submission);
|
|
256
|
-
event.preventDefault();
|
|
257
|
-
} else {
|
|
258
|
-
var _config$onSubmit;
|
|
259
|
-
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
|
|
260
|
-
formData,
|
|
261
|
-
submission,
|
|
262
|
-
action: getFormAction(nativeEvent),
|
|
263
|
-
encType: getFormEncType(nativeEvent),
|
|
264
|
-
method: getFormMethod(nativeEvent)
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
} catch (error) {
|
|
268
|
-
// eslint-disable-next-line no-console
|
|
269
|
-
console.warn('Client validation failed', error);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
if (config.id) {
|
|
275
|
-
form.id = config.id;
|
|
276
|
-
form.errorId = "".concat(config.id, "-error");
|
|
277
|
-
form.props.id = form.id;
|
|
278
|
-
}
|
|
279
|
-
if (form.errorId && form.errors.length > 0) {
|
|
280
|
-
form.props['aria-invalid'] = 'true';
|
|
281
|
-
form.props['aria-describedby'] = form.errorId;
|
|
282
|
-
}
|
|
283
|
-
return [form, fieldset];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* A set of field configuration
|
|
288
|
-
*/
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Returns all the information about the fieldset.
|
|
292
|
-
*
|
|
293
|
-
* @see https://conform.guide/api/react#usefieldset
|
|
294
|
-
*/
|
|
295
|
-
|
|
296
|
-
function useFieldset(ref, config) {
|
|
297
|
-
var [error] = useFormError(ref, {
|
|
298
|
-
initialError: config.initialError,
|
|
299
|
-
name: config.name
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* This allows us constructing the field at runtime as we have no information
|
|
304
|
-
* about which fields would be available. The proxy will also help tracking
|
|
305
|
-
* the usage of each field for optimization in the future.
|
|
306
|
-
*/
|
|
307
|
-
return new Proxy({}, {
|
|
308
|
-
get(_target, key) {
|
|
309
|
-
var _fieldsetConfig$const, _fieldsetConfig$initi, _fieldsetConfig$defau;
|
|
310
|
-
if (typeof key !== 'string') {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
var fieldsetConfig = config;
|
|
314
|
-
var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key];
|
|
315
|
-
var errors = error === null || error === void 0 ? void 0 : error[key];
|
|
316
|
-
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref2) => {
|
|
317
|
-
var [name, message] = _ref2;
|
|
318
|
-
var [field, ...paths] = getPaths(name);
|
|
319
|
-
if (field === key) {
|
|
320
|
-
result[getName(paths)] = message;
|
|
321
|
-
}
|
|
322
|
-
return result;
|
|
323
|
-
}, {});
|
|
324
|
-
var field = _objectSpread2(_objectSpread2({}, constraint), {}, {
|
|
325
|
-
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
|
|
326
|
-
// @ts-expect-error The FieldValue type might need a rework
|
|
327
|
-
defaultValue: (_fieldsetConfig$defau = fieldsetConfig.defaultValue) === null || _fieldsetConfig$defau === void 0 ? void 0 : _fieldsetConfig$defau[key],
|
|
328
|
-
initialError,
|
|
329
|
-
error: errors === null || errors === void 0 ? void 0 : errors[0],
|
|
330
|
-
errors
|
|
331
|
-
});
|
|
332
|
-
if (fieldsetConfig.form) {
|
|
333
|
-
field.form = fieldsetConfig.form;
|
|
334
|
-
field.id = "".concat(fieldsetConfig.form, "-").concat(field.name);
|
|
335
|
-
field.errorId = "".concat(field.id, "-error");
|
|
336
|
-
field.descriptionId = "".concat(field.id, "-description");
|
|
84
|
+
};
|
|
85
|
+
case 'onReset':
|
|
86
|
+
return event => form.reset(event.nativeEvent);
|
|
87
|
+
case 'noValidate':
|
|
88
|
+
return noValidate;
|
|
337
89
|
}
|
|
338
|
-
return
|
|
90
|
+
return Reflect.get(target, key, receiver);
|
|
339
91
|
}
|
|
340
92
|
});
|
|
341
93
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
name: config.name
|
|
353
|
-
});
|
|
354
|
-
var [entries, setEntries] = useState(() => {
|
|
355
|
-
var _config$defaultValue;
|
|
356
|
-
return Object.entries((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : []);
|
|
357
|
-
});
|
|
358
|
-
useEffect(() => {
|
|
359
|
-
var conformHandler = event => {
|
|
360
|
-
var form = getFormElement(ref.current);
|
|
361
|
-
if (!form || event.target !== form) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
var intent = parseIntent(event.detail);
|
|
365
|
-
if ((intent === null || intent === void 0 ? void 0 : intent.type) !== 'list' || (intent === null || intent === void 0 ? void 0 : intent.payload.name) !== configRef.current.name) {
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
setEntries(entries => {
|
|
369
|
-
var list = [...entries];
|
|
370
|
-
switch (intent.payload.operation) {
|
|
371
|
-
case 'append':
|
|
372
|
-
case 'prepend':
|
|
373
|
-
case 'insert':
|
|
374
|
-
case 'replace':
|
|
375
|
-
return updateList(list, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
|
|
376
|
-
defaultValue: [
|
|
377
|
-
// Generate a random key to avoid conflicts
|
|
378
|
-
getUniqueKey(), intent.payload.defaultValue]
|
|
379
|
-
}));
|
|
380
|
-
default:
|
|
381
|
-
return updateList(list, intent.payload);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
setError(error => {
|
|
385
|
-
var errorList = [];
|
|
386
|
-
for (var [key, messages] of Object.entries(error)) {
|
|
387
|
-
if (typeof key === 'number') {
|
|
388
|
-
errorList[key] = messages;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
switch (intent.payload.operation) {
|
|
392
|
-
case 'append':
|
|
393
|
-
case 'prepend':
|
|
394
|
-
case 'insert':
|
|
395
|
-
case 'replace':
|
|
396
|
-
errorList = updateList(errorList, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
|
|
397
|
-
defaultValue: undefined
|
|
398
|
-
}));
|
|
399
|
-
break;
|
|
400
|
-
default:
|
|
401
|
-
errorList = updateList(errorList, intent.payload);
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
return Object.assign({}, errorList);
|
|
94
|
+
function useFieldset(options) {
|
|
95
|
+
var subjectRef = useSubjectRef();
|
|
96
|
+
var form = useFormStore(options.formId, options.context);
|
|
97
|
+
var context = useFormContext(form, subjectRef);
|
|
98
|
+
return new Proxy({}, {
|
|
99
|
+
get(target, prop, receiver) {
|
|
100
|
+
var getMetadata = key => getFieldMetadata(options.formId, context, {
|
|
101
|
+
name: options.name,
|
|
102
|
+
key: key,
|
|
103
|
+
subjectRef
|
|
405
104
|
});
|
|
406
|
-
};
|
|
407
|
-
var resetHandler = event => {
|
|
408
|
-
var _configRef$current$de;
|
|
409
|
-
var form = getFormElement(ref.current);
|
|
410
|
-
if (!form || event.target !== form) {
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : []));
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
// @ts-expect-error Custom event: conform
|
|
417
|
-
document.addEventListener('conform', conformHandler, true);
|
|
418
|
-
document.addEventListener('reset', resetHandler);
|
|
419
|
-
return () => {
|
|
420
|
-
// @ts-expect-error Custom event: conform
|
|
421
|
-
document.removeEventListener('conform', conformHandler, true);
|
|
422
|
-
document.removeEventListener('reset', resetHandler);
|
|
423
|
-
};
|
|
424
|
-
}, [ref, configRef, setError]);
|
|
425
|
-
return entries.map((_ref3, index) => {
|
|
426
|
-
var _config$initialError;
|
|
427
|
-
var [key, defaultValue] = _ref3;
|
|
428
|
-
var errors = error[index];
|
|
429
|
-
var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref4) => {
|
|
430
|
-
var [name, message] = _ref4;
|
|
431
|
-
var [field, ...paths] = getPaths(name);
|
|
432
|
-
if (field === index) {
|
|
433
|
-
result[getName(paths)] = message;
|
|
434
|
-
}
|
|
435
|
-
return result;
|
|
436
|
-
}, {});
|
|
437
|
-
var fieldConfig = {
|
|
438
|
-
name: "".concat(config.name, "[").concat(index, "]"),
|
|
439
|
-
defaultValue,
|
|
440
|
-
initialError,
|
|
441
|
-
error: errors === null || errors === void 0 ? void 0 : errors[0],
|
|
442
|
-
errors
|
|
443
|
-
};
|
|
444
|
-
if (config.form) {
|
|
445
|
-
fieldConfig.form = config.form;
|
|
446
|
-
fieldConfig.id = "".concat(config.form, "-").concat(config.name, "-").concat(index);
|
|
447
|
-
fieldConfig.errorId = "".concat(fieldConfig.id, "-error");
|
|
448
|
-
fieldConfig.descriptionId = "".concat(fieldConfig.id, "-description");
|
|
449
|
-
}
|
|
450
|
-
return _objectSpread2({
|
|
451
|
-
key
|
|
452
|
-
}, fieldConfig);
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* useLayoutEffect is client-only.
|
|
458
|
-
* This basically makes it a no-op on server
|
|
459
|
-
*/
|
|
460
|
-
var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
461
|
-
/**
|
|
462
|
-
* Returns a ref object and a set of helpers that dispatch corresponding dom event.
|
|
463
|
-
*
|
|
464
|
-
* @see https://conform.guide/api/react#useinputevent
|
|
465
|
-
*/
|
|
466
|
-
function useInputEvent(options) {
|
|
467
|
-
var optionsRef = useConfigRef(options);
|
|
468
|
-
var eventDispatched = useRef({
|
|
469
|
-
onInput: false,
|
|
470
|
-
onFocus: false,
|
|
471
|
-
onBlur: false
|
|
472
|
-
});
|
|
473
|
-
useSafeLayoutEffect(() => {
|
|
474
|
-
var createEventListener = listener => {
|
|
475
|
-
return event => {
|
|
476
|
-
var _optionsRef$current, _optionsRef$current2, _optionsRef$current3;
|
|
477
|
-
var element = typeof ((_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 ? void 0 : _optionsRef$current.ref) === 'function' ? (_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : _optionsRef$current2.ref() : (_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : _optionsRef$current3.ref.current;
|
|
478
|
-
if (isFieldElement(element) && (listener === 'onReset' ? event.target === element.form : event.target === element)) {
|
|
479
|
-
var _optionsRef$current4, _optionsRef$current4$;
|
|
480
|
-
if (listener !== 'onReset') {
|
|
481
|
-
eventDispatched.current[listener] = true;
|
|
482
|
-
}
|
|
483
|
-
(_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 || (_optionsRef$current4$ = _optionsRef$current4[listener]) === null || _optionsRef$current4$ === void 0 ? void 0 : _optionsRef$current4$.call(_optionsRef$current4, event);
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
};
|
|
487
|
-
var inputHandler = createEventListener('onInput');
|
|
488
|
-
var focusHandler = createEventListener('onFocus');
|
|
489
|
-
var blurHandler = createEventListener('onBlur');
|
|
490
|
-
var resetHandler = createEventListener('onReset');
|
|
491
|
-
|
|
492
|
-
// focus/blur event does not bubble
|
|
493
|
-
document.addEventListener('input', inputHandler, true);
|
|
494
|
-
document.addEventListener('focus', focusHandler, true);
|
|
495
|
-
document.addEventListener('blur', blurHandler, true);
|
|
496
|
-
document.addEventListener('reset', resetHandler);
|
|
497
|
-
return () => {
|
|
498
|
-
document.removeEventListener('input', inputHandler, true);
|
|
499
|
-
document.removeEventListener('focus', focusHandler, true);
|
|
500
|
-
document.removeEventListener('blur', blurHandler, true);
|
|
501
|
-
document.removeEventListener('reset', resetHandler);
|
|
502
|
-
};
|
|
503
|
-
}, []);
|
|
504
|
-
var control = useMemo(() => {
|
|
505
|
-
var dispatch = (listener, fn) => {
|
|
506
|
-
if (!eventDispatched.current[listener]) {
|
|
507
|
-
var _optionsRef$current5, _optionsRef$current6, _optionsRef$current7;
|
|
508
|
-
var _element2 = typeof ((_optionsRef$current5 = optionsRef.current) === null || _optionsRef$current5 === void 0 ? void 0 : _optionsRef$current5.ref) === 'function' ? (_optionsRef$current6 = optionsRef.current) === null || _optionsRef$current6 === void 0 ? void 0 : _optionsRef$current6.ref() : (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.ref.current;
|
|
509
|
-
if (!isFieldElement(_element2)) {
|
|
510
|
-
// eslint-disable-next-line no-console
|
|
511
|
-
console.warn('Failed to dispatch event; is the input mounted?');
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// To avoid recursion
|
|
516
|
-
eventDispatched.current[listener] = true;
|
|
517
|
-
fn(_element2);
|
|
518
|
-
}
|
|
519
|
-
eventDispatched.current[listener] = false;
|
|
520
|
-
};
|
|
521
|
-
return {
|
|
522
|
-
change(eventOrValue) {
|
|
523
|
-
dispatch('onInput', element => {
|
|
524
|
-
if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
|
|
525
|
-
if (typeof eventOrValue !== 'boolean') {
|
|
526
|
-
throw new Error('You should pass a boolean when changing a checkbox or radio input');
|
|
527
|
-
}
|
|
528
|
-
element.checked = eventOrValue;
|
|
529
|
-
} else {
|
|
530
|
-
if (typeof eventOrValue === 'boolean') {
|
|
531
|
-
throw new Error('You can pass a boolean only when changing a checkbox or radio input');
|
|
532
|
-
}
|
|
533
|
-
var _value = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
|
|
534
|
-
|
|
535
|
-
// No change event will triggered on React if `element.value` is updated
|
|
536
|
-
// before dispatching the event
|
|
537
|
-
if (element.value !== _value) {
|
|
538
|
-
/**
|
|
539
|
-
* Triggering react custom change event
|
|
540
|
-
* Solution based on dom-testing-library
|
|
541
|
-
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
542
|
-
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
543
|
-
*/
|
|
544
|
-
var {
|
|
545
|
-
set: valueSetter
|
|
546
|
-
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
547
|
-
var prototype = Object.getPrototypeOf(element);
|
|
548
|
-
var {
|
|
549
|
-
set: prototypeValueSetter
|
|
550
|
-
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
551
|
-
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
552
|
-
prototypeValueSetter.call(element, _value);
|
|
553
|
-
} else {
|
|
554
|
-
if (valueSetter) {
|
|
555
|
-
valueSetter.call(element, _value);
|
|
556
|
-
} else {
|
|
557
|
-
throw new Error('The given element does not have a value setter');
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
105
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
})
|
|
106
|
+
// To support array destructuring
|
|
107
|
+
if (prop === Symbol.iterator) {
|
|
108
|
+
var _index = 0;
|
|
109
|
+
return () => ({
|
|
110
|
+
next: () => ({
|
|
111
|
+
value: getMetadata(_index++),
|
|
112
|
+
done: false
|
|
113
|
+
})
|
|
571
114
|
});
|
|
572
|
-
},
|
|
573
|
-
focus() {
|
|
574
|
-
dispatch('onFocus', element => {
|
|
575
|
-
element.dispatchEvent(new FocusEvent('focusin', {
|
|
576
|
-
bubbles: true
|
|
577
|
-
}));
|
|
578
|
-
element.dispatchEvent(new FocusEvent('focus'));
|
|
579
|
-
});
|
|
580
|
-
},
|
|
581
|
-
blur() {
|
|
582
|
-
dispatch('onBlur', element => {
|
|
583
|
-
element.dispatchEvent(new FocusEvent('focusout', {
|
|
584
|
-
bubbles: true
|
|
585
|
-
}));
|
|
586
|
-
element.dispatchEvent(new FocusEvent('blur'));
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
}, [optionsRef]);
|
|
591
|
-
return control;
|
|
592
|
-
}
|
|
593
|
-
var FORM_ERROR_ELEMENT_NAME = '__form__';
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Validate the form with the Constraint Validation API
|
|
597
|
-
* @see https://conform.guide/api/react#validateconstraint
|
|
598
|
-
*/
|
|
599
|
-
function validateConstraint(options) {
|
|
600
|
-
var _options$formData, _options$formatMessag;
|
|
601
|
-
var formData = (_options$formData = options === null || options === void 0 ? void 0 : options.formData) !== null && _options$formData !== void 0 ? _options$formData : new FormData(options.form);
|
|
602
|
-
var getDefaultErrors = (validity, result) => {
|
|
603
|
-
var errors = [];
|
|
604
|
-
if (validity.valueMissing) errors.push('required');
|
|
605
|
-
if (validity.typeMismatch || validity.badInput) errors.push('type');
|
|
606
|
-
if (validity.tooShort) errors.push('minLength');
|
|
607
|
-
if (validity.rangeUnderflow) errors.push('min');
|
|
608
|
-
if (validity.stepMismatch) errors.push('step');
|
|
609
|
-
if (validity.tooLong) errors.push('maxLength');
|
|
610
|
-
if (validity.rangeOverflow) errors.push('max');
|
|
611
|
-
if (validity.patternMismatch) errors.push('pattern');
|
|
612
|
-
for (var [constraintName, valid] of Object.entries(result)) {
|
|
613
|
-
if (!valid) {
|
|
614
|
-
errors.push(constraintName);
|
|
615
115
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
var formatMessages = (_options$formatMessag = options === null || options === void 0 ? void 0 : options.formatMessages) !== null && _options$formatMessag !== void 0 ? _options$formatMessag : _ref5 => {
|
|
620
|
-
var {
|
|
621
|
-
defaultErrors
|
|
622
|
-
} = _ref5;
|
|
623
|
-
return defaultErrors;
|
|
624
|
-
};
|
|
625
|
-
return parse(formData, {
|
|
626
|
-
resolve() {
|
|
627
|
-
var error = {};
|
|
628
|
-
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
|
|
629
|
-
var _loop = function _loop(_element3) {
|
|
630
|
-
if (isFieldElement(_element3)) {
|
|
631
|
-
var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : '';
|
|
632
|
-
var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => {
|
|
633
|
-
var [name, attributeValue = ''] = _ref6;
|
|
634
|
-
if (constraintPattern.test(name)) {
|
|
635
|
-
var _options$constraint;
|
|
636
|
-
var constraintName = name.slice(10).toLowerCase();
|
|
637
|
-
var _validate = (_options$constraint = options.constraint) === null || _options$constraint === void 0 ? void 0 : _options$constraint[constraintName];
|
|
638
|
-
if (typeof _validate === 'function') {
|
|
639
|
-
result[constraintName] = _validate(_element3.value, {
|
|
640
|
-
formData,
|
|
641
|
-
attributeValue
|
|
642
|
-
});
|
|
643
|
-
} else {
|
|
644
|
-
// eslint-disable-next-line no-console
|
|
645
|
-
console.warn("Found an \"".concat(constraintName, "\" constraint with undefined definition; Please specify it on the validateConstraint API."));
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return result;
|
|
649
|
-
}, {});
|
|
650
|
-
var errors = formatMessages({
|
|
651
|
-
name,
|
|
652
|
-
validity: _element3.validity,
|
|
653
|
-
constraint,
|
|
654
|
-
defaultErrors: getDefaultErrors(_element3.validity, constraint)
|
|
655
|
-
});
|
|
656
|
-
if (errors.length > 0) {
|
|
657
|
-
error[name] = errors;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
};
|
|
661
|
-
for (var _element3 of options.form.elements) {
|
|
662
|
-
_loop(_element3);
|
|
116
|
+
var index = Number(prop);
|
|
117
|
+
if (typeof prop === 'string') {
|
|
118
|
+
return getMetadata(Number.isNaN(index) ? prop : index);
|
|
663
119
|
}
|
|
664
|
-
return
|
|
665
|
-
error
|
|
666
|
-
};
|
|
120
|
+
return Reflect.get(target, prop, receiver);
|
|
667
121
|
}
|
|
668
122
|
});
|
|
669
123
|
}
|
|
670
|
-
function
|
|
671
|
-
var
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
return value.toString(36);
|
|
676
|
-
}
|
|
677
|
-
function reportSubmission(form, submission) {
|
|
678
|
-
for (var [name, message] of Object.entries(submission.error)) {
|
|
679
|
-
// There is no need to create a placeholder button if all we want is to reset the error
|
|
680
|
-
if (message.length === 0) {
|
|
681
|
-
continue;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// We can't use empty string as button name
|
|
685
|
-
// As `form.element.namedItem('')` will always returns null
|
|
686
|
-
var elementName = name ? name : FORM_ERROR_ELEMENT_NAME;
|
|
687
|
-
var item = form.elements.namedItem(elementName);
|
|
688
|
-
if (item === null) {
|
|
689
|
-
// Create placeholder button to keep the error without contributing to the form data
|
|
690
|
-
var button = document.createElement('button');
|
|
691
|
-
button.name = elementName;
|
|
692
|
-
button.hidden = true;
|
|
693
|
-
button.dataset.conformTouched = 'true';
|
|
694
|
-
form.appendChild(button);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
var intent = parseIntent(submission.intent);
|
|
698
|
-
var scope = getScope(intent);
|
|
699
|
-
for (var _element4 of getFormControls(form)) {
|
|
700
|
-
var _submission$error$_el;
|
|
701
|
-
var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : '';
|
|
702
|
-
var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : [];
|
|
703
|
-
if (scope === null || scope === _elementName) {
|
|
704
|
-
_element4.dataset.conformTouched = 'true';
|
|
705
|
-
}
|
|
706
|
-
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
|
|
707
|
-
var invalidEvent = new Event('invalid', {
|
|
708
|
-
cancelable: true
|
|
709
|
-
});
|
|
710
|
-
_element4.setCustomValidity(getValidationMessage(messages));
|
|
711
|
-
_element4.dispatchEvent(invalidEvent);
|
|
124
|
+
function useFieldList(options) {
|
|
125
|
+
var _context$initialValue;
|
|
126
|
+
var subjectRef = useSubjectRef({
|
|
127
|
+
initialValue: {
|
|
128
|
+
name: [options.name]
|
|
712
129
|
}
|
|
130
|
+
});
|
|
131
|
+
var form = useFormStore(options.formId, options.context);
|
|
132
|
+
var context = useFormContext(form, subjectRef);
|
|
133
|
+
var initialValue = (_context$initialValue = context.initialValue[options.name]) !== null && _context$initialValue !== void 0 ? _context$initialValue : [];
|
|
134
|
+
if (!Array.isArray(initialValue)) {
|
|
135
|
+
throw new Error('The initial value at the given name is not a list');
|
|
713
136
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
137
|
+
return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(options.formId, context, {
|
|
138
|
+
name: options.name,
|
|
139
|
+
key: index,
|
|
140
|
+
subjectRef
|
|
141
|
+
}));
|
|
717
142
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
143
|
+
function useField(options) {
|
|
144
|
+
var subjectRef = useSubjectRef();
|
|
145
|
+
var form = useFormStore(options.formId, options.context);
|
|
146
|
+
var context = useFormContext(form, subjectRef);
|
|
147
|
+
var metadata = getFieldMetadata(options.formId, context, {
|
|
148
|
+
name: options.name,
|
|
149
|
+
subjectRef
|
|
150
|
+
});
|
|
151
|
+
return metadata;
|
|
726
152
|
}
|
|
727
153
|
|
|
728
|
-
export {
|
|
154
|
+
export { useField, useFieldList, useFieldset, useForm, useFormId, useFormMetadata, useSafeLayoutEffect };
|