@conform-to/react 0.6.1 → 0.6.2

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