@conform-to/react 0.7.4 → 0.8.0-pre.1

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/README.md CHANGED
@@ -373,7 +373,6 @@ function Example() {
373
373
  <input
374
374
  {...conform.input(title, {
375
375
  type: 'text',
376
- ariaAttributes: true,
377
376
  })}
378
377
  />
379
378
  </form>
@@ -393,16 +392,16 @@ import { parse } from '@conform-to/react';
393
392
  const formData = new FormData();
394
393
  const submission = parse(formData, {
395
394
  resolve({ email, password }) {
396
- const error: Record<string, string> = {};
395
+ const error: Record<string, string[]> = {};
397
396
 
398
397
  if (typeof email !== 'string') {
399
- error.email = 'Email is required';
398
+ error.email = ['Email is required'];
400
399
  } else if (!/^[^@]+@[^@]+$/.test(email)) {
401
- error.email = 'Email is invalid';
400
+ error.email = ['Email is invalid'];
402
401
  }
403
402
 
404
403
  if (typeof password !== 'string') {
405
- error.password = 'Password is required';
404
+ error.password = ['Password is required'];
406
405
  }
407
406
 
408
407
  if (error.email || error.password) {
package/helpers.d.ts CHANGED
@@ -38,10 +38,10 @@ interface TextareaProps extends FormControlProps {
38
38
  defaultValue?: string;
39
39
  }
40
40
  type BaseOptions = {
41
- ariaAttributes?: false;
42
- } | {
43
- ariaAttributes: true;
41
+ ariaAttributes?: true;
44
42
  description?: boolean;
43
+ } | {
44
+ ariaAttributes: false;
45
45
  };
46
46
  type ControlOptions = BaseOptions & {
47
47
  hidden?: boolean;
@@ -64,5 +64,5 @@ export declare function input<Schema extends File | File[]>(config: FieldConfig<
64
64
  }): InputProps<Schema>;
65
65
  export declare function select<Schema extends Primitive | Primitive[] | undefined | unknown>(config: FieldConfig<Schema>, options?: ControlOptions): SelectProps;
66
66
  export declare function textarea<Schema extends Primitive | undefined | unknown>(config: FieldConfig<Schema>, options?: ControlOptions): TextareaProps;
67
- export declare function fieldset<Schema extends Record<string, any> | undefined | unknown>(config: FieldConfig<Schema>, options?: BaseOptions): FormControlProps;
67
+ export declare function fieldset<Schema extends Record<string, unknown> | undefined | unknown>(config: FieldConfig<Schema>, options?: BaseOptions): FormControlProps;
68
68
  export { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED };
package/helpers.js CHANGED
@@ -17,14 +17,16 @@ function cleanup(props) {
17
17
  }
18
18
  return props;
19
19
  }
20
- function getFormElementProps(config, options) {
21
- var _config$error, _config$error2;
20
+ function getFormElementProps(config) {
21
+ var _options$ariaAttribut, _config$error, _config$error2;
22
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
23
+ var hasAriaAttributes = (_options$ariaAttribut = options.ariaAttributes) !== null && _options$ariaAttribut !== void 0 ? _options$ariaAttribut : true;
22
24
  return cleanup({
23
25
  id: config.id,
24
26
  name: config.name,
25
27
  form: config.form,
26
- 'aria-invalid': options !== null && options !== void 0 && options.ariaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
27
- 'aria-describedby': options !== null && options !== void 0 && options.ariaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options !== null && options !== void 0 && options.description ? config.descriptionId : undefined].reduce((result, id) => {
28
+ 'aria-invalid': hasAriaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
29
+ 'aria-describedby': hasAriaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options.ariaAttributes !== false && options.description ? config.descriptionId : undefined].reduce((result, id) => {
28
30
  if (!result) {
29
31
  return id;
30
32
  }
package/helpers.mjs CHANGED
@@ -13,14 +13,16 @@ function cleanup(props) {
13
13
  }
14
14
  return props;
15
15
  }
16
- function getFormElementProps(config, options) {
17
- var _config$error, _config$error2;
16
+ function getFormElementProps(config) {
17
+ var _options$ariaAttribut, _config$error, _config$error2;
18
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
19
+ var hasAriaAttributes = (_options$ariaAttribut = options.ariaAttributes) !== null && _options$ariaAttribut !== void 0 ? _options$ariaAttribut : true;
18
20
  return cleanup({
19
21
  id: config.id,
20
22
  name: config.name,
21
23
  form: config.form,
22
- 'aria-invalid': options !== null && options !== void 0 && options.ariaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
23
- 'aria-describedby': options !== null && options !== void 0 && options.ariaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options !== null && options !== void 0 && options.description ? config.descriptionId : undefined].reduce((result, id) => {
24
+ 'aria-invalid': hasAriaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined,
25
+ 'aria-describedby': hasAriaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options.ariaAttributes !== false && options.description ? config.descriptionId : undefined].reduce((result, id) => {
24
26
  if (!result) {
25
27
  return id;
26
28
  }
package/hooks.d.ts CHANGED
@@ -5,7 +5,7 @@ export interface FieldConfig<Schema> extends FieldConstraint<Schema> {
5
5
  id?: string;
6
6
  name: string;
7
7
  defaultValue?: FieldValue<Schema>;
8
- initialError?: Record<string, string | string[]>;
8
+ initialError?: Record<string, string[]>;
9
9
  form?: string;
10
10
  descriptionId?: string;
11
11
  errorId?: string;
@@ -26,6 +26,11 @@ type SubmissionResult = {
26
26
  payload: Submission['payload'] | null;
27
27
  error: Submission['error'];
28
28
  };
29
+ interface ReportOptions {
30
+ formError?: string[];
31
+ resetForm?: boolean;
32
+ }
33
+ export declare function report(submission: Submission, options?: ReportOptions): SubmissionResult;
29
34
  export interface FormConfig<Output extends Record<string, any>, Input extends Record<string, any> = Output> {
30
35
  /**
31
36
  * If the form id is provided, Id for label,
@@ -107,7 +112,7 @@ interface FormProps {
107
112
  interface Form {
108
113
  id?: string;
109
114
  errorId?: string;
110
- error: string;
115
+ error: string | undefined;
111
116
  errors: string[];
112
117
  ref: RefObject<HTMLFormElement>;
113
118
  props: FormProps;
@@ -137,7 +142,7 @@ export interface FieldsetConfig<Schema extends Record<string, any> | undefined>
137
142
  /**
138
143
  * An object describing the initial error of each field
139
144
  */
140
- initialError?: Record<string, string | string[]>;
145
+ initialError?: Record<string, string[]>;
141
146
  /**
142
147
  * An object describing the constraint of each field
143
148
  */
@@ -171,7 +176,6 @@ interface InputControl {
171
176
  focus: () => void;
172
177
  blur: () => void;
173
178
  }
174
- export declare function useEventListeners(type: string, ref: RefObject<FieldElement>): void;
175
179
  /**
176
180
  * Returns a ref object and a set of helpers that dispatch corresponding dom event.
177
181
  *
@@ -196,11 +200,6 @@ export declare function validateConstraint(options: {
196
200
  formData: FormData;
197
201
  attributeValue: string;
198
202
  }) => boolean>;
199
- acceptMultipleErrors?: ({ name, intent, payload, }: {
200
- name: string;
201
- intent: string;
202
- payload: Record<string, any>;
203
- }) => boolean;
204
203
  formatMessages?: ({ name, validity, constraint, defaultErrors, }: {
205
204
  name: string;
206
205
  validity: ValidityState;
@@ -208,6 +207,7 @@ export declare function validateConstraint(options: {
208
207
  defaultErrors: string[];
209
208
  }) => string[];
210
209
  }): Submission;
210
+ export declare function getUniqueKey(): string;
211
211
  export declare function reportSubmission(form: HTMLFormElement, submission: SubmissionResult): void;
212
212
  export declare function getScope(intent: ReturnType<typeof parseIntent>): string | null;
213
213
  export {};
package/hooks.js CHANGED
@@ -6,20 +6,21 @@ var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js
6
6
  var dom = require('@conform-to/dom');
7
7
  var react = require('react');
8
8
 
9
+ function report(submission, options) {
10
+ var _submission$error$;
11
+ return {
12
+ intent: submission.intent,
13
+ payload: options !== null && options !== void 0 && options.resetForm ? null : submission.payload,
14
+ error: options !== null && options !== void 0 && options.formError ? _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission.error), {}, {
15
+ '': options.formError.concat((_submission$error$ = submission.error['']) !== null && _submission$error$ !== void 0 ? _submission$error$ : [])
16
+ }) : submission.error
17
+ };
18
+ }
19
+
9
20
  /**
10
21
  * Properties to be applied to the form element
11
22
  */
12
23
 
13
- /**
14
- * Normalize error to an array of string.
15
- */
16
- function normalizeError(error) {
17
- if (!error) {
18
- // This treat both empty string and undefined as no error.
19
- return [];
20
- }
21
- return [].concat(error);
22
- }
23
24
  function useNoValidate(defaultNoValidate, validateBeforeHydrate) {
24
25
  var [noValidate, setNoValidate] = react.useState(defaultNoValidate || !validateBeforeHydrate);
25
26
  react.useEffect(() => {
@@ -78,42 +79,33 @@ function useFormError(ref, config) {
78
79
  }
79
80
  var result = {};
80
81
  for (var [name, message] of Object.entries(config.initialError)) {
81
- var paths = dom.getPaths(name);
82
- if (paths.length === 1) {
83
- result[paths[0]] = normalizeError(message);
82
+ var [path, ...restPaths] = dom.getPaths(name);
83
+ if (typeof path !== 'undefined' && restPaths.length === 0) {
84
+ result[path] = message;
84
85
  }
85
86
  }
86
87
  return result;
87
88
  });
88
89
  react.useEffect(() => {
89
90
  var handleInvalid = event => {
91
+ var _config$name;
90
92
  var form = dom.getFormElement(ref.current);
91
93
  var element = event.target;
92
- if (!dom.isFieldElement(element) || element.form !== form || !element.dataset.conformTouched) {
94
+ var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : '';
95
+ if (!dom.isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) {
93
96
  return;
94
97
  }
95
- var key = element.name;
96
- if (config.name) {
97
- var scopePaths = dom.getPaths(config.name);
98
- var fieldPaths = dom.getPaths(element.name);
99
- for (var i = 0; i <= scopePaths.length; i++) {
100
- var path = fieldPaths[i];
101
- if (i < scopePaths.length) {
102
- // Skip if the field is not in the scope
103
- if (path !== scopePaths[i]) {
104
- return;
105
- }
106
- } else {
107
- key = path;
108
- }
109
- }
98
+ var name = element.name.slice(prefix.length);
99
+ var [path, ...restPaths] = dom.getPaths(name);
100
+ if (typeof path === 'undefined' || restPaths.length > 0) {
101
+ return;
110
102
  }
111
103
  setError(prev => {
112
- if (element.validationMessage === dom.getValidationMessage(prev[key])) {
104
+ if (element.validationMessage === dom.getValidationMessage(prev[path])) {
113
105
  return prev;
114
106
  }
115
107
  return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
116
- [key]: dom.getErrors(element.validationMessage)
108
+ [path]: dom.getErrors(element.validationMessage)
117
109
  });
118
110
  });
119
111
  event.preventDefault();
@@ -141,31 +133,35 @@ function useFormError(ref, config) {
141
133
  * @see https://conform.guide/api/react#useform
142
134
  */
143
135
  function useForm() {
144
- var _config$lastSubmissio2, _config$lastSubmissio3;
136
+ var _config$lastSubmissio3, _config$lastSubmissio4;
145
137
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
146
138
  var configRef = useConfigRef(config);
147
139
  var ref = useFormRef(config.ref);
148
140
  var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
149
141
  var report = useFormReporter(ref, config.lastSubmission);
150
142
  var [errors, setErrors] = react.useState(() => {
151
- var _config$lastSubmissio;
152
- return normalizeError((_config$lastSubmissio = config.lastSubmission) === null || _config$lastSubmissio === void 0 ? void 0 : _config$lastSubmissio.error['']);
143
+ var _config$lastSubmissio, _config$lastSubmissio2;
144
+ return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : [];
153
145
  });
154
146
  var initialError = react.useMemo(() => {
147
+ var _submission$error$sco;
155
148
  var submission = config.lastSubmission;
156
149
  if (!submission) {
157
150
  return {};
158
151
  }
159
152
  var intent = dom.parseIntent(submission.intent);
160
153
  var scope = getScope(intent);
161
- return scope === null ? submission.error : {
162
- [scope]: submission.error[scope]
154
+ if (typeof scope !== 'string') {
155
+ return submission.error;
156
+ }
157
+ return {
158
+ [scope]: (_submission$error$sco = submission.error[scope]) !== null && _submission$error$sco !== void 0 ? _submission$error$sco : []
163
159
  };
164
160
  }, [config.lastSubmission]);
165
161
  // This payload from lastSubmission is only useful before hydration
166
162
  // After hydration, any new payload on lastSubmission will be ignored
167
163
  var [defaultValueFromLastSubmission, setDefaultValueFromLastSubmission] = react.useState( // @ts-expect-error defaultValue is not in Submission type
168
- (_config$lastSubmissio2 = (_config$lastSubmissio3 = config.lastSubmission) === null || _config$lastSubmissio3 === void 0 ? void 0 : _config$lastSubmissio3.payload) !== null && _config$lastSubmissio2 !== void 0 ? _config$lastSubmissio2 : null);
164
+ (_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null);
169
165
  var fieldset = useFieldset(ref, {
170
166
  defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue,
171
167
  initialError,
@@ -252,7 +248,7 @@ function useForm() {
252
248
  shouldServerValidate
253
249
  } = Object.entries(submission.error).reduce((result, _ref) => {
254
250
  var [, error] = _ref;
255
- for (var message of normalizeError(error)) {
251
+ for (var message of error) {
256
252
  if (message === dom.VALIDATION_UNDEFINED) {
257
253
  result.shouldServerValidate = true;
258
254
  } else if (message !== dom.VALIDATION_SKIPPED) {
@@ -393,7 +389,7 @@ function useFieldList(ref, config) {
393
389
  return dom.updateList(list, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, intent.payload), {}, {
394
390
  defaultValue: [
395
391
  // Generate a random key to avoid conflicts
396
- crypto.getRandomValues(new Uint32Array(1))[0].toString(36), intent.payload.defaultValue]
392
+ getUniqueKey(), intent.payload.defaultValue]
397
393
  }));
398
394
  default:
399
395
  return dom.updateList(list, intent.payload);
@@ -475,7 +471,6 @@ function useFieldList(ref, config) {
475
471
  * This basically makes it a no-op on server
476
472
  */
477
473
  var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect;
478
-
479
474
  /**
480
475
  * Returns a ref object and a set of helpers that dispatch corresponding dom event.
481
476
  *
@@ -641,12 +636,11 @@ function validateConstraint(options) {
641
636
  return defaultErrors;
642
637
  };
643
638
  return dom.parse(formData, {
644
- resolve(payload, intent) {
639
+ resolve() {
645
640
  var error = {};
646
641
  var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
647
642
  var _loop = function _loop(_element3) {
648
643
  if (dom.isFieldElement(_element3)) {
649
- var _options$acceptMultip, _options$acceptMultip2;
650
644
  var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : '';
651
645
  var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => {
652
646
  var [name, attributeValue = ''] = _ref6;
@@ -672,13 +666,8 @@ function validateConstraint(options) {
672
666
  constraint,
673
667
  defaultErrors: getDefaultErrors(_element3.validity, constraint)
674
668
  });
675
- var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 || (_options$acceptMultip2 = options.acceptMultipleErrors) === null || _options$acceptMultip2 === void 0 ? void 0 : _options$acceptMultip2.call(options, {
676
- name,
677
- payload,
678
- intent
679
- })) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
680
669
  if (errors.length > 0) {
681
- error[name] = shouldAcceptMultipleErrors ? errors : errors[0];
670
+ error[name] = errors;
682
671
  }
683
672
  }
684
673
  };
@@ -691,10 +680,17 @@ function validateConstraint(options) {
691
680
  }
692
681
  });
693
682
  }
683
+ function getUniqueKey() {
684
+ var [value] = crypto.getRandomValues(new Uint32Array(1));
685
+ if (!value) {
686
+ throw new Error('Fail to generate an unique key');
687
+ }
688
+ return value.toString(36);
689
+ }
694
690
  function reportSubmission(form, submission) {
695
691
  for (var [name, message] of Object.entries(submission.error)) {
696
692
  // There is no need to create a placeholder button if all we want is to reset the error
697
- if (message === '') {
693
+ if (message.length === 0) {
698
694
  continue;
699
695
  }
700
696
 
@@ -723,8 +719,9 @@ function reportSubmission(form, submission) {
723
719
  var intent = dom.parseIntent(submission.intent);
724
720
  var scope = getScope(intent);
725
721
  for (var _element4 of dom.getFormControls(form)) {
722
+ var _submission$error$_el;
726
723
  var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : '';
727
- var messages = normalizeError(submission.error[_elementName]);
724
+ var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : [];
728
725
  if (scope === null || scope === _elementName) {
729
726
  _element4.dataset.conformTouched = 'true';
730
727
  }
@@ -752,6 +749,8 @@ function getScope(intent) {
752
749
 
753
750
  exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
754
751
  exports.getScope = getScope;
752
+ exports.getUniqueKey = getUniqueKey;
753
+ exports.report = report;
755
754
  exports.reportSubmission = reportSubmission;
756
755
  exports.useFieldList = useFieldList;
757
756
  exports.useFieldset = useFieldset;
package/hooks.mjs CHANGED
@@ -2,20 +2,21 @@ import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHe
2
2
  import { parseIntent, getFormData, parse, VALIDATION_UNDEFINED, VALIDATION_SKIPPED, getFormAction, getFormEncType, getFormMethod, getPaths, getName, isFieldElement, getErrors, getFormControls, getFormElement, updateList, getValidationMessage, focusFirstInvalidControl, isFocusableFormControl, requestIntent, validate } from '@conform-to/dom';
3
3
  import { useState, useMemo, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
4
4
 
5
+ function report(submission, options) {
6
+ var _submission$error$;
7
+ return {
8
+ intent: submission.intent,
9
+ payload: options !== null && options !== void 0 && options.resetForm ? null : submission.payload,
10
+ error: options !== null && options !== void 0 && options.formError ? _objectSpread2(_objectSpread2({}, submission.error), {}, {
11
+ '': options.formError.concat((_submission$error$ = submission.error['']) !== null && _submission$error$ !== void 0 ? _submission$error$ : [])
12
+ }) : submission.error
13
+ };
14
+ }
15
+
5
16
  /**
6
17
  * Properties to be applied to the form element
7
18
  */
8
19
 
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
20
  function useNoValidate(defaultNoValidate, validateBeforeHydrate) {
20
21
  var [noValidate, setNoValidate] = useState(defaultNoValidate || !validateBeforeHydrate);
21
22
  useEffect(() => {
@@ -74,42 +75,33 @@ function useFormError(ref, config) {
74
75
  }
75
76
  var result = {};
76
77
  for (var [name, message] of Object.entries(config.initialError)) {
77
- var paths = getPaths(name);
78
- if (paths.length === 1) {
79
- result[paths[0]] = normalizeError(message);
78
+ var [path, ...restPaths] = getPaths(name);
79
+ if (typeof path !== 'undefined' && restPaths.length === 0) {
80
+ result[path] = message;
80
81
  }
81
82
  }
82
83
  return result;
83
84
  });
84
85
  useEffect(() => {
85
86
  var handleInvalid = event => {
87
+ var _config$name;
86
88
  var form = getFormElement(ref.current);
87
89
  var element = event.target;
88
- if (!isFieldElement(element) || element.form !== form || !element.dataset.conformTouched) {
90
+ var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : '';
91
+ if (!isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) {
89
92
  return;
90
93
  }
91
- var key = element.name;
92
- if (config.name) {
93
- var scopePaths = getPaths(config.name);
94
- var fieldPaths = getPaths(element.name);
95
- for (var i = 0; i <= scopePaths.length; i++) {
96
- var path = fieldPaths[i];
97
- if (i < scopePaths.length) {
98
- // Skip if the field is not in the scope
99
- if (path !== scopePaths[i]) {
100
- return;
101
- }
102
- } else {
103
- key = path;
104
- }
105
- }
94
+ var name = element.name.slice(prefix.length);
95
+ var [path, ...restPaths] = getPaths(name);
96
+ if (typeof path === 'undefined' || restPaths.length > 0) {
97
+ return;
106
98
  }
107
99
  setError(prev => {
108
- if (element.validationMessage === getValidationMessage(prev[key])) {
100
+ if (element.validationMessage === getValidationMessage(prev[path])) {
109
101
  return prev;
110
102
  }
111
103
  return _objectSpread2(_objectSpread2({}, prev), {}, {
112
- [key]: getErrors(element.validationMessage)
104
+ [path]: getErrors(element.validationMessage)
113
105
  });
114
106
  });
115
107
  event.preventDefault();
@@ -137,31 +129,35 @@ function useFormError(ref, config) {
137
129
  * @see https://conform.guide/api/react#useform
138
130
  */
139
131
  function useForm() {
140
- var _config$lastSubmissio2, _config$lastSubmissio3;
132
+ var _config$lastSubmissio3, _config$lastSubmissio4;
141
133
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
142
134
  var configRef = useConfigRef(config);
143
135
  var ref = useFormRef(config.ref);
144
136
  var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
145
137
  var report = useFormReporter(ref, config.lastSubmission);
146
138
  var [errors, setErrors] = useState(() => {
147
- var _config$lastSubmissio;
148
- return normalizeError((_config$lastSubmissio = config.lastSubmission) === null || _config$lastSubmissio === void 0 ? void 0 : _config$lastSubmissio.error['']);
139
+ var _config$lastSubmissio, _config$lastSubmissio2;
140
+ return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : [];
149
141
  });
150
142
  var initialError = useMemo(() => {
143
+ var _submission$error$sco;
151
144
  var submission = config.lastSubmission;
152
145
  if (!submission) {
153
146
  return {};
154
147
  }
155
148
  var intent = parseIntent(submission.intent);
156
149
  var scope = getScope(intent);
157
- return scope === null ? submission.error : {
158
- [scope]: submission.error[scope]
150
+ if (typeof scope !== 'string') {
151
+ return submission.error;
152
+ }
153
+ return {
154
+ [scope]: (_submission$error$sco = submission.error[scope]) !== null && _submission$error$sco !== void 0 ? _submission$error$sco : []
159
155
  };
160
156
  }, [config.lastSubmission]);
161
157
  // This payload from lastSubmission is only useful before hydration
162
158
  // After hydration, any new payload on lastSubmission will be ignored
163
159
  var [defaultValueFromLastSubmission, setDefaultValueFromLastSubmission] = useState( // @ts-expect-error defaultValue is not in Submission type
164
- (_config$lastSubmissio2 = (_config$lastSubmissio3 = config.lastSubmission) === null || _config$lastSubmissio3 === void 0 ? void 0 : _config$lastSubmissio3.payload) !== null && _config$lastSubmissio2 !== void 0 ? _config$lastSubmissio2 : null);
160
+ (_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null);
165
161
  var fieldset = useFieldset(ref, {
166
162
  defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue,
167
163
  initialError,
@@ -248,7 +244,7 @@ function useForm() {
248
244
  shouldServerValidate
249
245
  } = Object.entries(submission.error).reduce((result, _ref) => {
250
246
  var [, error] = _ref;
251
- for (var message of normalizeError(error)) {
247
+ for (var message of error) {
252
248
  if (message === VALIDATION_UNDEFINED) {
253
249
  result.shouldServerValidate = true;
254
250
  } else if (message !== VALIDATION_SKIPPED) {
@@ -389,7 +385,7 @@ function useFieldList(ref, config) {
389
385
  return updateList(list, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
390
386
  defaultValue: [
391
387
  // Generate a random key to avoid conflicts
392
- crypto.getRandomValues(new Uint32Array(1))[0].toString(36), intent.payload.defaultValue]
388
+ getUniqueKey(), intent.payload.defaultValue]
393
389
  }));
394
390
  default:
395
391
  return updateList(list, intent.payload);
@@ -471,7 +467,6 @@ function useFieldList(ref, config) {
471
467
  * This basically makes it a no-op on server
472
468
  */
473
469
  var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
474
-
475
470
  /**
476
471
  * Returns a ref object and a set of helpers that dispatch corresponding dom event.
477
472
  *
@@ -637,12 +632,11 @@ function validateConstraint(options) {
637
632
  return defaultErrors;
638
633
  };
639
634
  return parse(formData, {
640
- resolve(payload, intent) {
635
+ resolve() {
641
636
  var error = {};
642
637
  var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
643
638
  var _loop = function _loop(_element3) {
644
639
  if (isFieldElement(_element3)) {
645
- var _options$acceptMultip, _options$acceptMultip2;
646
640
  var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : '';
647
641
  var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => {
648
642
  var [name, attributeValue = ''] = _ref6;
@@ -668,13 +662,8 @@ function validateConstraint(options) {
668
662
  constraint,
669
663
  defaultErrors: getDefaultErrors(_element3.validity, constraint)
670
664
  });
671
- var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 || (_options$acceptMultip2 = options.acceptMultipleErrors) === null || _options$acceptMultip2 === void 0 ? void 0 : _options$acceptMultip2.call(options, {
672
- name,
673
- payload,
674
- intent
675
- })) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
676
665
  if (errors.length > 0) {
677
- error[name] = shouldAcceptMultipleErrors ? errors : errors[0];
666
+ error[name] = errors;
678
667
  }
679
668
  }
680
669
  };
@@ -687,10 +676,17 @@ function validateConstraint(options) {
687
676
  }
688
677
  });
689
678
  }
679
+ function getUniqueKey() {
680
+ var [value] = crypto.getRandomValues(new Uint32Array(1));
681
+ if (!value) {
682
+ throw new Error('Fail to generate an unique key');
683
+ }
684
+ return value.toString(36);
685
+ }
690
686
  function reportSubmission(form, submission) {
691
687
  for (var [name, message] of Object.entries(submission.error)) {
692
688
  // There is no need to create a placeholder button if all we want is to reset the error
693
- if (message === '') {
689
+ if (message.length === 0) {
694
690
  continue;
695
691
  }
696
692
 
@@ -719,8 +715,9 @@ function reportSubmission(form, submission) {
719
715
  var intent = parseIntent(submission.intent);
720
716
  var scope = getScope(intent);
721
717
  for (var _element4 of getFormControls(form)) {
718
+ var _submission$error$_el;
722
719
  var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : '';
723
- var messages = normalizeError(submission.error[_elementName]);
720
+ var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : [];
724
721
  if (scope === null || scope === _elementName) {
725
722
  _element4.dataset.conformTouched = 'true';
726
723
  }
@@ -746,4 +743,4 @@ function getScope(intent) {
746
743
  return null;
747
744
  }
748
745
 
749
- export { FORM_ERROR_ELEMENT_NAME, getScope, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
746
+ export { FORM_ERROR_ELEMENT_NAME, getScope, getUniqueKey, report, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
package/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { type FieldsetConstraint, type Submission, parse, list, validate, requestIntent, isFieldElement, } from '@conform-to/dom';
2
- export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, } from './hooks.js';
2
+ export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, report, } from './hooks.js';
3
3
  export * as conform from './helpers.js';
package/index.js CHANGED
@@ -28,6 +28,7 @@ Object.defineProperty(exports, 'validate', {
28
28
  enumerable: true,
29
29
  get: function () { return dom.validate; }
30
30
  });
31
+ exports.report = hooks.report;
31
32
  exports.useFieldList = hooks.useFieldList;
32
33
  exports.useFieldset = hooks.useFieldset;
33
34
  exports.useForm = hooks.useForm;
package/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  export { isFieldElement, list, parse, requestIntent, validate } from '@conform-to/dom';
2
- export { useFieldList, useFieldset, useForm, useInputEvent, validateConstraint } from './hooks.mjs';
2
+ export { report, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint } from './hooks.mjs';
3
3
  import * as helpers from './helpers.mjs';
4
4
  export { helpers as conform };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Conform view adapter for react",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "0.7.4",
6
+ "version": "0.8.0-pre.1",
7
7
  "main": "index.js",
8
8
  "module": "index.mjs",
9
9
  "types": "index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "url": "https://github.com/edmundhung/conform/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@conform-to/dom": "0.7.4"
33
+ "@conform-to/dom": "0.8.0-pre.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": ">=16.8"