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