@conform-to/react 0.4.1 → 0.5.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/helpers.d.ts CHANGED
@@ -1,10 +1,13 @@
1
- import { type FieldConfig, type Primitive } from '@conform-to/dom';
2
- import { type HTMLInputTypeAttribute } from 'react';
1
+ import type { FieldConfig } from '@conform-to/dom';
2
+ import type { HTMLInputTypeAttribute } from 'react';
3
3
  interface FieldProps {
4
+ id?: string;
4
5
  name: string;
5
6
  form?: string;
6
7
  required?: boolean;
7
8
  autoFocus?: boolean;
9
+ 'aria-invalid': boolean;
10
+ 'aria-describedby'?: string;
8
11
  }
9
12
  interface InputProps<Schema> extends FieldProps {
10
13
  type?: HTMLInputTypeAttribute;
@@ -28,10 +31,20 @@ interface TextareaProps extends FieldProps {
28
31
  maxLength?: number;
29
32
  defaultValue?: string;
30
33
  }
31
- export declare function input<Schema extends Primitive>(config: FieldConfig<Schema>, { type, value }?: {
32
- type?: HTMLInputTypeAttribute;
34
+ declare type InputOptions = {
35
+ type: 'checkbox' | 'radio';
33
36
  value?: string;
37
+ } | {
38
+ type: 'file';
39
+ value?: never;
40
+ } | {
41
+ type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden' | 'file'>;
42
+ value?: never;
43
+ };
44
+ export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: {
45
+ type: 'file';
34
46
  }): InputProps<Schema>;
35
- export declare function select<Schema extends Primitive | Array<Primitive>>(config: FieldConfig<Schema>): SelectProps;
36
- export declare function textarea<Schema extends Primitive>(config: FieldConfig<Schema>): TextareaProps;
47
+ export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
48
+ export declare function select<Schema>(config: FieldConfig<Schema>): SelectProps;
49
+ export declare function textarea<Schema>(config: FieldConfig<Schema>): TextareaProps;
37
50
  export {};
package/helpers.js CHANGED
@@ -3,13 +3,11 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  function input(config) {
6
- var {
7
- type,
8
- value
9
- } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
10
- var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
6
+ var _config$initialError;
7
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11
8
  var attributes = {
12
- type,
9
+ id: config.id,
10
+ type: options.type,
13
11
  name: config.name,
14
12
  form: config.form,
15
13
  required: config.required,
@@ -19,27 +17,33 @@ function input(config) {
19
17
  max: config.max,
20
18
  step: config.step,
21
19
  pattern: config.pattern,
22
- multiple: config.multiple
20
+ multiple: config.multiple,
21
+ 'aria-invalid': Boolean((_config$initialError = config.initialError) === null || _config$initialError === void 0 ? void 0 : _config$initialError.length),
22
+ 'aria-describedby': config.errorId
23
23
  };
24
24
  if (config.initialError && config.initialError.length > 0) {
25
25
  attributes.autoFocus = true;
26
26
  }
27
- if (isCheckboxOrRadio) {
28
- attributes.value = value !== null && value !== void 0 ? value : 'on';
27
+ if (options.type === 'checkbox' || options.type === 'radio') {
28
+ var _options$value;
29
+ attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
29
30
  attributes.defaultChecked = config.defaultValue === attributes.value;
30
- } else {
31
+ } else if (options.type !== 'file') {
31
32
  attributes.defaultValue = config.defaultValue;
32
33
  }
33
34
  return attributes;
34
35
  }
35
36
  function select(config) {
36
- var _config$defaultValue;
37
+ var _config$defaultValue, _config$initialError2;
37
38
  var attributes = {
39
+ id: config.id,
38
40
  name: config.name,
39
41
  form: config.form,
40
42
  defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
41
43
  required: config.required,
42
- multiple: config.multiple
44
+ multiple: config.multiple,
45
+ 'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
46
+ 'aria-describedby': config.errorId
43
47
  };
44
48
  if (config.initialError && config.initialError.length > 0) {
45
49
  attributes.autoFocus = true;
@@ -47,15 +51,18 @@ function select(config) {
47
51
  return attributes;
48
52
  }
49
53
  function textarea(config) {
50
- var _config$defaultValue2;
54
+ var _config$defaultValue2, _config$initialError3;
51
55
  var attributes = {
56
+ id: config.id,
52
57
  name: config.name,
53
58
  form: config.form,
54
59
  defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
55
60
  required: config.required,
56
61
  minLength: config.minLength,
57
62
  maxLength: config.maxLength,
58
- autoFocus: Boolean(config.initialError)
63
+ autoFocus: Boolean(config.initialError),
64
+ 'aria-invalid': Boolean((_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : _config$initialError3.length),
65
+ 'aria-describedby': config.errorId
59
66
  };
60
67
  if (config.initialError && config.initialError.length > 0) {
61
68
  attributes.autoFocus = true;
package/hooks.d.ts CHANGED
@@ -1,6 +1,11 @@
1
- import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type ListCommand, type Primitive, type Submission } from '@conform-to/dom';
1
+ import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type Primitive, type Submission } from '@conform-to/dom';
2
2
  import { type InputHTMLAttributes, type FormEvent, type RefObject } from 'react';
3
3
  export interface FormConfig<Schema extends Record<string, any>> {
4
+ /**
5
+ * If the form id is provided, Id for label,
6
+ * input and error elements will be derived.
7
+ */
8
+ id?: string;
4
9
  /**
5
10
  * Validation mode. Default to `client-only`.
6
11
  */
@@ -20,6 +25,10 @@ export interface FormConfig<Schema extends Record<string, any>> {
20
25
  * An object describing the state from the last submission
21
26
  */
22
27
  state?: Submission<Schema>;
28
+ /**
29
+ * An object describing the constraint of each field
30
+ */
31
+ constraint?: FieldsetConstraint<Schema>;
23
32
  /**
24
33
  * Enable native validation before hydation.
25
34
  *
@@ -53,10 +62,12 @@ export interface FormConfig<Schema extends Record<string, any>> {
53
62
  */
54
63
  interface FormProps {
55
64
  ref: RefObject<HTMLFormElement>;
65
+ id?: string;
56
66
  onSubmit: (event: FormEvent<HTMLFormElement>) => void;
57
67
  noValidate: boolean;
58
68
  }
59
69
  interface Form<Schema extends Record<string, any>> {
70
+ id?: string;
60
71
  ref: RefObject<HTMLFormElement>;
61
72
  error: string;
62
73
  props: FormProps;
@@ -66,9 +77,9 @@ interface Form<Schema extends Record<string, any>> {
66
77
  * Returns properties required to hook into form events.
67
78
  * Applied custom validation and define when error should be reported.
68
79
  *
69
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#useform
80
+ * @see https://conform.guide/api/react#useform
70
81
  */
71
- export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>;
82
+ export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): [Form<Schema>, Fieldset<Schema>];
72
83
  /**
73
84
  * All the information of the field, including state and config.
74
85
  */
@@ -107,38 +118,21 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
107
118
  /**
108
119
  * Returns all the information about the fieldset.
109
120
  *
110
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldset
121
+ * @see https://conform.guide/api/react#usefieldset
111
122
  */
112
123
  export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldsetConfig<Schema>): Fieldset<Schema>;
113
124
  export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Schema>): Fieldset<Schema>;
114
- interface CommandButtonProps {
115
- name?: string;
116
- value?: string;
117
- form?: string;
118
- formNoValidate: true;
119
- }
120
- declare type ListCommandPayload<Schema, Type extends ListCommand<FieldValue<Schema>>['type']> = Extract<ListCommand<FieldValue<Schema>>, {
121
- type: Type;
122
- }>['payload'];
123
125
  /**
124
126
  * Returns a list of key and config, with a group of helpers
125
127
  * configuring buttons for list manipulation
126
128
  *
127
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldlist
129
+ * @see https://conform.guide/api/react#usefieldlist
128
130
  */
129
- export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
130
- Array<{
131
- key: string;
132
- config: FieldConfig<Payload>;
133
- }>,
134
- {
135
- prepend(payload?: ListCommandPayload<Payload, 'prepend'>): CommandButtonProps;
136
- append(payload?: ListCommandPayload<Payload, 'append'>): CommandButtonProps;
137
- replace(payload: ListCommandPayload<Payload, 'replace'>): CommandButtonProps;
138
- remove(payload: ListCommandPayload<Payload, 'remove'>): CommandButtonProps;
139
- reorder(payload: ListCommandPayload<Payload, 'reorder'>): CommandButtonProps;
140
- }
141
- ];
131
+ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): Array<{
132
+ key: string;
133
+ error: string | undefined;
134
+ config: FieldConfig<Payload>;
135
+ }>;
142
136
  interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
143
137
  ref: RefObject<HTMLInputElement>;
144
138
  }
@@ -160,7 +154,7 @@ interface InputControl<Element extends {
160
154
  * This is particular useful when integrating dropdown and datepicker whichs
161
155
  * introduces custom input mode.
162
156
  *
163
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usecontrolledinput
157
+ * @see https://conform.guide/api/react#usecontrolledinput
164
158
  */
165
159
  export declare function useControlledInput<Element extends {
166
160
  focus: () => void;
package/hooks.js CHANGED
@@ -11,7 +11,7 @@ var helpers = require('./helpers.js');
11
11
  * Returns properties required to hook into form events.
12
12
  * Applied custom validation and define when error should be reported.
13
13
  *
14
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#useform
14
+ * @see https://conform.guide/api/react#useform
15
15
  */
16
16
  function useForm() {
17
17
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -25,17 +25,26 @@ function useForm() {
25
25
  })) !== null && _config$state$error$f !== void 0 ? _config$state$error$f : [];
26
26
  return message !== null && message !== void 0 ? message : '';
27
27
  });
28
- var [fieldsetConfig, setFieldsetConfig] = react.useState(() => {
29
- var _config$state$error2, _config$state2, _config$state$value, _config$state3;
30
- var error = (_config$state$error2 = (_config$state2 = config.state) === null || _config$state2 === void 0 ? void 0 : _config$state2.error) !== null && _config$state$error2 !== void 0 ? _config$state$error2 : [];
28
+ var [uncontrolledState, setUncontrolledState] = react.useState(() => {
29
+ var submission = config.state;
30
+ if (!submission) {
31
+ return {
32
+ defaultValue: config.defaultValue
33
+ };
34
+ }
31
35
  return {
32
- defaultValue: (_config$state$value = (_config$state3 = config.state) === null || _config$state3 === void 0 ? void 0 : _config$state3.value) !== null && _config$state$value !== void 0 ? _config$state$value : config.defaultValue,
33
- initialError: error.filter(_ref2 => {
36
+ defaultValue: submission.value,
37
+ initialError: submission.error.filter(_ref2 => {
34
38
  var [name] = _ref2;
35
- return name !== '' && dom.getSubmissionType(name) === null;
39
+ return name !== '' && dom.shouldValidate(submission, name);
36
40
  })
37
41
  };
38
42
  });
43
+ var fieldsetConfig = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, uncontrolledState), {}, {
44
+ constraint: config.constraint,
45
+ form: config.id
46
+ });
47
+ var fieldset = useFieldset(ref, fieldsetConfig);
39
48
  var [noValidate, setNoValidate] = react.useState(config.noValidate || !config.fallbackNative);
40
49
  react.useEffect(() => {
41
50
  configRef.current = config;
@@ -48,11 +57,7 @@ function useForm() {
48
57
  if (!form || !config.state) {
49
58
  return;
50
59
  }
51
- dom.setFormError(form, config.state);
52
- if (!form.reportValidity()) {
53
- dom.focusFirstInvalidField(form);
54
- }
55
- dom.requestSubmit(form);
60
+ dom.reportSubmission(form, config.state);
56
61
  }, [config.state]);
57
62
  react.useEffect(() => {
58
63
  // Revalidate the form when input value is changed
@@ -63,11 +68,8 @@ function useForm() {
63
68
  if (!form || !dom.isFieldElement(field) || field.form !== form) {
64
69
  return;
65
70
  }
66
- if (formConfig.initialReport === 'onChange') {
67
- field.dataset.conformTouched = 'true';
68
- }
69
- if (field.dataset.conformTouched) {
70
- dom.requestValidate(form, field.name);
71
+ if (field.dataset.conformTouched || formConfig.initialReport === 'onChange') {
72
+ dom.requestCommand(form, dom.validate(field.name));
71
73
  }
72
74
  };
73
75
  var handleBlur = event => {
@@ -78,14 +80,13 @@ function useForm() {
78
80
  return;
79
81
  }
80
82
  if (formConfig.initialReport === 'onBlur' && !field.dataset.conformTouched) {
81
- field.dataset.conformTouched = 'true';
82
- dom.requestValidate(form, field.name);
83
+ dom.requestCommand(form, dom.validate(field.name));
83
84
  }
84
85
  };
85
86
  var handleInvalid = event => {
86
87
  var form = dom.getFormElement(ref.current);
87
88
  var field = event.target;
88
- if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '') {
89
+ if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '__form__') {
89
90
  return;
90
91
  }
91
92
  event.preventDefault();
@@ -104,11 +105,12 @@ function useForm() {
104
105
  for (var field of form.elements) {
105
106
  if (dom.isFieldElement(field)) {
106
107
  delete field.dataset.conformTouched;
108
+ field.setAttribute('aria-invalid', 'false');
107
109
  field.setCustomValidity('');
108
110
  }
109
111
  }
110
112
  setError('');
111
- setFieldsetConfig({
113
+ setUncontrolledState({
112
114
  defaultValue: formConfig.defaultValue,
113
115
  initialError: []
114
116
  });
@@ -131,22 +133,18 @@ function useForm() {
131
133
  document.removeEventListener('reset', handleReset);
132
134
  };
133
135
  }, []);
134
- return {
136
+ var form = {
137
+ id: config.id,
135
138
  ref,
136
139
  error,
137
140
  props: {
138
141
  ref,
142
+ id: config.id,
139
143
  noValidate,
140
144
  onSubmit(event) {
141
145
  var form = event.currentTarget;
142
146
  var nativeEvent = event.nativeEvent;
143
147
  var submitter = nativeEvent.submitter;
144
- for (var element of form.elements) {
145
- if (dom.isFieldElement(element) && element.name === '' && element.willValidate) {
146
- setError(element.validationMessage);
147
- break;
148
- }
149
- }
150
148
 
151
149
  /**
152
150
  * It checks defaultPrevented to confirm if the submission is intentional
@@ -154,8 +152,7 @@ function useForm() {
154
152
  * event is captured and revalidate the form with new fields without triggering
155
153
  * a form submission at the same time.
156
154
  */
157
- if (!submitter || event.defaultPrevented) {
158
- event.preventDefault();
155
+ if (event.defaultPrevented) {
159
156
  return;
160
157
  }
161
158
  try {
@@ -176,29 +173,15 @@ function useForm() {
176
173
  *
177
174
  * This is mainly used to showcase the constraint validation API.
178
175
  */
179
- dom.setFormError(form, {
180
- type: 'submit',
181
- value: {},
182
- error: []
183
- });
184
- for (var _element of form.elements) {
185
- if (dom.isFieldElement(_element) && _element.willValidate) {
186
- submission.error.push([_element.name, _element.validationMessage]);
176
+ for (var element of form.elements) {
177
+ if (dom.isFieldElement(element) && element.willValidate) {
178
+ element.setCustomValidity('');
179
+ submission.error.push([element.name, element.validationMessage]);
187
180
  }
188
181
  }
189
182
  }
190
183
  }
191
-
192
- // Touch all fields only if the submitter is not a command button
193
- if (submission.type === 'submit') {
194
- for (var field of form.elements) {
195
- if (dom.isFieldElement(field)) {
196
- // Mark the field as touched
197
- field.dataset.conformTouched = 'true';
198
- }
199
- }
200
- }
201
- if (!config.noValidate && !submitter.formNoValidate && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
184
+ if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
202
185
  event.preventDefault();
203
186
  } else {
204
187
  var _config$onSubmit;
@@ -208,10 +191,7 @@ function useForm() {
208
191
  });
209
192
  }
210
193
  if (event.defaultPrevented) {
211
- dom.setFormError(form, submission);
212
- if (!form.reportValidity()) {
213
- dom.focusFirstInvalidField(form);
214
- }
194
+ dom.reportSubmission(form, submission);
215
195
  }
216
196
  } catch (e) {
217
197
  console.warn(e);
@@ -220,6 +200,7 @@ function useForm() {
220
200
  },
221
201
  config: fieldsetConfig
222
202
  };
203
+ return [form, fieldset];
223
204
  }
224
205
 
225
206
  /**
@@ -280,6 +261,10 @@ function useFieldset(ref, config) {
280
261
  // Update the error only if the field belongs to the fieldset
281
262
  if (typeof key === 'string' && paths.length === 0) {
282
263
  if (field.dataset.conformTouched) {
264
+ // Update the aria attribute only if it is set
265
+ if (field.getAttribute('aria-invalid')) {
266
+ field.setAttribute('aria-invalid', field.validationMessage !== '' ? 'true' : 'false');
267
+ }
283
268
  setError(prev => {
284
269
  var _prev$key;
285
270
  var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : '';
@@ -294,38 +279,6 @@ function useFieldset(ref, config) {
294
279
  event.preventDefault();
295
280
  }
296
281
  };
297
- var submitHandler = event => {
298
- var form = dom.getFormElement(ref.current);
299
- if (!form || event.target !== form) {
300
- return;
301
- }
302
-
303
- /**
304
- * Reset the error state of each field if its validity is changed.
305
- *
306
- * This is a workaround as no official way is provided to notify
307
- * when the validity of the field is changed from `invalid` to `valid`.
308
- */
309
- setError(prev => {
310
- var _configRef$current$na2;
311
- var next = prev;
312
- var fieldsetName = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
313
- for (var field of form.elements) {
314
- if (dom.isFieldElement(field) && field.name.startsWith(fieldsetName)) {
315
- var _next$key, _next;
316
- var key = fieldsetName ? field.name.slice(fieldsetName.length + 1) : field.name;
317
- var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
318
- var nextMessage = field.validationMessage;
319
- if (prevMessage !== '' && nextMessage === '') {
320
- next = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, next), {}, {
321
- [key]: ''
322
- });
323
- }
324
- }
325
- }
326
- return next;
327
- });
328
- };
329
282
  var resetHandler = event => {
330
283
  var _fieldsetConfig$defau;
331
284
  var form = dom.getFormElement(ref.current);
@@ -343,11 +296,9 @@ function useFieldset(ref, config) {
343
296
 
344
297
  // The invalid event does not bubble and so listening on the capturing pharse is needed
345
298
  document.addEventListener('invalid', invalidHandler, true);
346
- document.addEventListener('submit', submitHandler);
347
299
  document.addEventListener('reset', resetHandler);
348
300
  return () => {
349
301
  document.removeEventListener('invalid', invalidHandler, true);
350
- document.removeEventListener('submit', submitHandler);
351
302
  document.removeEventListener('reset', resetHandler);
352
303
  };
353
304
  }, [ref]);
@@ -368,21 +319,26 @@ function useFieldset(ref, config) {
368
319
  var field = {
369
320
  config: _rollupPluginBabelHelpers.objectSpread2({
370
321
  name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
371
- form: fieldsetConfig.form,
372
322
  defaultValue: uncontrolledState.defaultValue[key],
373
323
  initialError: uncontrolledState.initialError[key]
374
324
  }, constraint),
375
325
  error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : ''
376
326
  };
327
+ if (fieldsetConfig.form) {
328
+ field.config.form = fieldsetConfig.form;
329
+ field.config.id = "".concat(fieldsetConfig.form, "-").concat(field.config.name);
330
+ field.config.errorId = "".concat(field.config.id, "-error");
331
+ }
377
332
  return field;
378
333
  }
379
334
  });
380
335
  }
336
+
381
337
  /**
382
338
  * Returns a list of key and config, with a group of helpers
383
339
  * configuring buttons for list manipulation
384
340
  *
385
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldlist
341
+ * @see https://conform.guide/api/react#usefieldlist
386
342
  */
387
343
  function useFieldList(ref, config) {
388
344
  var configRef = react.useRef(config);
@@ -408,48 +364,40 @@ function useFieldList(ref, config) {
408
364
  initialError
409
365
  };
410
366
  });
367
+ var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => error === null || error === void 0 ? void 0 : error[0][1]));
411
368
  var [entries, setEntries] = react.useState(() => {
412
369
  var _config$defaultValue3;
413
370
  return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
414
371
  });
415
- var list = entries.map((_ref3, index) => {
416
- var [key, defaultValue] = _ref3;
417
- return {
418
- key,
419
- config: {
420
- name: "".concat(config.name, "[").concat(index, "]"),
421
- form: config.form,
422
- defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
423
- initialError: uncontrolledState.initialError[index]
424
- }
425
- };
426
- });
427
-
428
- /***
429
- * This use proxy to capture all information about the command and
430
- * have it encoded in the value.
431
- */
432
- var command = new Proxy({}, {
433
- get(_target, type) {
434
- return function () {
435
- var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
436
- return {
437
- name: 'conform/list',
438
- value: JSON.stringify({
439
- type,
440
- scope: config.name,
441
- payload
442
- }),
443
- form: config.form,
444
- formNoValidate: true
445
- };
446
- };
447
- }
448
- });
449
372
  react.useEffect(() => {
450
373
  configRef.current = config;
451
374
  });
452
375
  react.useEffect(() => {
376
+ var invalidHandler = event => {
377
+ var _configRef$current$na2;
378
+ var form = dom.getFormElement(ref.current);
379
+ var field = event.target;
380
+ var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
381
+ if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
382
+ return;
383
+ }
384
+ var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
385
+
386
+ // Update the error only if the field belongs to the fieldset
387
+ if (typeof index === 'number' && paths.length === 0) {
388
+ if (field.dataset.conformTouched) {
389
+ setError(prev => {
390
+ var _prev$index;
391
+ var prevMessage = (_prev$index = prev === null || prev === void 0 ? void 0 : prev[index]) !== null && _prev$index !== void 0 ? _prev$index : '';
392
+ if (prevMessage === field.validationMessage) {
393
+ return prev;
394
+ }
395
+ return [...prev.slice(0, index), field.validationMessage, ...prev.slice(index + 1)];
396
+ });
397
+ }
398
+ event.preventDefault();
399
+ }
400
+ };
453
401
  var submitHandler = event => {
454
402
  var form = dom.getFormElement(ref.current);
455
403
  if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
@@ -476,6 +424,22 @@ function useFieldList(ref, config) {
476
424
  }
477
425
  }
478
426
  });
427
+ setError(error => {
428
+ switch (command.type) {
429
+ case 'append':
430
+ case 'prepend':
431
+ case 'replace':
432
+ return dom.updateList([...error], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
433
+ payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
434
+ defaultValue: undefined
435
+ })
436
+ }));
437
+ default:
438
+ {
439
+ return dom.updateList([...error], command);
440
+ }
441
+ }
442
+ });
479
443
  event.preventDefault();
480
444
  };
481
445
  var resetHandler = event => {
@@ -490,24 +454,42 @@ function useFieldList(ref, config) {
490
454
  initialError: []
491
455
  });
492
456
  setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
457
+ setError([]);
493
458
  };
494
459
  document.addEventListener('submit', submitHandler, true);
460
+ document.addEventListener('invalid', invalidHandler, true);
495
461
  document.addEventListener('reset', resetHandler);
496
462
  return () => {
497
463
  document.removeEventListener('submit', submitHandler, true);
464
+ document.removeEventListener('invalid', invalidHandler, true);
498
465
  document.removeEventListener('reset', resetHandler);
499
466
  };
500
467
  }, [ref]);
501
- return [list,
502
- // @ts-expect-error proxy type
503
- command];
468
+ return entries.map((_ref3, index) => {
469
+ var [key, defaultValue] = _ref3;
470
+ var fieldConfig = {
471
+ name: "".concat(config.name, "[").concat(index, "]"),
472
+ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
473
+ initialError: uncontrolledState.initialError[index]
474
+ };
475
+ if (config.form) {
476
+ fieldConfig.form = config.form;
477
+ fieldConfig.id = "".concat(config.form, "-").concat(config.name);
478
+ fieldConfig.errorId = "".concat(fieldConfig.id, "-error");
479
+ }
480
+ return {
481
+ key,
482
+ error: error[index],
483
+ config: fieldConfig
484
+ };
485
+ });
504
486
  }
505
487
  /**
506
488
  * Returns the properties required to configure a shadow input for validation.
507
489
  * This is particular useful when integrating dropdown and datepicker whichs
508
490
  * introduces custom input mode.
509
491
  *
510
- * @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usecontrolledinput
492
+ * @see https://conform.guide/api/react#usecontrolledinput
511
493
  */
512
494
  function useControlledInput(config) {
513
495
  var _config$defaultValue4;
package/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { type FieldConfig, type FieldsetConstraint, type Submission, getFormElements, hasError, parse, shouldValidate, } from '@conform-to/dom';
1
+ export { type FieldConfig, type FieldsetConstraint, type Submission, getFormElements, hasError, list, validate, requestCommand, requestSubmit, parse, shouldValidate, } from '@conform-to/dom';
2
2
  export * from './hooks';
3
3
  export * as conform from './helpers';