@conform-to/react 0.6.1 → 0.6.3-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/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 _config$lastSubmissio, _config$ref, _ref2, _config$lastSubmissio2;
132
+ var _ref, _config$lastSubmissio2;
17
133
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
18
- var configRef = react.useRef(config);
19
- var formRef = react.useRef(null);
20
- var [lastSubmission, setLastSubmission] = react.useState((_config$lastSubmissio = config.lastSubmission) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : null);
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
- if (!config.lastSubmission) {
23
- return [];
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 Object.entries(submission.error).reduce((result, _ref) => {
34
- var [name, message] = _ref;
35
- if (name !== '' && (scope === null || scope === name)) {
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: (_ref2 = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref2 !== void 0 ? _ref2 : config.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
- var form = ref.current;
71
- if (!form || !lastSubmission) {
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
- } = formConfig;
103
- if (!form || !dom.isFieldElement(field) || field.form !== form) {
167
+ } = configRef.current;
168
+ if (!form || !dom.isFocusableFormControl(field) || field.form !== form || !field.name) {
104
169
  return;
105
170
  }
106
- if (field.dataset.conformTouched ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
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 !== dom.FORM_ERROR_ELEMENT_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 field of form.elements) {
129
- if (dom.isFieldElement(field)) {
130
- delete field.dataset.conformTouched;
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 getSubmission = (_config$onValidate = config.onValidate) !== null && _config$onValidate !== void 0 ? _config$onValidate : context => dom.parse(context.formData);
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
- if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && Object.entries(submission.error).some(_ref3 => {
170
- var [, message] = _ref3;
171
- return message !== '' && ![].concat(message).includes(dom.VALIDATION_UNDEFINED);
172
- }) || typeof config.onValidate !== 'undefined' && (submission.intent.startsWith('validate') || submission.intent.startsWith('list')) && Object.entries(submission.error).every(_ref4 => {
173
- var [, message] = _ref4;
174
- return ![].concat(message).includes(dom.VALIDATION_UNDEFINED);
175
- })) {
176
- var listCommand = dom.parseListCommand(submission.intent);
177
- if (listCommand) {
178
- form.dispatchEvent(new CustomEvent('conform/list', {
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, _rollupPluginBabelHelpers.objectSpread2({
246
+ (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
187
247
  formData,
188
- submission
189
- }, dom.getFormAttributes(form, submitter)));
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 configRef = react.useRef(config);
215
- var [error, setError] = react.useState(() => {
216
- var initialError = config === null || config === void 0 ? void 0 : config.initialError;
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, _ref5) => {
291
- var [name, message] = _ref5;
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;
@@ -297,6 +303,7 @@ function useFieldset(ref, config) {
297
303
  }, {});
298
304
  var field = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, constraint), {}, {
299
305
  name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
306
+ // @ts-expect-error The FieldValue type might need a rework
300
307
  defaultValue: (_fieldsetConfig$defau = fieldsetConfig.defaultValue) === null || _fieldsetConfig$defau === void 0 ? void 0 : _fieldsetConfig$defau[key],
301
308
  initialError,
302
309
  error: errors === null || errors === void 0 ? void 0 : errors[0],
@@ -314,57 +321,22 @@ function useFieldset(ref, config) {
314
321
  }
315
322
 
316
323
  /**
317
- * Returns a list of key and config, with a group of helpers
318
- * configuring buttons for list manipulation
324
+ * Returns a list of key and field config.
319
325
  *
320
326
  * @see https://conform.guide/api/react#usefieldlist
321
327
  */
322
328
  function useFieldList(ref, config) {
323
- var configRef = react.useRef(config);
324
- var [error, setError] = react.useState(() => {
325
- var initialError = [];
326
- for (var [name, message] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) {
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;
329
+ var configRef = useConfigRef(config);
330
+ var [error, setError] = useFormError(ref, {
331
+ initialError: config.initialError,
332
+ name: config.name
334
333
  });
335
334
  var [entries, setEntries] = react.useState(() => {
336
335
  var _config$defaultValue;
337
336
  return Object.entries((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : [undefined]);
338
337
  });
339
- useSafeLayoutEffect(() => {
340
- configRef.current = config;
341
- });
342
338
  react.useEffect(() => {
343
- var invalidHandler = event => {
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 => {
339
+ var conformHandler = event => {
368
340
  var form = dom.getFormElement(ref.current);
369
341
  if (!form || event.target !== form) {
370
342
  return;
@@ -393,20 +365,29 @@ function useFieldList(ref, config) {
393
365
  }
394
366
  });
395
367
  setError(error => {
368
+ var errorList = [];
369
+ for (var [key, messages] of Object.entries(error)) {
370
+ if (typeof key === 'number') {
371
+ errorList[key] = messages;
372
+ }
373
+ }
396
374
  switch (command.type) {
397
375
  case 'append':
398
376
  case 'prepend':
399
377
  case 'replace':
400
- return dom.updateList([...error], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
378
+ errorList = dom.updateList(errorList, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
401
379
  payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
402
380
  defaultValue: undefined
403
381
  })
404
382
  }));
383
+ break;
405
384
  default:
406
385
  {
407
- return dom.updateList([...error], command);
386
+ errorList = dom.updateList(errorList, command);
387
+ break;
408
388
  }
409
389
  }
390
+ return Object.assign({}, errorList);
410
391
  });
411
392
  };
412
393
  var resetHandler = event => {
@@ -416,26 +397,23 @@ function useFieldList(ref, config) {
416
397
  return;
417
398
  }
418
399
  setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : [undefined]));
419
- setError([]);
420
400
  };
421
401
 
422
- // @ts-expect-error Custom event: conform/list
423
- document.addEventListener('conform/list', listHandler, true);
424
- document.addEventListener('invalid', invalidHandler, true);
402
+ // @ts-expect-error Custom event: conform
403
+ document.addEventListener('conform', conformHandler, true);
425
404
  document.addEventListener('reset', resetHandler);
426
405
  return () => {
427
- // @ts-expect-error Custom event: conform/list
428
- document.removeEventListener('conform/list', listHandler, true);
429
- document.removeEventListener('invalid', invalidHandler, true);
406
+ // @ts-expect-error Custom event: conform
407
+ document.removeEventListener('conform', conformHandler, true);
430
408
  document.removeEventListener('reset', resetHandler);
431
409
  };
432
- }, [ref]);
433
- return entries.map((_ref6, index) => {
434
- var _config$initialError2, _config$defaultValue2;
435
- var [key, defaultValue] = _ref6;
410
+ }, [ref, configRef, setError]);
411
+ return entries.map((_ref4, index) => {
412
+ var _config$initialError, _config$defaultValue2;
413
+ var [key, defaultValue] = _ref4;
436
414
  var errors = error[index];
437
- var initialError = Object.entries((_config$initialError2 = config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {}).reduce((result, _ref7) => {
438
- var [name, message] = _ref7;
415
+ var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref5) => {
416
+ var [name, message] = _ref5;
439
417
  var [field, ...paths] = dom.getPaths(name);
440
418
  if (field === index) {
441
419
  result[dom.getName(paths)] = message;
@@ -497,13 +475,10 @@ function setNativeValue(element, value) {
497
475
  var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect;
498
476
  function useInputEvent(options) {
499
477
  var ref = react.useRef(null);
500
- var optionsRef = react.useRef(options);
478
+ var optionsRef = useConfigRef(options);
501
479
  var changeDispatched = react.useRef(false);
502
480
  var focusDispatched = react.useRef(false);
503
481
  var blurDispatched = react.useRef(false);
504
- useSafeLayoutEffect(() => {
505
- optionsRef.current = options;
506
- });
507
482
  useSafeLayoutEffect(() => {
508
483
  var getInputElement = () => {
509
484
  var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;
@@ -632,11 +607,160 @@ function useInputEvent(options) {
632
607
  blurDispatched.current = false;
633
608
  }
634
609
  };
635
- }, []);
610
+ }, [optionsRef]);
636
611
  return [ref, control];
637
612
  }
613
+ var VALIDATION_UNDEFINED = '__undefined__';
614
+ var VALIDATION_SKIPPED = '__skipped__';
615
+ var FORM_ERROR_ELEMENT_NAME = '__form__';
616
+
617
+ /**
618
+ * Validate the form with the Constraint Validation API
619
+ * @see https://conform.guide/api/react#validateconstraint
620
+ */
621
+ function validateConstraint(options) {
622
+ var _options$formData, _options$formatMessag;
623
+ var formData = (_options$formData = options === null || options === void 0 ? void 0 : options.formData) !== null && _options$formData !== void 0 ? _options$formData : new FormData(options.form);
624
+ var getDefaultErrors = (validity, result) => {
625
+ var errors = [];
626
+ if (validity.valueMissing) errors.push('required');
627
+ if (validity.typeMismatch || validity.badInput) errors.push('type');
628
+ if (validity.tooShort) errors.push('minLength');
629
+ if (validity.rangeUnderflow) errors.push('min');
630
+ if (validity.stepMismatch) errors.push('step');
631
+ if (validity.tooLong) errors.push('maxLength');
632
+ if (validity.rangeOverflow) errors.push('max');
633
+ if (validity.patternMismatch) errors.push('pattern');
634
+ for (var [constraintName, valid] of Object.entries(result)) {
635
+ if (!valid) {
636
+ errors.push(constraintName);
637
+ }
638
+ }
639
+ return errors;
640
+ };
641
+ var formatMessages = (_options$formatMessag = options === null || options === void 0 ? void 0 : options.formatMessages) !== null && _options$formatMessag !== void 0 ? _options$formatMessag : _ref6 => {
642
+ var {
643
+ defaultErrors
644
+ } = _ref6;
645
+ return defaultErrors;
646
+ };
647
+ return dom.parse(formData, {
648
+ resolve(payload, intent) {
649
+ var error = {};
650
+ var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
651
+ var _loop = function _loop(element) {
652
+ if (dom.isFieldElement(element)) {
653
+ var _options$acceptMultip, _options$acceptMultip2;
654
+ var name = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
655
+ var constraint = Object.entries(element.dataset).reduce((result, _ref7) => {
656
+ var [name, attributeValue = ''] = _ref7;
657
+ if (constraintPattern.test(name)) {
658
+ var _options$constraint;
659
+ var constraintName = name.slice(10).toLowerCase();
660
+ var _validate = (_options$constraint = options.constraint) === null || _options$constraint === void 0 ? void 0 : _options$constraint[constraintName];
661
+ if (typeof _validate === 'function') {
662
+ result[constraintName] = _validate(element.value, {
663
+ formData,
664
+ attributeValue
665
+ });
666
+ } else {
667
+ console.warn("Found an \"".concat(constraintName, "\" constraint with undefined definition; Please specify it on the validateConstraint API."));
668
+ }
669
+ }
670
+ return result;
671
+ }, {});
672
+ var errors = formatMessages({
673
+ name,
674
+ validity: element.validity,
675
+ constraint,
676
+ defaultErrors: getDefaultErrors(element.validity, constraint)
677
+ });
678
+ 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, {
679
+ name,
680
+ payload,
681
+ intent
682
+ })) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
683
+ if (errors.length > 0) {
684
+ error[name] = shouldAcceptMultipleErrors ? errors : errors[0];
685
+ }
686
+ }
687
+ };
688
+ for (var element of options.form.elements) {
689
+ _loop(element);
690
+ }
691
+ return {
692
+ error
693
+ };
694
+ }
695
+ });
696
+ }
697
+ function reportSubmission(form, submission) {
698
+ for (var [name, message] of Object.entries(submission.error)) {
699
+ // There is no need to create a placeholder button if all we want is to reset the error
700
+ if (message === '') {
701
+ continue;
702
+ }
703
+
704
+ // We can't use empty string as button name
705
+ // As `form.element.namedItem('')` will always returns null
706
+ var elementName = name ? name : FORM_ERROR_ELEMENT_NAME;
707
+ var item = form.elements.namedItem(elementName);
708
+ if (item instanceof RadioNodeList) {
709
+ for (var field of item) {
710
+ if (field.type !== 'radio') {
711
+ console.warn('Repeated field name is not supported.');
712
+ continue;
713
+ }
714
+ }
715
+ }
716
+ if (item === null) {
717
+ // Create placeholder button to keep the error without contributing to the form data
718
+ var button = document.createElement('button');
719
+ button.name = elementName;
720
+ button.hidden = true;
721
+ button.dataset.conformTouched = 'true';
722
+ form.appendChild(button);
723
+ }
724
+ }
725
+ var scope = dom.getScope(submission.intent);
726
+ for (var element of dom.getFormControls(form)) {
727
+ var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
728
+ var messages = normalizeError(submission.error[_elementName]);
729
+ if (scope === null || scope === _elementName) {
730
+ element.dataset.conformTouched = 'true';
731
+ }
732
+ if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
733
+ var invalidEvent = new Event('invalid', {
734
+ cancelable: true
735
+ });
736
+ element.setCustomValidity(dom.getValidationMessage(messages));
737
+ element.dispatchEvent(invalidEvent);
738
+ }
739
+ }
740
+ if (dom.isSubmitting(submission.intent) || isFocusedOnIntentButton(form, submission.intent)) {
741
+ if (scope) {
742
+ dom.focusFormControl(form, scope);
743
+ } else {
744
+ dom.focusFirstInvalidControl(form);
745
+ }
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Check if the current focus is on a intent button.
751
+ */
752
+ function isFocusedOnIntentButton(form, intent) {
753
+ var element = document.activeElement;
754
+ return dom.isFieldElement(element) && element.type === 'submit' && element.form === form && element.name === dom.INTENT && element.value === intent;
755
+ }
638
756
 
757
+ exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
758
+ exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
759
+ exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
760
+ exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
761
+ exports.reportSubmission = reportSubmission;
639
762
  exports.useFieldList = useFieldList;
640
763
  exports.useFieldset = useFieldset;
641
764
  exports.useForm = useForm;
642
765
  exports.useInputEvent = useInputEvent;
766
+ exports.validateConstraint = validateConstraint;