@conform-to/react 0.3.0-pre.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,15 +2,18 @@
2
2
 
3
3
  > [React](https://github.com/facebook/react) adapter for [conform](https://github.com/edmundhung/conform)
4
4
 
5
+ <!-- aside -->
6
+
5
7
  ## API Reference
6
8
 
7
- - [useForm](#useForm)
8
- - [useFieldset](#useFieldset)
9
- - [useFieldList](#useFieldList)
10
- - [useControlledInput](#useControlledInput)
9
+ - [useForm](#useform)
10
+ - [useFieldset](#usefieldset)
11
+ - [useFieldList](#usefieldlist)
12
+ - [useControlledInput](#usecontrolledinput)
13
+ - [createValidate](#createvalidate)
11
14
  - [conform](#conform)
12
15
 
13
- ---
16
+ <!-- /aside -->
14
17
 
15
18
  ### useForm
16
19
 
@@ -219,31 +222,31 @@ function BookFieldset() {
219
222
  * This would be 'Invalid ISBN' initially as specified
220
223
  * in the initialError config
221
224
  */
222
- console.log(book.error);
225
+ console.log(isbn.error);
223
226
 
224
227
  /**
225
228
  * This would be `book.isbn` instead of `isbn`
226
229
  * if the `name` option is provided
227
230
  */
228
- console.log(book.config.name);
231
+ console.log(isbn.config.name);
229
232
 
230
233
  /**
231
234
  * This would be `0340013818` if specified
232
235
  * on the `initalValue` option
233
236
  */
234
- console.log(book.config.defaultValue);
237
+ console.log(isbn.config.defaultValue);
235
238
 
236
239
  /**
237
240
  * Initial error message
238
241
  * This would be 'Invalid ISBN' if specified
239
242
  */
240
- console.log(book.config.initialError);
243
+ console.log(isbn.config.initialError);
241
244
 
242
245
  /**
243
246
  * This would be `random-form-id`
244
247
  * because of the `form` option provided
245
248
  */
246
- console.log(book.config.form);
249
+ console.log(isbn.config.form);
247
250
 
248
251
  /**
249
252
  * Constraint of the field (required, minLength etc)
@@ -254,8 +257,8 @@ function BookFieldset() {
254
257
  * pattern: '[0-9]{10,13}'
255
258
  * }
256
259
  */
257
- console.log(book.config.required);
258
- console.log(book.config.pattern);
260
+ console.log(isbn.config.required);
261
+ console.log(isbn.config.pattern);
259
262
 
260
263
  return <form {...formProps}>{/* ... */}</form>;
261
264
  }
@@ -353,7 +356,7 @@ type Collection = {
353
356
  function CollectionFieldset() {
354
357
  const ref = useRef();
355
358
  const { books } = useFieldset<Collection>(ref);
356
- const [bookList, control] = useFieldList(ref, books);
359
+ const [bookList, control] = useFieldList(ref, books.config);
357
360
 
358
361
  return (
359
362
  <fieldset ref={ref}>
@@ -392,14 +395,14 @@ import { useRef } from 'react';
392
395
  function CollectionFieldset() {
393
396
  const ref = useRef();
394
397
  const { books } = useFieldset<Collection>(ref);
395
- const [bookList, control] = useFieldList(ref, books);
398
+ const [bookList, control] = useFieldList(ref, books.config);
396
399
 
397
400
  return (
398
401
  <fieldset ref={ref}>
399
402
  {bookList.map((book, index) => (
400
403
  <div key={book.key}>
401
- {/* `book.props` is a FieldConfig object similar to `books` */}
402
- <BookFieldset {...book.config}>
404
+ {/* `book.config` is a FieldConfig object similar to `books` */}
405
+ <BookFieldset {...book.config} />
403
406
 
404
407
  {/* To setup a delete button */}
405
408
  <button {...control.remove({ index })}>Delete</button>
@@ -426,11 +429,7 @@ function BookFieldset({ name, form, defaultValue, error }) {
426
429
  error,
427
430
  });
428
431
 
429
- return (
430
- <fieldset ref={ref}>
431
- {/* ... */}
432
- </fieldset>
433
- );
432
+ return <fieldset ref={ref}>{/* ... */}</fieldset>;
434
433
  }
435
434
  ```
436
435
 
@@ -470,7 +469,7 @@ import { useRef } from 'react';
470
469
  function MuiForm() {
471
470
  const ref = useRef();
472
471
  const { category } = useFieldset(schema);
473
- const [inputProps, control] = useControlledInput(category);
472
+ const [inputProps, control] = useControlledInput(category.config);
474
473
 
475
474
  return (
476
475
  <fieldset ref={ref}>
@@ -480,6 +479,7 @@ function MuiForm() {
480
479
  {/* MUI Select is a controlled component */}
481
480
  <Select
482
481
  label="Category"
482
+ inputRef={control.ref}
483
483
  value={control.value}
484
484
  onChange={control.onChange}
485
485
  onBlur={control.onBlur}
@@ -499,6 +499,57 @@ function MuiForm() {
499
499
 
500
500
  ---
501
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>;
548
+ }
549
+ ```
550
+
551
+ ---
552
+
502
553
  ### conform
503
554
 
504
555
  It provides several helpers to configure a native input field quickly:
@@ -509,13 +560,13 @@ import { useRef } from 'react';
509
560
 
510
561
  function RandomForm() {
511
562
  const ref = useRef();
512
- const { cateogry } = useFieldset(ref);
563
+ const { category } = useFieldset(ref);
513
564
 
514
565
  return (
515
566
  <fieldset ref={ref}>
516
- <input {...conform.input(cateogry, { type: 'text' })} />
517
- <textarea {...conform.textarea(cateogry)} />
518
- <select {...conform.select(cateogry)}>{/* ... */}</select>
567
+ <input {...conform.input(category.config, { type: 'text' })} />
568
+ <textarea {...conform.textarea(category.config)} />
569
+ <select {...conform.select(category.config)}>{/* ... */}</select>
519
570
  </fieldset>
520
571
  );
521
572
  }
@@ -526,37 +577,37 @@ This is equivalent to:
526
577
  ```tsx
527
578
  function RandomForm() {
528
579
  const ref = useRef();
529
- const { cateogry } = useFieldset(ref);
580
+ const { category } = useFieldset(ref);
530
581
 
531
582
  return (
532
583
  <fieldset ref={ref}>
533
584
  <input
534
585
  type="text"
535
- name={cateogry.name}
536
- form={cateogry.form}
537
- defaultValue={cateogry.defaultValue}
538
- requried={cateogry.required}
539
- minLength={cateogry.minLength}
540
- maxLength={cateogry.maxLength}
541
- min={cateogry.min}
542
- max={cateogry.max}
543
- multiple={cateogry.multiple}
544
- pattern={cateogry.pattern}
586
+ name={category.config.name}
587
+ form={category.config.form}
588
+ defaultValue={category.config.defaultValue}
589
+ requried={category.config.required}
590
+ minLength={category.config.minLength}
591
+ maxLength={category.config.maxLength}
592
+ min={category.config.min}
593
+ max={category.config.max}
594
+ multiple={category.config.multiple}
595
+ pattern={category.config.pattern}
545
596
  >
546
597
  <textarea
547
- name={cateogry.name}
548
- form={cateogry.form}
549
- defaultValue={cateogry.defaultValue}
550
- requried={cateogry.required}
551
- minLength={cateogry.minLength}
552
- maxLength={cateogry.maxLength}
598
+ name={category.config.name}
599
+ form={category.config.form}
600
+ defaultValue={category.config.defaultValue}
601
+ requried={category.config.required}
602
+ minLength={category.config.minLength}
603
+ maxLength={category.config.maxLength}
553
604
  />
554
605
  <select
555
- name={cateogry.name}
556
- form={cateogry.form}
557
- defaultValue={cateogry.defaultValue}
558
- requried={cateogry.required}
559
- multiple={cateogry.multiple}
606
+ name={category.config.name}
607
+ form={category.config.form}
608
+ defaultValue={category.config.defaultValue}
609
+ requried={category.config.required}
610
+ multiple={category.config.multiple}
560
611
  >
561
612
  {/* ... */}
562
613
  </select>
package/helpers.js CHANGED
@@ -22,6 +22,10 @@ function input(config) {
22
22
  multiple: config.multiple
23
23
  };
24
24
 
25
+ if (typeof config.initialError !== 'undefined') {
26
+ attributes.autoFocus = true;
27
+ }
28
+
25
29
  if (isCheckboxOrRadio) {
26
30
  attributes.value = value !== null && value !== void 0 ? value : 'on';
27
31
  attributes.defaultChecked = config.defaultValue === attributes.value;
@@ -34,25 +38,39 @@ function input(config) {
34
38
  function select(config) {
35
39
  var _config$defaultValue;
36
40
 
37
- return {
41
+ var attributes = {
38
42
  name: config.name,
39
43
  form: config.form,
40
44
  defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
41
45
  required: config.required,
42
- multiple: config.multiple
46
+ multiple: config.multiple,
47
+ autoFocus: typeof config.initialError !== 'undefined' ? Boolean(config.initialError) : undefined
43
48
  };
49
+
50
+ if (typeof config.initialError !== 'undefined') {
51
+ attributes.autoFocus = true;
52
+ }
53
+
54
+ return attributes;
44
55
  }
45
56
  function textarea(config) {
46
57
  var _config$defaultValue2;
47
58
 
48
- return {
59
+ var attributes = {
49
60
  name: config.name,
50
61
  form: config.form,
51
62
  defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
52
63
  required: config.required,
53
64
  minLength: config.minLength,
54
- maxLength: config.maxLength
65
+ maxLength: config.maxLength,
66
+ autoFocus: Boolean(config.initialError)
55
67
  };
68
+
69
+ if (typeof config.initialError !== 'undefined') {
70
+ attributes.autoFocus = true;
71
+ }
72
+
73
+ return attributes;
56
74
  }
57
75
 
58
76
  exports.input = input;
package/hooks.d.ts CHANGED
@@ -42,7 +42,7 @@ interface FormProps {
42
42
  * Returns properties required to hook into form events.
43
43
  * Applied custom validation and define when error should be reported.
44
44
  *
45
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#useform
45
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#useform
46
46
  */
47
47
  export declare function useForm(config?: FormConfig): FormProps;
48
48
  /**
@@ -83,7 +83,7 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
83
83
  /**
84
84
  * Returns all the information about the fieldset.
85
85
  *
86
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usefieldset
86
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldset
87
87
  */
88
88
  export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldsetConfig<Schema>): Fieldset<Schema>;
89
89
  export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldConfig<Schema>): Fieldset<Schema>;
@@ -110,7 +110,7 @@ interface ListControl<Schema> {
110
110
  * Returns a list of key and config, with a group of helpers
111
111
  * configuring buttons for list manipulation
112
112
  *
113
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usefieldlist
113
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist
114
114
  */
115
115
  export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
116
116
  Array<{
@@ -122,7 +122,10 @@ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormEleme
122
122
  interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
123
123
  ref: RefObject<HTMLInputElement>;
124
124
  }
125
- interface InputControl {
125
+ interface InputControl<Element extends {
126
+ focus: () => void;
127
+ }> {
128
+ ref: RefObject<Element>;
126
129
  value: string;
127
130
  onChange: (eventOrValue: {
128
131
  target: {
@@ -137,7 +140,9 @@ interface InputControl {
137
140
  * This is particular useful when integrating dropdown and datepicker whichs
138
141
  * introduces custom input mode.
139
142
  *
140
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usecontrolledinput
143
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput
141
144
  */
142
- export declare function useControlledInput<Schema extends Primitive = Primitive>(field: FieldConfig<Schema>): [ShadowInputProps, InputControl];
145
+ export declare function useControlledInput<Element extends {
146
+ focus: () => void;
147
+ } = HTMLInputElement, Schema extends Primitive = Primitive>(field: FieldConfig<Schema>): [ShadowInputProps, InputControl<Element>];
143
148
  export {};
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.3.0-pre.0/packages/conform-react/README.md#useform
14
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#useform
15
15
  */
16
16
  function useForm() {
17
17
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -125,10 +125,17 @@ function useForm() {
125
125
  */
126
126
 
127
127
  if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && !event.defaultPrevented) {
128
- // Mark all fields as touched
128
+ var focused = false;
129
+
129
130
  for (var field of form.elements) {
130
131
  if (dom.isFieldElement(field)) {
131
- field.dataset.conformTouched = 'true';
132
+ // Mark the field as touched
133
+ field.dataset.conformTouched = 'true'; // Focus on the first invalid field
134
+
135
+ if (!focused && !field.validity.valid && field.tagName !== 'BUTTON') {
136
+ field.focus();
137
+ focused = true;
138
+ }
132
139
  }
133
140
  } // Check the validity of the form
134
141
 
@@ -303,7 +310,7 @@ function useFieldset(ref, config) {
303
310
 
304
311
  return new Proxy({}, {
305
312
  get(_target, key) {
306
- var _constraint, _config$defaultValue, _config$initialError3, _config$initialError4, _error$key;
313
+ var _constraint, _config$defaultValue, _config$initialError$, _config$initialError3, _config$initialError4, _config$initialError5, _config$initialError6, _error$key;
307
314
 
308
315
  if (typeof key !== 'string') {
309
316
  return;
@@ -315,7 +322,7 @@ function useFieldset(ref, config) {
315
322
  name: config !== null && config !== void 0 && config.name ? "".concat(config.name, ".").concat(key) : key,
316
323
  form: config === null || config === void 0 ? void 0 : config.form,
317
324
  defaultValue: config === null || config === void 0 ? void 0 : (_config$defaultValue = config.defaultValue) === null || _config$defaultValue === void 0 ? void 0 : _config$defaultValue[key],
318
- initialError: config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details
325
+ initialError: (_config$initialError$ = config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details) !== null && _config$initialError$ !== void 0 ? _config$initialError$ : config === null || config === void 0 ? void 0 : (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[key]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.message
319
326
  }, constraint),
320
327
  error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : ''
321
328
  };
@@ -329,7 +336,7 @@ function useFieldset(ref, config) {
329
336
  * Returns a list of key and config, with a group of helpers
330
337
  * configuring buttons for list manipulation
331
338
  *
332
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usefieldlist
339
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist
333
340
  */
334
341
  function useFieldList(ref, config) {
335
342
  var [entries, setEntries] = react.useState(() => {
@@ -338,7 +345,7 @@ function useFieldList(ref, config) {
338
345
  return Object.entries((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [undefined]);
339
346
  });
340
347
  var list = entries.map((_ref, index) => {
341
- var _config$defaultValue3, _config$initialError5, _config$initialError6;
348
+ var _config$defaultValue3, _config$initialError7, _config$initialError8;
342
349
 
343
350
  var [key, defaultValue] = _ref;
344
351
  return {
@@ -346,7 +353,7 @@ function useFieldList(ref, config) {
346
353
  config: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), {}, {
347
354
  name: "".concat(config.name, "[").concat(index, "]"),
348
355
  defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue3 = config.defaultValue) === null || _config$defaultValue3 === void 0 ? void 0 : _config$defaultValue3[index],
349
- initialError: (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[index]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.details
356
+ initialError: (_config$initialError7 = config.initialError) === null || _config$initialError7 === void 0 ? void 0 : (_config$initialError8 = _config$initialError7[index]) === null || _config$initialError8 === void 0 ? void 0 : _config$initialError8.details
350
357
  })
351
358
  };
352
359
  });
@@ -448,12 +455,13 @@ function useFieldList(ref, config) {
448
455
  * This is particular useful when integrating dropdown and datepicker whichs
449
456
  * introduces custom input mode.
450
457
  *
451
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usecontrolledinput
458
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput
452
459
  */
453
460
  function useControlledInput(field) {
454
461
  var _field$defaultValue;
455
462
 
456
463
  var ref = react.useRef(null);
464
+ var inputRef = react.useRef(null);
457
465
  var [value, setValue] = react.useState("".concat((_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : ''));
458
466
 
459
467
  var handleChange = eventOrValue => {
@@ -483,10 +491,28 @@ function useControlledInput(field) {
483
491
 
484
492
  return [_rollupPluginBabelHelpers.objectSpread2({
485
493
  ref,
486
- hidden: true
494
+ style: {
495
+ position: 'absolute',
496
+ width: '1px',
497
+ height: '1px',
498
+ padding: 0,
499
+ margin: '-1px',
500
+ overflow: 'hidden',
501
+ clip: 'rect(0,0,0,0)',
502
+ whiteSpace: 'nowrap',
503
+ borderWidth: 0
504
+ },
505
+
506
+ onFocus() {
507
+ var _inputRef$current;
508
+
509
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
510
+ }
511
+
487
512
  }, helpers.input(field, {
488
513
  type: 'text'
489
514
  })), {
515
+ ref: inputRef,
490
516
  value,
491
517
  onChange: handleChange,
492
518
  onBlur: handleBlur,
package/module/helpers.js CHANGED
@@ -18,6 +18,10 @@ function input(config) {
18
18
  multiple: config.multiple
19
19
  };
20
20
 
21
+ if (typeof config.initialError !== 'undefined') {
22
+ attributes.autoFocus = true;
23
+ }
24
+
21
25
  if (isCheckboxOrRadio) {
22
26
  attributes.value = value !== null && value !== void 0 ? value : 'on';
23
27
  attributes.defaultChecked = config.defaultValue === attributes.value;
@@ -30,25 +34,39 @@ function input(config) {
30
34
  function select(config) {
31
35
  var _config$defaultValue;
32
36
 
33
- return {
37
+ var attributes = {
34
38
  name: config.name,
35
39
  form: config.form,
36
40
  defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
37
41
  required: config.required,
38
- multiple: config.multiple
42
+ multiple: config.multiple,
43
+ autoFocus: typeof config.initialError !== 'undefined' ? Boolean(config.initialError) : undefined
39
44
  };
45
+
46
+ if (typeof config.initialError !== 'undefined') {
47
+ attributes.autoFocus = true;
48
+ }
49
+
50
+ return attributes;
40
51
  }
41
52
  function textarea(config) {
42
53
  var _config$defaultValue2;
43
54
 
44
- return {
55
+ var attributes = {
45
56
  name: config.name,
46
57
  form: config.form,
47
58
  defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
48
59
  required: config.required,
49
60
  minLength: config.minLength,
50
- maxLength: config.maxLength
61
+ maxLength: config.maxLength,
62
+ autoFocus: Boolean(config.initialError)
51
63
  };
64
+
65
+ if (typeof config.initialError !== 'undefined') {
66
+ attributes.autoFocus = true;
67
+ }
68
+
69
+ return attributes;
52
70
  }
53
71
 
54
72
  export { input, select, textarea };
package/module/hooks.js CHANGED
@@ -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.3.0-pre.0/packages/conform-react/README.md#useform
10
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#useform
11
11
  */
12
12
  function useForm() {
13
13
  var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -121,10 +121,17 @@ function useForm() {
121
121
  */
122
122
 
123
123
  if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && !event.defaultPrevented) {
124
- // Mark all fields as touched
124
+ var focused = false;
125
+
125
126
  for (var field of form.elements) {
126
127
  if (isFieldElement(field)) {
127
- field.dataset.conformTouched = 'true';
128
+ // Mark the field as touched
129
+ field.dataset.conformTouched = 'true'; // Focus on the first invalid field
130
+
131
+ if (!focused && !field.validity.valid && field.tagName !== 'BUTTON') {
132
+ field.focus();
133
+ focused = true;
134
+ }
128
135
  }
129
136
  } // Check the validity of the form
130
137
 
@@ -299,7 +306,7 @@ function useFieldset(ref, config) {
299
306
 
300
307
  return new Proxy({}, {
301
308
  get(_target, key) {
302
- var _constraint, _config$defaultValue, _config$initialError3, _config$initialError4, _error$key;
309
+ var _constraint, _config$defaultValue, _config$initialError$, _config$initialError3, _config$initialError4, _config$initialError5, _config$initialError6, _error$key;
303
310
 
304
311
  if (typeof key !== 'string') {
305
312
  return;
@@ -311,7 +318,7 @@ function useFieldset(ref, config) {
311
318
  name: config !== null && config !== void 0 && config.name ? "".concat(config.name, ".").concat(key) : key,
312
319
  form: config === null || config === void 0 ? void 0 : config.form,
313
320
  defaultValue: config === null || config === void 0 ? void 0 : (_config$defaultValue = config.defaultValue) === null || _config$defaultValue === void 0 ? void 0 : _config$defaultValue[key],
314
- initialError: config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details
321
+ initialError: (_config$initialError$ = config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details) !== null && _config$initialError$ !== void 0 ? _config$initialError$ : config === null || config === void 0 ? void 0 : (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[key]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.message
315
322
  }, constraint),
316
323
  error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : ''
317
324
  };
@@ -325,7 +332,7 @@ function useFieldset(ref, config) {
325
332
  * Returns a list of key and config, with a group of helpers
326
333
  * configuring buttons for list manipulation
327
334
  *
328
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usefieldlist
335
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist
329
336
  */
330
337
  function useFieldList(ref, config) {
331
338
  var [entries, setEntries] = useState(() => {
@@ -334,7 +341,7 @@ function useFieldList(ref, config) {
334
341
  return Object.entries((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [undefined]);
335
342
  });
336
343
  var list = entries.map((_ref, index) => {
337
- var _config$defaultValue3, _config$initialError5, _config$initialError6;
344
+ var _config$defaultValue3, _config$initialError7, _config$initialError8;
338
345
 
339
346
  var [key, defaultValue] = _ref;
340
347
  return {
@@ -342,7 +349,7 @@ function useFieldList(ref, config) {
342
349
  config: _objectSpread2(_objectSpread2({}, config), {}, {
343
350
  name: "".concat(config.name, "[").concat(index, "]"),
344
351
  defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue3 = config.defaultValue) === null || _config$defaultValue3 === void 0 ? void 0 : _config$defaultValue3[index],
345
- initialError: (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[index]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.details
352
+ initialError: (_config$initialError7 = config.initialError) === null || _config$initialError7 === void 0 ? void 0 : (_config$initialError8 = _config$initialError7[index]) === null || _config$initialError8 === void 0 ? void 0 : _config$initialError8.details
346
353
  })
347
354
  };
348
355
  });
@@ -444,12 +451,13 @@ function useFieldList(ref, config) {
444
451
  * This is particular useful when integrating dropdown and datepicker whichs
445
452
  * introduces custom input mode.
446
453
  *
447
- * @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usecontrolledinput
454
+ * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput
448
455
  */
449
456
  function useControlledInput(field) {
450
457
  var _field$defaultValue;
451
458
 
452
459
  var ref = useRef(null);
460
+ var inputRef = useRef(null);
453
461
  var [value, setValue] = useState("".concat((_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : ''));
454
462
 
455
463
  var handleChange = eventOrValue => {
@@ -479,10 +487,28 @@ function useControlledInput(field) {
479
487
 
480
488
  return [_objectSpread2({
481
489
  ref,
482
- hidden: true
490
+ style: {
491
+ position: 'absolute',
492
+ width: '1px',
493
+ height: '1px',
494
+ padding: 0,
495
+ margin: '-1px',
496
+ overflow: 'hidden',
497
+ clip: 'rect(0,0,0,0)',
498
+ whiteSpace: 'nowrap',
499
+ borderWidth: 0
500
+ },
501
+
502
+ onFocus() {
503
+ var _inputRef$current;
504
+
505
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
506
+ }
507
+
483
508
  }, input(field, {
484
509
  type: 'text'
485
510
  })), {
511
+ ref: inputRef,
486
512
  value,
487
513
  onChange: handleChange,
488
514
  onBlur: handleBlur,
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.3.0-pre.0",
5
+ "version": "0.3.1",
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.3.0-pre.0"
22
+ "@conform-to/dom": "0.3.1"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": ">=16.8"