@conform-to/react 0.4.1 → 0.5.0-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/README.md CHANGED
@@ -324,7 +324,7 @@ function ExampleForm() {
324
324
 
325
325
  ### useFieldList
326
326
 
327
- It returns a list of key and config, with helpers to configure command buttons with [list command](/docs/submission.md#list-command).
327
+ It returns a list of key, config and error, with helpers to configure [list command](/docs/submission.md#list-command) button.
328
328
 
329
329
  ```tsx
330
330
  import { useFieldset, useFieldList } from '@conform-to/react';
@@ -333,33 +333,24 @@ import { useRef } from 'react';
333
333
  /**
334
334
  * Consider the schema as follow:
335
335
  */
336
- type Book = {
337
- name: string;
338
- isbn: string;
339
- };
340
-
341
- type Collection = {
342
- books: Book[];
336
+ type Schema = {
337
+ list: string[];
343
338
  };
344
339
 
345
340
  function CollectionFieldset() {
346
- const ref = useRef();
347
- const { books } = useFieldset<Collection>(ref);
348
- const [bookList, command] = useFieldList(ref, books.config);
341
+ const ref = useRef<HTMLFieldsetElement>(null);
342
+ const fieldset = useFieldset<Collection>(ref);
343
+ const [list, command] = useFieldList(ref, fieldset.list.config);
349
344
 
350
345
  return (
351
346
  <fieldset ref={ref}>
352
- {bookList.map((book, index) => (
353
- <div key={book.key}>
354
- {/* To setup the fields */}
355
- <input
356
- name={`${book.config.name}.name`}
357
- defaultValue={book.config.defaultValue.name}
358
- />
359
- <input
360
- name={`${book.config.name}.isbn`}
361
- defaultValue={book.config.defaultValue.isbn}
362
- />
347
+ {list.map((item, index) => (
348
+ <div key={item.key}>
349
+ {/* Setup an input per item */}
350
+ <input {...conform.input(item.config)} />
351
+
352
+ {/* Error of each book */}
353
+ <span>{item.error}</span>
363
354
 
364
355
  {/* To setup a delete button */}
365
356
  <button {...command.remove({ index })}>Delete</button>
@@ -367,79 +358,86 @@ function CollectionFieldset() {
367
358
  ))}
368
359
 
369
360
  {/* To setup a button that can append a new row with optional default value */}
370
- <button {...command.append({ defaultValue: { name: '', isbn: '' } })}>
371
- add
372
- </button>
361
+ <button {...command.append({ defaultValue: '' })}>add</button>
373
362
  </fieldset>
374
363
  );
375
364
  }
376
365
  ```
377
366
 
378
- This hook can also be used in combination with `useFieldset` to distribute the config:
367
+ This hook can also be used in combination with `useFieldset` for nested list:
379
368
 
380
369
  ```tsx
381
- import { useForm, useFieldset, useFieldList } from '@conform-to/react';
370
+ import {
371
+ type FieldConfig,
372
+ useForm,
373
+ useFieldset,
374
+ useFieldList,
375
+ } from '@conform-to/react';
382
376
  import { useRef } from 'react';
383
377
 
378
+ /**
379
+ * Consider the schema as follow:
380
+ */
381
+ type Schema = {
382
+ list: Array<Item>;
383
+ };
384
+
385
+ type Item = {
386
+ title: string;
387
+ description: string;
388
+ };
389
+
384
390
  function CollectionFieldset() {
385
- const ref = useRef();
386
- const { books } = useFieldset<Collection>(ref);
387
- const [bookList, command] = useFieldList(ref, books.config);
391
+ const ref = useRef<HTMLFieldsetElement>(null);
392
+ const fieldset = useFieldset<Collection>(ref);
393
+ const [list, command] = useFieldList(ref, fieldset.list.config);
388
394
 
389
395
  return (
390
396
  <fieldset ref={ref}>
391
- {bookList.map((book, index) => (
392
- <div key={book.key}>
393
- {/* `book.config` is a FieldConfig object similar to `books` */}
394
- <BookFieldset {...book.config} />
395
-
396
- {/* To setup a delete button */}
397
- <button {...command.remove({ index })}>Delete</button>
397
+ {list.map((item, index) => (
398
+ <div key={item.key}>
399
+ {/* Pass the item config to another fieldset*/}
400
+ <ItemFieldset {...item.config} />
398
401
  </div>
399
402
  ))}
400
-
401
- {/* To setup a button that can append a new row */}
402
- <button {...command.append()}>add</button>
403
403
  </fieldset>
404
404
  );
405
405
  }
406
406
 
407
- /**
408
- * This is basically the BookFieldset component from
409
- * the `useFieldset` example, but setting all the
410
- * options with the component props instead
411
- */
412
- function BookFieldset({ name, form, defaultValue, error }) {
413
- const ref = useRef();
414
- const { name, isbn } = useFieldset(ref, {
415
- name,
416
- form,
417
- defaultValue,
418
- error,
419
- });
407
+ function ItemFieldset(config: FieldConfig<Item>) {
408
+ const ref = useRef<HTMLFieldsetElement>(null);
409
+ const { title, description } = useFieldset(ref, config);
420
410
 
421
- return <fieldset ref={ref}>{/* ... */}</fieldset>;
411
+ return (
412
+ <fieldset ref={ref}>
413
+ <input {...conform.input(title.config)} />
414
+ <span>{title.error}</span>
415
+
416
+ <input {...conform.input(description.config)} />
417
+ <span>{description.error}</span>
418
+ </fieldset>
419
+ );
422
420
  }
423
421
  ```
424
422
 
425
423
  <details>
426
- <summary>What can I do with `controls`?</summary>
424
+ <summary>What can I do with `command`?</summary>
427
425
 
428
426
  ```tsx
429
427
  // To append a new row with optional defaultValue
430
- <button {...controls.append({ defaultValue })}>Append</button>;
428
+ <button {...command.append({ defaultValue })}>Append</button>;
431
429
 
432
430
  // To prepend a new row with optional defaultValue
433
- <button {...controls.prepend({ defaultValue })}>Prepend</button>;
431
+ <button {...command.prepend({ defaultValue })}>Prepend</button>;
434
432
 
435
433
  // To remove a row by index
436
- <button {...controls.remove({ index })}>Remove</button>;
434
+ <button {...command.remove({ index })}>Remove</button>;
437
435
 
438
436
  // To replace a row with another defaultValue
439
- <button {...controls.replace({ index, defaultValue })}>Replace</button>;
437
+ <button {...command.replace({ index, defaultValue })}>Replace</button>;
440
438
 
441
439
  // To reorder a particular row to an another index
442
- <button {...controls.reorder({ from, to })}>Reorder</button>;
440
+ <button {...command.reorder({ from, to })}>Reorder</button>;
443
441
  ```
444
442
 
445
443
  </details>
package/helpers.d.ts CHANGED
@@ -1,5 +1,5 @@
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
4
  name: string;
5
5
  form?: string;
@@ -28,10 +28,20 @@ interface TextareaProps extends FieldProps {
28
28
  maxLength?: number;
29
29
  defaultValue?: string;
30
30
  }
31
- export declare function input<Schema extends Primitive>(config: FieldConfig<Schema>, { type, value }?: {
32
- type?: HTMLInputTypeAttribute;
31
+ declare type InputOptions = {
32
+ type: 'checkbox' | 'radio';
33
33
  value?: string;
34
+ } | {
35
+ type: 'file';
36
+ value?: never;
37
+ } | {
38
+ type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden' | 'file'>;
39
+ value?: never;
40
+ };
41
+ export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: {
42
+ type: 'file';
34
43
  }): 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;
44
+ export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
45
+ export declare function select<Schema>(config: FieldConfig<Schema>): SelectProps;
46
+ export declare function textarea<Schema>(config: FieldConfig<Schema>): TextareaProps;
37
47
  export {};
package/helpers.js CHANGED
@@ -3,13 +3,9 @@
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 options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11
7
  var attributes = {
12
- type,
8
+ type: options.type,
13
9
  name: config.name,
14
10
  form: config.form,
15
11
  required: config.required,
@@ -24,10 +20,11 @@ function input(config) {
24
20
  if (config.initialError && config.initialError.length > 0) {
25
21
  attributes.autoFocus = true;
26
22
  }
27
- if (isCheckboxOrRadio) {
28
- attributes.value = value !== null && value !== void 0 ? value : 'on';
23
+ if (options.type === 'checkbox' || options.type === 'radio') {
24
+ var _options$value;
25
+ attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
29
26
  attributes.defaultChecked = config.defaultValue === attributes.value;
30
- } else {
27
+ } else if (options.type !== 'file') {
31
28
  attributes.defaultValue = config.defaultValue;
32
29
  }
33
30
  return attributes;
package/hooks.d.ts CHANGED
@@ -129,6 +129,7 @@ declare type ListCommandPayload<Schema, Type extends ListCommand<FieldValue<Sche
129
129
  export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
130
130
  Array<{
131
131
  key: string;
132
+ error: string | undefined;
132
133
  config: FieldConfig<Payload>;
133
134
  }>,
134
135
  {
package/hooks.js CHANGED
@@ -48,11 +48,7 @@ function useForm() {
48
48
  if (!form || !config.state) {
49
49
  return;
50
50
  }
51
- dom.setFormError(form, config.state);
52
- if (!form.reportValidity()) {
53
- dom.focusFirstInvalidField(form);
54
- }
55
- dom.requestSubmit(form);
51
+ dom.reportSubmission(form, config.state);
56
52
  }, [config.state]);
57
53
  react.useEffect(() => {
58
54
  // Revalidate the form when input value is changed
@@ -85,7 +81,7 @@ function useForm() {
85
81
  var handleInvalid = event => {
86
82
  var form = dom.getFormElement(ref.current);
87
83
  var field = event.target;
88
- if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '') {
84
+ if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '__form__') {
89
85
  return;
90
86
  }
91
87
  event.preventDefault();
@@ -141,12 +137,6 @@ function useForm() {
141
137
  var form = event.currentTarget;
142
138
  var nativeEvent = event.nativeEvent;
143
139
  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
140
 
151
141
  /**
152
142
  * It checks defaultPrevented to confirm if the submission is intentional
@@ -154,8 +144,7 @@ function useForm() {
154
144
  * event is captured and revalidate the form with new fields without triggering
155
145
  * a form submission at the same time.
156
146
  */
157
- if (!submitter || event.defaultPrevented) {
158
- event.preventDefault();
147
+ if (event.defaultPrevented) {
159
148
  return;
160
149
  }
161
150
  try {
@@ -176,14 +165,10 @@ function useForm() {
176
165
  *
177
166
  * This is mainly used to showcase the constraint validation API.
178
167
  */
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]);
168
+ for (var element of form.elements) {
169
+ if (dom.isFieldElement(element) && element.willValidate) {
170
+ element.setCustomValidity('');
171
+ submission.error.push([element.name, element.validationMessage]);
187
172
  }
188
173
  }
189
174
  }
@@ -198,7 +183,7 @@ function useForm() {
198
183
  }
199
184
  }
200
185
  }
201
- if (!config.noValidate && !submitter.formNoValidate && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
186
+ if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
202
187
  event.preventDefault();
203
188
  } else {
204
189
  var _config$onSubmit;
@@ -208,10 +193,7 @@ function useForm() {
208
193
  });
209
194
  }
210
195
  if (event.defaultPrevented) {
211
- dom.setFormError(form, submission);
212
- if (!form.reportValidity()) {
213
- dom.focusFirstInvalidField(form);
214
- }
196
+ dom.reportSubmission(form, submission);
215
197
  }
216
198
  } catch (e) {
217
199
  console.warn(e);
@@ -294,38 +276,6 @@ function useFieldset(ref, config) {
294
276
  event.preventDefault();
295
277
  }
296
278
  };
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
279
  var resetHandler = event => {
330
280
  var _fieldsetConfig$defau;
331
281
  var form = dom.getFormElement(ref.current);
@@ -343,11 +293,9 @@ function useFieldset(ref, config) {
343
293
 
344
294
  // The invalid event does not bubble and so listening on the capturing pharse is needed
345
295
  document.addEventListener('invalid', invalidHandler, true);
346
- document.addEventListener('submit', submitHandler);
347
296
  document.addEventListener('reset', resetHandler);
348
297
  return () => {
349
298
  document.removeEventListener('invalid', invalidHandler, true);
350
- document.removeEventListener('submit', submitHandler);
351
299
  document.removeEventListener('reset', resetHandler);
352
300
  };
353
301
  }, [ref]);
@@ -408,6 +356,7 @@ function useFieldList(ref, config) {
408
356
  initialError
409
357
  };
410
358
  });
359
+ var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => error === null || error === void 0 ? void 0 : error[0][1]));
411
360
  var [entries, setEntries] = react.useState(() => {
412
361
  var _config$defaultValue3;
413
362
  return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
@@ -416,6 +365,7 @@ function useFieldList(ref, config) {
416
365
  var [key, defaultValue] = _ref3;
417
366
  return {
418
367
  key,
368
+ error: error[index],
419
369
  config: {
420
370
  name: "".concat(config.name, "[").concat(index, "]"),
421
371
  form: config.form,
@@ -450,6 +400,31 @@ function useFieldList(ref, config) {
450
400
  configRef.current = config;
451
401
  });
452
402
  react.useEffect(() => {
403
+ var invalidHandler = event => {
404
+ var _configRef$current$na2;
405
+ var form = dom.getFormElement(ref.current);
406
+ var field = event.target;
407
+ var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
408
+ if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
409
+ return;
410
+ }
411
+ var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
412
+
413
+ // Update the error only if the field belongs to the fieldset
414
+ if (typeof index === 'number' && paths.length === 0) {
415
+ if (field.dataset.conformTouched) {
416
+ setError(prev => {
417
+ var _prev$index;
418
+ var prevMessage = (_prev$index = prev === null || prev === void 0 ? void 0 : prev[index]) !== null && _prev$index !== void 0 ? _prev$index : '';
419
+ if (prevMessage === field.validationMessage) {
420
+ return prev;
421
+ }
422
+ return [...prev.slice(0, index), field.validationMessage, ...prev.slice(index + 1)];
423
+ });
424
+ }
425
+ event.preventDefault();
426
+ }
427
+ };
453
428
  var submitHandler = event => {
454
429
  var form = dom.getFormElement(ref.current);
455
430
  if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
@@ -476,6 +451,22 @@ function useFieldList(ref, config) {
476
451
  }
477
452
  }
478
453
  });
454
+ setError(error => {
455
+ switch (command.type) {
456
+ case 'append':
457
+ case 'prepend':
458
+ case 'replace':
459
+ return dom.updateList([...error], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
460
+ payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
461
+ defaultValue: undefined
462
+ })
463
+ }));
464
+ default:
465
+ {
466
+ return dom.updateList([...error], command);
467
+ }
468
+ }
469
+ });
479
470
  event.preventDefault();
480
471
  };
481
472
  var resetHandler = event => {
@@ -490,11 +481,14 @@ function useFieldList(ref, config) {
490
481
  initialError: []
491
482
  });
492
483
  setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
484
+ setError([]);
493
485
  };
494
486
  document.addEventListener('submit', submitHandler, true);
487
+ document.addEventListener('invalid', invalidHandler, true);
495
488
  document.addEventListener('reset', resetHandler);
496
489
  return () => {
497
490
  document.removeEventListener('submit', submitHandler, true);
491
+ document.removeEventListener('invalid', invalidHandler, true);
498
492
  document.removeEventListener('reset', resetHandler);
499
493
  };
500
494
  }, [ref]);
package/module/helpers.js CHANGED
@@ -1,11 +1,7 @@
1
1
  function input(config) {
2
- var {
3
- type,
4
- value
5
- } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
6
- var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
2
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
7
3
  var attributes = {
8
- type,
4
+ type: options.type,
9
5
  name: config.name,
10
6
  form: config.form,
11
7
  required: config.required,
@@ -20,10 +16,11 @@ function input(config) {
20
16
  if (config.initialError && config.initialError.length > 0) {
21
17
  attributes.autoFocus = true;
22
18
  }
23
- if (isCheckboxOrRadio) {
24
- attributes.value = value !== null && value !== void 0 ? value : 'on';
19
+ if (options.type === 'checkbox' || options.type === 'radio') {
20
+ var _options$value;
21
+ attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
25
22
  attributes.defaultChecked = config.defaultValue === attributes.value;
26
- } else {
23
+ } else if (options.type !== 'file') {
27
24
  attributes.defaultValue = config.defaultValue;
28
25
  }
29
26
  return attributes;
package/module/hooks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
2
- import { getSubmissionType, setFormError, focusFirstInvalidField, requestSubmit, isFieldElement, getFormData, parse, hasError, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom';
2
+ import { getSubmissionType, reportSubmission, getFormData, parse, isFieldElement, hasError, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom';
3
3
  import { useRef, useState, useEffect } from 'react';
4
4
  import { input } from './helpers.js';
5
5
 
@@ -44,11 +44,7 @@ function useForm() {
44
44
  if (!form || !config.state) {
45
45
  return;
46
46
  }
47
- setFormError(form, config.state);
48
- if (!form.reportValidity()) {
49
- focusFirstInvalidField(form);
50
- }
51
- requestSubmit(form);
47
+ reportSubmission(form, config.state);
52
48
  }, [config.state]);
53
49
  useEffect(() => {
54
50
  // Revalidate the form when input value is changed
@@ -81,7 +77,7 @@ function useForm() {
81
77
  var handleInvalid = event => {
82
78
  var form = getFormElement(ref.current);
83
79
  var field = event.target;
84
- if (!form || !isFieldElement(field) || field.form !== form || field.name !== '') {
80
+ if (!form || !isFieldElement(field) || field.form !== form || field.name !== '__form__') {
85
81
  return;
86
82
  }
87
83
  event.preventDefault();
@@ -137,12 +133,6 @@ function useForm() {
137
133
  var form = event.currentTarget;
138
134
  var nativeEvent = event.nativeEvent;
139
135
  var submitter = nativeEvent.submitter;
140
- for (var element of form.elements) {
141
- if (isFieldElement(element) && element.name === '' && element.willValidate) {
142
- setError(element.validationMessage);
143
- break;
144
- }
145
- }
146
136
 
147
137
  /**
148
138
  * It checks defaultPrevented to confirm if the submission is intentional
@@ -150,8 +140,7 @@ function useForm() {
150
140
  * event is captured and revalidate the form with new fields without triggering
151
141
  * a form submission at the same time.
152
142
  */
153
- if (!submitter || event.defaultPrevented) {
154
- event.preventDefault();
143
+ if (event.defaultPrevented) {
155
144
  return;
156
145
  }
157
146
  try {
@@ -172,14 +161,10 @@ function useForm() {
172
161
  *
173
162
  * This is mainly used to showcase the constraint validation API.
174
163
  */
175
- setFormError(form, {
176
- type: 'submit',
177
- value: {},
178
- error: []
179
- });
180
- for (var _element of form.elements) {
181
- if (isFieldElement(_element) && _element.willValidate) {
182
- submission.error.push([_element.name, _element.validationMessage]);
164
+ for (var element of form.elements) {
165
+ if (isFieldElement(element) && element.willValidate) {
166
+ element.setCustomValidity('');
167
+ submission.error.push([element.name, element.validationMessage]);
183
168
  }
184
169
  }
185
170
  }
@@ -194,7 +179,7 @@ function useForm() {
194
179
  }
195
180
  }
196
181
  }
197
- if (!config.noValidate && !submitter.formNoValidate && hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
182
+ if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
198
183
  event.preventDefault();
199
184
  } else {
200
185
  var _config$onSubmit;
@@ -204,10 +189,7 @@ function useForm() {
204
189
  });
205
190
  }
206
191
  if (event.defaultPrevented) {
207
- setFormError(form, submission);
208
- if (!form.reportValidity()) {
209
- focusFirstInvalidField(form);
210
- }
192
+ reportSubmission(form, submission);
211
193
  }
212
194
  } catch (e) {
213
195
  console.warn(e);
@@ -290,38 +272,6 @@ function useFieldset(ref, config) {
290
272
  event.preventDefault();
291
273
  }
292
274
  };
293
- var submitHandler = event => {
294
- var form = getFormElement(ref.current);
295
- if (!form || event.target !== form) {
296
- return;
297
- }
298
-
299
- /**
300
- * Reset the error state of each field if its validity is changed.
301
- *
302
- * This is a workaround as no official way is provided to notify
303
- * when the validity of the field is changed from `invalid` to `valid`.
304
- */
305
- setError(prev => {
306
- var _configRef$current$na2;
307
- var next = prev;
308
- var fieldsetName = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
309
- for (var field of form.elements) {
310
- if (isFieldElement(field) && field.name.startsWith(fieldsetName)) {
311
- var _next$key, _next;
312
- var key = fieldsetName ? field.name.slice(fieldsetName.length + 1) : field.name;
313
- var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
314
- var nextMessage = field.validationMessage;
315
- if (prevMessage !== '' && nextMessage === '') {
316
- next = _objectSpread2(_objectSpread2({}, next), {}, {
317
- [key]: ''
318
- });
319
- }
320
- }
321
- }
322
- return next;
323
- });
324
- };
325
275
  var resetHandler = event => {
326
276
  var _fieldsetConfig$defau;
327
277
  var form = getFormElement(ref.current);
@@ -339,11 +289,9 @@ function useFieldset(ref, config) {
339
289
 
340
290
  // The invalid event does not bubble and so listening on the capturing pharse is needed
341
291
  document.addEventListener('invalid', invalidHandler, true);
342
- document.addEventListener('submit', submitHandler);
343
292
  document.addEventListener('reset', resetHandler);
344
293
  return () => {
345
294
  document.removeEventListener('invalid', invalidHandler, true);
346
- document.removeEventListener('submit', submitHandler);
347
295
  document.removeEventListener('reset', resetHandler);
348
296
  };
349
297
  }, [ref]);
@@ -404,6 +352,7 @@ function useFieldList(ref, config) {
404
352
  initialError
405
353
  };
406
354
  });
355
+ var [error, setError] = useState(() => uncontrolledState.initialError.map(error => error === null || error === void 0 ? void 0 : error[0][1]));
407
356
  var [entries, setEntries] = useState(() => {
408
357
  var _config$defaultValue3;
409
358
  return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
@@ -412,6 +361,7 @@ function useFieldList(ref, config) {
412
361
  var [key, defaultValue] = _ref3;
413
362
  return {
414
363
  key,
364
+ error: error[index],
415
365
  config: {
416
366
  name: "".concat(config.name, "[").concat(index, "]"),
417
367
  form: config.form,
@@ -446,6 +396,31 @@ function useFieldList(ref, config) {
446
396
  configRef.current = config;
447
397
  });
448
398
  useEffect(() => {
399
+ var invalidHandler = event => {
400
+ var _configRef$current$na2;
401
+ var form = getFormElement(ref.current);
402
+ var field = event.target;
403
+ var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
404
+ if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
405
+ return;
406
+ }
407
+ var [index, ...paths] = getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
408
+
409
+ // Update the error only if the field belongs to the fieldset
410
+ if (typeof index === 'number' && paths.length === 0) {
411
+ if (field.dataset.conformTouched) {
412
+ setError(prev => {
413
+ var _prev$index;
414
+ var prevMessage = (_prev$index = prev === null || prev === void 0 ? void 0 : prev[index]) !== null && _prev$index !== void 0 ? _prev$index : '';
415
+ if (prevMessage === field.validationMessage) {
416
+ return prev;
417
+ }
418
+ return [...prev.slice(0, index), field.validationMessage, ...prev.slice(index + 1)];
419
+ });
420
+ }
421
+ event.preventDefault();
422
+ }
423
+ };
449
424
  var submitHandler = event => {
450
425
  var form = getFormElement(ref.current);
451
426
  if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
@@ -472,6 +447,22 @@ function useFieldList(ref, config) {
472
447
  }
473
448
  }
474
449
  });
450
+ setError(error => {
451
+ switch (command.type) {
452
+ case 'append':
453
+ case 'prepend':
454
+ case 'replace':
455
+ return updateList([...error], _objectSpread2(_objectSpread2({}, command), {}, {
456
+ payload: _objectSpread2(_objectSpread2({}, command.payload), {}, {
457
+ defaultValue: undefined
458
+ })
459
+ }));
460
+ default:
461
+ {
462
+ return updateList([...error], command);
463
+ }
464
+ }
465
+ });
475
466
  event.preventDefault();
476
467
  };
477
468
  var resetHandler = event => {
@@ -486,11 +477,14 @@ function useFieldList(ref, config) {
486
477
  initialError: []
487
478
  });
488
479
  setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
480
+ setError([]);
489
481
  };
490
482
  document.addEventListener('submit', submitHandler, true);
483
+ document.addEventListener('invalid', invalidHandler, true);
491
484
  document.addEventListener('reset', resetHandler);
492
485
  return () => {
493
486
  document.removeEventListener('submit', submitHandler, true);
487
+ document.removeEventListener('invalid', invalidHandler, true);
494
488
  document.removeEventListener('reset', resetHandler);
495
489
  };
496
490
  }, [ref]);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@conform-to/react",
3
3
  "description": "Conform view adapter for react",
4
4
  "license": "MIT",
5
- "version": "0.4.1",
5
+ "version": "0.5.0-pre.0",
6
6
  "main": "index.js",
7
7
  "module": "module/index.js",
8
8
  "repository": {
@@ -19,7 +19,7 @@
19
19
  "url": "https://github.com/edmundhung/conform/issues"
20
20
  },
21
21
  "dependencies": {
22
- "@conform-to/dom": "0.4.1"
22
+ "@conform-to/dom": "0.5.0-pre.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": ">=16.8"