@conform-to/react 0.4.0-pre.1 → 0.4.0-pre.3

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
@@ -10,8 +10,11 @@
10
10
  - [useFieldset](#usefieldset)
11
11
  - [useFieldList](#usefieldlist)
12
12
  - [useControlledInput](#usecontrolledinput)
13
- - [createValidate](#createvalidate)
14
13
  - [conform](#conform)
14
+ - [getFormElements](#getformelements)
15
+ - [hasError](#haserror)
16
+ - [parse](#parse)
17
+ - [shouldValidate](#shouldvalidate)
15
18
 
16
19
  <!-- /aside -->
17
20
 
@@ -21,15 +24,23 @@ By default, the browser calls the [reportValidity()](https://developer.mozilla.o
21
24
 
22
25
  This hook enhances the form validation behaviour in 3 parts:
23
26
 
24
- 1. It lets you hook up custom validation logic into different form events. For example, revalidation will be triggered whenever something changed.
25
- 2. It provides options for you to decide the best timing to start reporting errors. This could be as earliest as the user start typing, or also as late as the user try submitting the form.
27
+ 1. It enhances form validation with custom rules by subscribing to different DOM events and reporting the errors only when it is configured to do so.
28
+ 2. It unifies client and server validation in one place.
26
29
  3. It exposes the state of each field in the form of [data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), such as `data-conform-touched`, allowing flexible styling across your form without the need to manipulate the class names.
27
30
 
28
31
  ```tsx
29
32
  import { useForm } from '@conform-to/react';
30
33
 
31
34
  function LoginForm() {
32
- const formProps = useForm({
35
+ const form = useForm({
36
+ /**
37
+ * Validation mode.
38
+ * Support "client-only" or "server-validation".
39
+ *
40
+ * Default to `client-only`.
41
+ */
42
+ mode: 'client-only',
43
+
33
44
  /**
34
45
  * Define when the error should be reported initially.
35
46
  * Support "onSubmit", "onChange", "onBlur".
@@ -38,6 +49,16 @@ function LoginForm() {
38
49
  */
39
50
  initialReport: 'onBlur',
40
51
 
52
+ /**
53
+ * An object representing the initial value of the form.
54
+ */
55
+ defaultValue: undefined;
56
+
57
+ /**
58
+ * An object describing the state from the last submission
59
+ */
60
+ state: undefined;
61
+
41
62
  /**
42
63
  * Enable native validation before hydation.
43
64
  *
@@ -54,43 +75,38 @@ function LoginForm() {
54
75
 
55
76
  /**
56
77
  * A function to be called when the form should be (re)validated.
78
+ * Only sync validation is supported
57
79
  */
58
- validate(form, submitter) {
80
+ onValidate({ form, formData, submission }) {
59
81
  // ...
60
82
  },
61
83
 
62
84
  /**
63
85
  * The submit event handler of the form.
64
86
  */
65
- onSubmit(event) {
87
+ onSubmit(event, { form, formData, submission }) {
66
88
  // ...
67
89
  },
68
90
  });
69
91
 
70
- return (
71
- <form {...formProps}>
72
- <input type="email" name="email" required />
73
- <input type="password" name="password" required />
74
- <button type="submit">Login</button>
75
- </form>
76
- );
92
+ // ...
77
93
  }
78
94
  ```
79
95
 
80
96
  <details>
81
- <summary>What is `formProps`?</summary>
97
+ <summary>What is `form.props`?</summary>
82
98
 
83
99
  It is a group of properties properties required to hook into form events. They can also be set explicitly as shown below:
84
100
 
85
101
  ```tsx
86
102
  function RandomForm() {
87
- const formProps = useForm();
103
+ const form = useForm();
88
104
 
89
105
  return (
90
106
  <form
91
- ref={formProps.ref}
92
- onSubmit={formProps.onSubmit}
93
- noValidate={formProps.noValidate}
107
+ ref={form.props.ref}
108
+ onSubmit={form.props.onSubmit}
109
+ noValidate={form.props.noValidate}
94
110
  >
95
111
  {/* ... */}
96
112
  </form>
@@ -103,17 +119,17 @@ function RandomForm() {
103
119
  <details>
104
120
  <summary>Does it work with custom form component like Remix Form?</summary>
105
121
 
106
- Yes! It will fallback to native form submission if the submit event handler is omitted or the event is not default prevented.
122
+ Yes! It will fallback to native form submission as long as the submit event is not default prevented.
107
123
 
108
124
  ```tsx
109
125
  import { useFrom } from '@conform-to/react';
110
126
  import { Form } from '@remix-run/react';
111
127
 
112
128
  function LoginForm() {
113
- const formProps = useForm();
129
+ const form = useForm();
114
130
 
115
131
  return (
116
- <Form method="post" action="/login" {...formProps}>
132
+ <Form method="post" action="/login" {...form.props}>
117
133
  {/* ... */}
118
134
  </Form>
119
135
  );
@@ -123,9 +139,9 @@ function LoginForm() {
123
139
  </details>
124
140
 
125
141
  <details>
126
- <summary>Is the `validate` function required?</summary>
142
+ <summary>Is the `onValidate` function required?</summary>
127
143
 
128
- The `validate` function is not required if the validation logic can be fully covered by the [native constraints](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Constraint_validation#validation-related_attributes), e.g. **required** / **min** / **pattern** etc.
144
+ The `onValidate` function is not required if the validation logic can be fully covered by the [native constraints](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Constraint_validation#validation-related_attributes), e.g. **required** / **min** / **pattern** etc.
129
145
 
130
146
  ```tsx
131
147
  import { useForm, useFieldset } from '@conform-to/react';
@@ -156,10 +172,10 @@ function LoginForm() {
156
172
 
157
173
  ### useFieldset
158
174
 
159
- This hook can be used to monitor the state of each field and help fields configuration. It lets you:
175
+ This hook can be used to monitor the state of each field and help configuration. It lets you:
160
176
 
161
177
  1. Capturing errors at the form/fieldset level, removing the need to setup invalid handler on each field.
162
- 2. Defining config in one central place. e.g. name, default value and constraint, then distributing it to each field using the [conform](#conform) helpers.
178
+ 2. Defining config in one central place. e.g. name, default value and constraint, then distributing it to each field with the [conform](#conform) helpers.
163
179
 
164
180
  ```tsx
165
181
  import { useForm, useFieldset } from '@conform-to/react';
@@ -278,33 +294,6 @@ function Fieldset() {
278
294
  }
279
295
  ```
280
296
 
281
- <details>
282
- <summary>Is it required to provide the FieldsetConfig to `useFieldset`?</summary>
283
-
284
- No. The only thing required is the ref object. All the config is optional. You can always pass them to each fields manually.
285
-
286
- ```tsx
287
- import { useForm, useFieldset } from '@conform-to/react';
288
-
289
- function SubscriptionForm() {
290
- const formProps = useForm();
291
- const { email } = useFieldset(formProps.ref);
292
-
293
- return (
294
- <form {...formProps}>
295
- <input
296
- type="email"
297
- name={email.config.name}
298
- defaultValue="support@conform.dev"
299
- required
300
- />
301
- </form>
302
- );
303
- }
304
- ```
305
-
306
- </details>
307
-
308
297
  <details>
309
298
  <summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
310
299
 
@@ -335,7 +324,7 @@ function ExampleForm() {
335
324
 
336
325
  ### useFieldList
337
326
 
338
- It returns a list of key and config, with a group of helpers configuring buttons for list manipulation
327
+ It returns a list of key and config, with helpers to configure command buttons with [list command](/docs/submission.md#list-command).
339
328
 
340
329
  ```tsx
341
330
  import { useFieldset, useFieldList } from '@conform-to/react';
@@ -356,7 +345,7 @@ type Collection = {
356
345
  function CollectionFieldset() {
357
346
  const ref = useRef();
358
347
  const { books } = useFieldset<Collection>(ref);
359
- const [bookList, control] = useFieldList(ref, books.config);
348
+ const [bookList, command] = useFieldList(ref, books.config);
360
349
 
361
350
  return (
362
351
  <fieldset ref={ref}>
@@ -373,12 +362,12 @@ function CollectionFieldset() {
373
362
  />
374
363
 
375
364
  {/* To setup a delete button */}
376
- <button {...control.remove({ index })}>Delete</button>
365
+ <button {...command.remove({ index })}>Delete</button>
377
366
  </div>
378
367
  ))}
379
368
 
380
369
  {/* To setup a button that can append a new row with optional default value */}
381
- <button {...control.append({ defaultValue: { name: '', isbn: '' } })}>
370
+ <button {...command.append({ defaultValue: { name: '', isbn: '' } })}>
382
371
  add
383
372
  </button>
384
373
  </fieldset>
@@ -395,7 +384,7 @@ import { useRef } from 'react';
395
384
  function CollectionFieldset() {
396
385
  const ref = useRef();
397
386
  const { books } = useFieldset<Collection>(ref);
398
- const [bookList, control] = useFieldList(ref, books.config);
387
+ const [bookList, command] = useFieldList(ref, books.config);
399
388
 
400
389
  return (
401
390
  <fieldset ref={ref}>
@@ -405,12 +394,12 @@ function CollectionFieldset() {
405
394
  <BookFieldset {...book.config} />
406
395
 
407
396
  {/* To setup a delete button */}
408
- <button {...control.remove({ index })}>Delete</button>
397
+ <button {...command.remove({ index })}>Delete</button>
409
398
  </div>
410
399
  ))}
411
400
 
412
401
  {/* To setup a button that can append a new row */}
413
- <button {...control.append()}>add</button>
402
+ <button {...command.append()}>add</button>
414
403
  </fieldset>
415
404
  );
416
405
  }
@@ -493,58 +482,7 @@ function MuiForm() {
493
482
  <MenuItem value="c">Category C</MenuItem>
494
483
  </TextField>
495
484
  </fieldset>
496
- )
497
- }
498
- ```
499
-
500
- ---
501
-
502
- ### createValidate
503
-
504
- This help you configure a validate function to check the validity of each fields and setup custom messages using the Constraint Validation APIs.
505
-
506
- ```tsx
507
- import { useForm, createValidate } from '@conform-to/react';
508
-
509
- export default function SignupForm() {
510
- const formProps = useForm({
511
- validate: createValidate((field, formData) => {
512
- switch (field.name) {
513
- case 'email':
514
- if (field.validity.valueMissing) {
515
- field.setCustomValidity('Email is required');
516
- } else if (field.validity.typeMismatch) {
517
- field.setCustomValidity('Please enter a valid email');
518
- } else {
519
- field.setCustomValidity('');
520
- }
521
- break;
522
- case 'password':
523
- if (field.validity.valueMissing) {
524
- field.setCustomValidity('Password is required');
525
- } else if (field.validity.tooShort) {
526
- field.setCustomValidity(
527
- 'The password should be at least 10 characters long',
528
- );
529
- } else {
530
- field.setCustomValidity('');
531
- }
532
- break;
533
- case 'confirm-password': {
534
- if (field.validity.valueMissing) {
535
- field.setCustomValidity('Confirm Password is required');
536
- } else if (field.value !== formData.get('password')) {
537
- field.setCustomValidity('The password does not match');
538
- } else {
539
- field.setCustomValidity('');
540
- }
541
- break;
542
- }
543
- }
544
- }),
545
- });
546
-
547
- return <form {...formProps}>{/* ... */}</form>;
485
+ );
548
486
  }
549
487
  ```
550
488
 
@@ -615,3 +553,110 @@ function RandomForm() {
615
553
  );
616
554
  }
617
555
  ```
556
+
557
+ ---
558
+
559
+ ### getFormElements
560
+
561
+ It returns all _input_ / _select_ / _textarea_ or _button_ in the forms. Useful when looping through the form elements to validate each field.
562
+
563
+ ```tsx
564
+ import { useForm, parse, getFormElements } from '@conform-to/react';
565
+
566
+ export default function LoginForm() {
567
+ const form = useForm({
568
+ onValidate({ form, formData }) {
569
+ const submission = parse(formData);
570
+
571
+ for (const element of getFormElements(form)) {
572
+ switch (element.name) {
573
+ case 'email': {
574
+ if (element.validity.valueMissing) {
575
+ submission.error.push([element.name, 'Email is required']);
576
+ } else if (element.validity.typeMismatch) {
577
+ submission.error.push([element.name, 'Email is invalid']);
578
+ }
579
+ break;
580
+ }
581
+ case 'password': {
582
+ if (element.validity.valueMissing) {
583
+ submission.error.push([element.name, 'Password is required']);
584
+ }
585
+ break;
586
+ }
587
+ }
588
+ }
589
+
590
+ return submission;
591
+ },
592
+
593
+ // ....
594
+ });
595
+
596
+ // ...
597
+ }
598
+ ```
599
+
600
+ ---
601
+
602
+ ### hasError
603
+
604
+ This helper checks if there is any message defined in error array with the provided name.
605
+
606
+ ```ts
607
+ import { hasError } from '@conform-to/react';
608
+
609
+ /**
610
+ * Assume the error looks like this:
611
+ */
612
+ const error = [['email', 'Email is required']];
613
+
614
+ // This will log `true`
615
+ console.log(hasError(error, 'email'));
616
+
617
+ // This will log `false`
618
+ console.log(hasError(error, 'password'));
619
+ ```
620
+
621
+ ---
622
+
623
+ ### parse
624
+
625
+ It parses the formData based on the [naming convention](/docs/submission).
626
+
627
+ ```tsx
628
+ import { parse } from '@conform-to/react';
629
+
630
+ const formData = new FormData();
631
+ const submission = parse(formData);
632
+
633
+ console.log(submission);
634
+ ```
635
+
636
+ ---
637
+
638
+ ### shouldValidate
639
+
640
+ This helper checks if the scope of validation includes a specific field by checking the submission:
641
+
642
+ ```tsx
643
+ import { shouldValidate } from '@conform-to/react';
644
+
645
+ /**
646
+ * The submission type and intent give us hint on what should be valdiated.
647
+ * If the type is 'validate', only the field with name matching the metadata must be validated.
648
+ * If the type is 'submit', everything should be validated (Default submission)
649
+ */
650
+ const submission = {
651
+ context: 'validate',
652
+ intent: 'email',
653
+ value: {},
654
+ error: [],
655
+ };
656
+
657
+ // This will log 'true'
658
+ console.log(shouldValidate(submission, 'email'));
659
+
660
+ // This will log 'false'
661
+ console.log(shouldValidate(submission, 'password'));
662
+ ```
package/hooks.d.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type ListCommand, type Primitive, type Submission } from '@conform-to/dom';
2
2
  import { type InputHTMLAttributes, type FormEvent, type RefObject } from 'react';
3
- interface FormContext<Schema extends Record<string, any>> {
4
- form: HTMLFormElement;
5
- formData: FormData;
6
- submission: Submission<Schema>;
7
- }
8
3
  export interface FormConfig<Schema extends Record<string, any>> {
9
4
  /**
10
5
  * Validation mode. Default to `client-only`.
@@ -40,12 +35,18 @@ export interface FormConfig<Schema extends Record<string, any>> {
40
35
  /**
41
36
  * A function to be called when the form should be (re)validated.
42
37
  */
43
- onValidate?: (context: FormContext<Schema>) => void;
38
+ onValidate?: ({ form, formData, }: {
39
+ form: HTMLFormElement;
40
+ formData: FormData;
41
+ }) => Submission<Schema>;
44
42
  /**
45
43
  * The submit event handler of the form. It will be called
46
44
  * only when the form is considered valid.
47
45
  */
48
- onSubmit?: (event: FormEvent<HTMLFormElement>, context: FormContext<Schema>) => void;
46
+ onSubmit?: (event: FormEvent<HTMLFormElement>, context: {
47
+ formData: FormData;
48
+ submission: Submission<Schema>;
49
+ }) => void;
49
50
  }
50
51
  /**
51
52
  * Properties to be applied to the form element
@@ -65,7 +66,7 @@ interface Form<Schema extends Record<string, any>> {
65
66
  * Returns properties required to hook into form events.
66
67
  * Applied custom validation and define when error should be reported.
67
68
  *
68
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#useform
69
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#useform
69
70
  */
70
71
  export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>;
71
72
  /**
@@ -106,41 +107,37 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
106
107
  /**
107
108
  * Returns all the information about the fieldset.
108
109
  *
109
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldset
110
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usefieldset
110
111
  */
111
- export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldsetConfig<Schema>): Fieldset<Schema>;
112
- export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldConfig<Schema>): Fieldset<Schema>;
113
- interface ControlButtonProps {
112
+ export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldsetConfig<Schema>): Fieldset<Schema>;
113
+ export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Schema>): Fieldset<Schema>;
114
+ interface CommandButtonProps {
114
115
  name?: string;
115
116
  value?: string;
116
117
  form?: string;
117
118
  formNoValidate: true;
118
119
  }
119
- declare type CommandPayload<Schema, Type extends ListCommand<FieldValue<Schema>>['type']> = Extract<ListCommand<FieldValue<Schema>>, {
120
+ declare type ListCommandPayload<Schema, Type extends ListCommand<FieldValue<Schema>>['type']> = Extract<ListCommand<FieldValue<Schema>>, {
120
121
  type: Type;
121
122
  }>['payload'];
122
- /**
123
- * A group of helpers for configuring a list control button
124
- */
125
- interface ListControl<Schema> {
126
- prepend(payload?: CommandPayload<Schema, 'prepend'>): ControlButtonProps;
127
- append(payload?: CommandPayload<Schema, 'append'>): ControlButtonProps;
128
- replace(payload: CommandPayload<Schema, 'replace'>): ControlButtonProps;
129
- remove(payload: CommandPayload<Schema, 'remove'>): ControlButtonProps;
130
- reorder(payload: CommandPayload<Schema, 'reorder'>): ControlButtonProps;
131
- }
132
123
  /**
133
124
  * Returns a list of key and config, with a group of helpers
134
125
  * configuring buttons for list manipulation
135
126
  *
136
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist
127
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usefieldlist
137
128
  */
138
129
  export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
139
130
  Array<{
140
131
  key: string;
141
132
  config: FieldConfig<Payload>;
142
133
  }>,
143
- ListControl<Payload>
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
+ }
144
141
  ];
145
142
  interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
146
143
  ref: RefObject<HTMLInputElement>;
@@ -163,7 +160,7 @@ interface InputControl<Element extends {
163
160
  * This is particular useful when integrating dropdown and datepicker whichs
164
161
  * introduces custom input mode.
165
162
  *
166
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput
163
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usecontrolledinput
167
164
  */
168
165
  export declare function useControlledInput<Element extends {
169
166
  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.0-pre.1/packages/conform-react/README.md#useform
14
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#useform
15
15
  */
16
16
  function useForm() {
17
17
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -182,48 +182,70 @@ function useForm() {
182
182
  return;
183
183
  }
184
184
 
185
- var formData = dom.getFormData(form, submitter);
186
- var submission = dom.parse(formData);
187
- var context = {
188
- form,
189
- formData,
190
- submission
191
- }; // Touch all fields only if the submitter is not a command button
192
-
193
- if (!submission.type) {
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
-
202
185
  try {
203
- if (!config.noValidate && !submitter.formNoValidate) {
204
- var _config$onValidate;
186
+ var submission;
187
+ var formData = dom.getFormData(form, submitter);
205
188
 
206
- (_config$onValidate = config.onValidate) === null || _config$onValidate === void 0 ? void 0 : _config$onValidate.call(config, context);
189
+ if (typeof config.onValidate === 'function') {
190
+ submission = config.onValidate({
191
+ form,
192
+ formData
193
+ });
194
+ } else {
195
+ submission = dom.parse(formData);
196
+
197
+ if (config.mode !== 'server-validation') {
198
+ /**
199
+ * As there is no custom logic defined,
200
+ * removing the custom validity state will allow us
201
+ * finding the latest validation message.
202
+ *
203
+ * This is mainly used to showcase the constraint validation API.
204
+ */
205
+ dom.setFormError(form, {
206
+ type: 'submit',
207
+ value: {},
208
+ error: []
209
+ });
207
210
 
208
- if (!form.reportValidity()) {
209
- dom.focusFirstInvalidField(form);
210
- event.preventDefault();
211
+ for (var _element of form.elements) {
212
+ if (dom.isFieldElement(_element) && _element.willValidate) {
213
+ submission.error.push([_element.name, _element.validationMessage]);
214
+ }
215
+ }
216
+ }
217
+ } // Touch all fields only if the submitter is not a command button
218
+
219
+
220
+ if (submission.type === 'submit') {
221
+ for (var field of form.elements) {
222
+ if (dom.isFieldElement(field)) {
223
+ // Mark the field as touched
224
+ field.dataset.conformTouched = 'true';
225
+ }
211
226
  }
212
227
  }
213
- } catch (e) {
214
- if (e !== form) {
215
- console.warn(e);
216
- }
217
- }
218
228
 
219
- if (!event.defaultPrevented) {
220
- if (config.mode !== 'server-validation' && submission.type === 'validate') {
229
+ if (!config.noValidate && !submitter.formNoValidate && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
221
230
  event.preventDefault();
222
231
  } else {
223
232
  var _config$onSubmit;
224
233
 
225
- (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context);
234
+ (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
235
+ formData,
236
+ submission
237
+ });
226
238
  }
239
+
240
+ if (event.defaultPrevented) {
241
+ dom.setFormError(form, submission);
242
+
243
+ if (!form.reportValidity()) {
244
+ dom.focusFirstInvalidField(form);
245
+ }
246
+ }
247
+ } catch (e) {
248
+ console.warn(e);
227
249
  }
228
250
  }
229
251
 
@@ -287,11 +309,11 @@ function useFieldset(ref, config) {
287
309
  });
288
310
  react.useEffect(() => {
289
311
  var invalidHandler = event => {
290
- var _configRef$current$na, _configRef$current;
312
+ var _configRef$current$na;
291
313
 
292
314
  var form = dom.getFormElement(ref.current);
293
315
  var field = event.target;
294
- var fieldsetName = (_configRef$current$na = (_configRef$current = configRef.current) === null || _configRef$current === void 0 ? void 0 : _configRef$current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
316
+ var fieldsetName = (_configRef$current$na = configRef.current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
295
317
 
296
318
  if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
297
319
  return;
@@ -335,10 +357,10 @@ function useFieldset(ref, config) {
335
357
 
336
358
 
337
359
  setError(prev => {
338
- var _configRef$current$na2, _configRef$current2;
360
+ var _configRef$current$na2;
339
361
 
340
362
  var next = prev;
341
- var fieldsetName = (_configRef$current$na2 = (_configRef$current2 = configRef.current) === null || _configRef$current2 === void 0 ? void 0 : _configRef$current2.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
363
+ var fieldsetName = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
342
364
 
343
365
  for (var field of form.elements) {
344
366
  if (dom.isFieldElement(field) && field.name.startsWith(fieldsetName)) {
@@ -423,7 +445,7 @@ function useFieldset(ref, config) {
423
445
  * Returns a list of key and config, with a group of helpers
424
446
  * configuring buttons for list manipulation
425
447
  *
426
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist
448
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usefieldlist
427
449
  */
428
450
  function useFieldList(ref, config) {
429
451
  var configRef = react.useRef(config);
@@ -479,7 +501,7 @@ function useFieldList(ref, config) {
479
501
  * have it encoded in the value.
480
502
  */
481
503
 
482
- var control = new Proxy({}, {
504
+ var command = new Proxy({}, {
483
505
  get(_target, type) {
484
506
  return function () {
485
507
  var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -559,7 +581,8 @@ function useFieldList(ref, config) {
559
581
  document.removeEventListener('reset', resetHandler);
560
582
  };
561
583
  }, [ref]);
562
- return [list, control];
584
+ return [list, // @ts-expect-error proxy type
585
+ command];
563
586
  }
564
587
 
565
588
  /**
@@ -567,7 +590,7 @@ function useFieldList(ref, config) {
567
590
  * This is particular useful when integrating dropdown and datepicker whichs
568
591
  * introduces custom input mode.
569
592
  *
570
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput
593
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usecontrolledinput
571
594
  */
572
595
  function useControlledInput(config) {
573
596
  var _config$defaultValue4;
package/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { type FieldsetConstraint, type Submission, hasError, isFieldElement, parse, setFormError, shouldValidate, } from '@conform-to/dom';
1
+ export { type FieldsetConstraint, type Submission, getFormElements, hasError, parse, shouldValidate, } from '@conform-to/dom';
2
2
  export * from './hooks';
3
3
  export * as conform from './helpers';
package/index.js CHANGED
@@ -8,22 +8,18 @@ var helpers = require('./helpers.js');
8
8
 
9
9
 
10
10
 
11
- Object.defineProperty(exports, 'hasError', {
11
+ Object.defineProperty(exports, 'getFormElements', {
12
12
  enumerable: true,
13
- get: function () { return dom.hasError; }
13
+ get: function () { return dom.getFormElements; }
14
14
  });
15
- Object.defineProperty(exports, 'isFieldElement', {
15
+ Object.defineProperty(exports, 'hasError', {
16
16
  enumerable: true,
17
- get: function () { return dom.isFieldElement; }
17
+ get: function () { return dom.hasError; }
18
18
  });
19
19
  Object.defineProperty(exports, 'parse', {
20
20
  enumerable: true,
21
21
  get: function () { return dom.parse; }
22
22
  });
23
- Object.defineProperty(exports, 'setFormError', {
24
- enumerable: true,
25
- get: function () { return dom.setFormError; }
26
- });
27
23
  Object.defineProperty(exports, 'shouldValidate', {
28
24
  enumerable: true,
29
25
  get: function () { return dom.shouldValidate; }
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, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom';
2
+ import { getSubmissionType, setFormError, focusFirstInvalidField, requestSubmit, isFieldElement, getFormData, parse, 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
 
@@ -7,7 +7,7 @@ import { input } from './helpers.js';
7
7
  * Returns properties required to hook into form events.
8
8
  * Applied custom validation and define when error should be reported.
9
9
  *
10
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#useform
10
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#useform
11
11
  */
12
12
  function useForm() {
13
13
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -178,48 +178,70 @@ function useForm() {
178
178
  return;
179
179
  }
180
180
 
181
- var formData = getFormData(form, submitter);
182
- var submission = parse(formData);
183
- var context = {
184
- form,
185
- formData,
186
- submission
187
- }; // Touch all fields only if the submitter is not a command button
188
-
189
- if (!submission.type) {
190
- for (var field of form.elements) {
191
- if (isFieldElement(field)) {
192
- // Mark the field as touched
193
- field.dataset.conformTouched = 'true';
194
- }
195
- }
196
- }
197
-
198
181
  try {
199
- if (!config.noValidate && !submitter.formNoValidate) {
200
- var _config$onValidate;
182
+ var submission;
183
+ var formData = getFormData(form, submitter);
201
184
 
202
- (_config$onValidate = config.onValidate) === null || _config$onValidate === void 0 ? void 0 : _config$onValidate.call(config, context);
185
+ if (typeof config.onValidate === 'function') {
186
+ submission = config.onValidate({
187
+ form,
188
+ formData
189
+ });
190
+ } else {
191
+ submission = parse(formData);
192
+
193
+ if (config.mode !== 'server-validation') {
194
+ /**
195
+ * As there is no custom logic defined,
196
+ * removing the custom validity state will allow us
197
+ * finding the latest validation message.
198
+ *
199
+ * This is mainly used to showcase the constraint validation API.
200
+ */
201
+ setFormError(form, {
202
+ type: 'submit',
203
+ value: {},
204
+ error: []
205
+ });
203
206
 
204
- if (!form.reportValidity()) {
205
- focusFirstInvalidField(form);
206
- event.preventDefault();
207
+ for (var _element of form.elements) {
208
+ if (isFieldElement(_element) && _element.willValidate) {
209
+ submission.error.push([_element.name, _element.validationMessage]);
210
+ }
211
+ }
212
+ }
213
+ } // Touch all fields only if the submitter is not a command button
214
+
215
+
216
+ if (submission.type === 'submit') {
217
+ for (var field of form.elements) {
218
+ if (isFieldElement(field)) {
219
+ // Mark the field as touched
220
+ field.dataset.conformTouched = 'true';
221
+ }
207
222
  }
208
223
  }
209
- } catch (e) {
210
- if (e !== form) {
211
- console.warn(e);
212
- }
213
- }
214
224
 
215
- if (!event.defaultPrevented) {
216
- if (config.mode !== 'server-validation' && submission.type === 'validate') {
225
+ if (!config.noValidate && !submitter.formNoValidate && hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
217
226
  event.preventDefault();
218
227
  } else {
219
228
  var _config$onSubmit;
220
229
 
221
- (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context);
230
+ (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
231
+ formData,
232
+ submission
233
+ });
222
234
  }
235
+
236
+ if (event.defaultPrevented) {
237
+ setFormError(form, submission);
238
+
239
+ if (!form.reportValidity()) {
240
+ focusFirstInvalidField(form);
241
+ }
242
+ }
243
+ } catch (e) {
244
+ console.warn(e);
223
245
  }
224
246
  }
225
247
 
@@ -283,11 +305,11 @@ function useFieldset(ref, config) {
283
305
  });
284
306
  useEffect(() => {
285
307
  var invalidHandler = event => {
286
- var _configRef$current$na, _configRef$current;
308
+ var _configRef$current$na;
287
309
 
288
310
  var form = getFormElement(ref.current);
289
311
  var field = event.target;
290
- var fieldsetName = (_configRef$current$na = (_configRef$current = configRef.current) === null || _configRef$current === void 0 ? void 0 : _configRef$current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
312
+ var fieldsetName = (_configRef$current$na = configRef.current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
291
313
 
292
314
  if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
293
315
  return;
@@ -331,10 +353,10 @@ function useFieldset(ref, config) {
331
353
 
332
354
 
333
355
  setError(prev => {
334
- var _configRef$current$na2, _configRef$current2;
356
+ var _configRef$current$na2;
335
357
 
336
358
  var next = prev;
337
- var fieldsetName = (_configRef$current$na2 = (_configRef$current2 = configRef.current) === null || _configRef$current2 === void 0 ? void 0 : _configRef$current2.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
359
+ var fieldsetName = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
338
360
 
339
361
  for (var field of form.elements) {
340
362
  if (isFieldElement(field) && field.name.startsWith(fieldsetName)) {
@@ -419,7 +441,7 @@ function useFieldset(ref, config) {
419
441
  * Returns a list of key and config, with a group of helpers
420
442
  * configuring buttons for list manipulation
421
443
  *
422
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist
444
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usefieldlist
423
445
  */
424
446
  function useFieldList(ref, config) {
425
447
  var configRef = useRef(config);
@@ -475,7 +497,7 @@ function useFieldList(ref, config) {
475
497
  * have it encoded in the value.
476
498
  */
477
499
 
478
- var control = new Proxy({}, {
500
+ var command = new Proxy({}, {
479
501
  get(_target, type) {
480
502
  return function () {
481
503
  var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -555,7 +577,8 @@ function useFieldList(ref, config) {
555
577
  document.removeEventListener('reset', resetHandler);
556
578
  };
557
579
  }, [ref]);
558
- return [list, control];
580
+ return [list, // @ts-expect-error proxy type
581
+ command];
559
582
  }
560
583
 
561
584
  /**
@@ -563,7 +586,7 @@ function useFieldList(ref, config) {
563
586
  * This is particular useful when integrating dropdown and datepicker whichs
564
587
  * introduces custom input mode.
565
588
  *
566
- * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput
589
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.3/packages/conform-react/README.md#usecontrolledinput
567
590
  */
568
591
  function useControlledInput(config) {
569
592
  var _config$defaultValue4;
package/module/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { hasError, isFieldElement, parse, setFormError, shouldValidate } from '@conform-to/dom';
1
+ export { getFormElements, hasError, parse, shouldValidate } from '@conform-to/dom';
2
2
  export { useControlledInput, useFieldList, useFieldset, useForm } from './hooks.js';
3
3
  import * as helpers from './helpers.js';
4
4
  export { helpers as conform };
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.0-pre.1",
5
+ "version": "0.4.0-pre.3",
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.0-pre.1"
22
+ "@conform-to/dom": "0.4.0-pre.3"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": ">=16.8"