@conform-to/react 0.3.0 → 0.4.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 +99 -48
- package/helpers.js +20 -3
- package/hooks.d.ts +36 -12
- package/hooks.js +342 -160
- package/index.d.ts +1 -1
- package/index.js +12 -4
- package/module/helpers.js +20 -3
- package/module/hooks.js +343 -161
- package/module/index.js +1 -1
- package/package.json +2 -2
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](#
|
|
8
|
-
- [useFieldset](#
|
|
9
|
-
- [useFieldList](#
|
|
10
|
-
- [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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
258
|
-
console.log(
|
|
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.
|
|
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 {
|
|
563
|
+
const { category } = useFieldset(ref);
|
|
513
564
|
|
|
514
565
|
return (
|
|
515
566
|
<fieldset ref={ref}>
|
|
516
|
-
<input {...conform.input(
|
|
517
|
-
<textarea {...conform.textarea(
|
|
518
|
-
<select {...conform.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 {
|
|
580
|
+
const { category } = useFieldset(ref);
|
|
530
581
|
|
|
531
582
|
return (
|
|
532
583
|
<fieldset ref={ref}>
|
|
533
584
|
<input
|
|
534
585
|
type="text"
|
|
535
|
-
name={
|
|
536
|
-
form={
|
|
537
|
-
defaultValue={
|
|
538
|
-
requried={
|
|
539
|
-
minLength={
|
|
540
|
-
maxLength={
|
|
541
|
-
min={
|
|
542
|
-
max={
|
|
543
|
-
multiple={
|
|
544
|
-
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={
|
|
548
|
-
form={
|
|
549
|
-
defaultValue={
|
|
550
|
-
requried={
|
|
551
|
-
minLength={
|
|
552
|
-
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={
|
|
556
|
-
form={
|
|
557
|
-
defaultValue={
|
|
558
|
-
requried={
|
|
559
|
-
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 (config.initialError && config.initialError.length > 0) {
|
|
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,38 @@ function input(config) {
|
|
|
34
38
|
function select(config) {
|
|
35
39
|
var _config$defaultValue;
|
|
36
40
|
|
|
37
|
-
|
|
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
46
|
multiple: config.multiple
|
|
43
47
|
};
|
|
48
|
+
|
|
49
|
+
if (config.initialError && config.initialError.length > 0) {
|
|
50
|
+
attributes.autoFocus = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return attributes;
|
|
44
54
|
}
|
|
45
55
|
function textarea(config) {
|
|
46
56
|
var _config$defaultValue2;
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
var attributes = {
|
|
49
59
|
name: config.name,
|
|
50
60
|
form: config.form,
|
|
51
61
|
defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
|
|
52
62
|
required: config.required,
|
|
53
63
|
minLength: config.minLength,
|
|
54
|
-
maxLength: config.maxLength
|
|
64
|
+
maxLength: config.maxLength,
|
|
65
|
+
autoFocus: Boolean(config.initialError)
|
|
55
66
|
};
|
|
67
|
+
|
|
68
|
+
if (config.initialError && config.initialError.length > 0) {
|
|
69
|
+
attributes.autoFocus = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return attributes;
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
exports.input = input;
|
package/hooks.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { type FieldConfig, type
|
|
1
|
+
import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type FormState, type ListCommand, type Primitive, type Submission } from '@conform-to/dom';
|
|
2
2
|
import { type InputHTMLAttributes, type FormEvent, type RefObject } from 'react';
|
|
3
|
-
|
|
3
|
+
interface FormContext<Schema extends Record<string, any>> {
|
|
4
|
+
form: HTMLFormElement;
|
|
5
|
+
formData: FormData;
|
|
6
|
+
submission: Submission<Schema>;
|
|
7
|
+
}
|
|
8
|
+
export interface FormConfig<Schema extends Record<string, any>> {
|
|
4
9
|
/**
|
|
5
10
|
* Define when the error should be reported initially.
|
|
6
11
|
* Support "onSubmit", "onChange", "onBlur".
|
|
@@ -8,6 +13,14 @@ export interface FormConfig {
|
|
|
8
13
|
* Default to `onSubmit`.
|
|
9
14
|
*/
|
|
10
15
|
initialReport?: 'onSubmit' | 'onChange' | 'onBlur';
|
|
16
|
+
/**
|
|
17
|
+
* An object representing the initial value of the form.
|
|
18
|
+
*/
|
|
19
|
+
defaultValue?: FieldValue<Schema>;
|
|
20
|
+
/**
|
|
21
|
+
* An object describing the state from the last submission
|
|
22
|
+
*/
|
|
23
|
+
state?: FormState<Schema>;
|
|
11
24
|
/**
|
|
12
25
|
* Enable native validation before hydation.
|
|
13
26
|
*
|
|
@@ -23,12 +36,12 @@ export interface FormConfig {
|
|
|
23
36
|
/**
|
|
24
37
|
* A function to be called when the form should be (re)validated.
|
|
25
38
|
*/
|
|
26
|
-
|
|
39
|
+
onValidate?: (context: FormContext<Schema>) => boolean;
|
|
27
40
|
/**
|
|
28
41
|
* The submit event handler of the form. It will be called
|
|
29
42
|
* only when the form is considered valid.
|
|
30
43
|
*/
|
|
31
|
-
onSubmit?: (event: FormEvent<HTMLFormElement>) => void;
|
|
44
|
+
onSubmit?: (event: FormEvent<HTMLFormElement>, context: FormContext<Schema>) => void;
|
|
32
45
|
}
|
|
33
46
|
/**
|
|
34
47
|
* Properties to be applied to the form element
|
|
@@ -38,13 +51,19 @@ interface FormProps {
|
|
|
38
51
|
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
|
39
52
|
noValidate: boolean;
|
|
40
53
|
}
|
|
54
|
+
interface Form<Schema extends Record<string, any>> {
|
|
55
|
+
ref: RefObject<HTMLFormElement>;
|
|
56
|
+
error: string;
|
|
57
|
+
props: FormProps;
|
|
58
|
+
config: FieldsetConfig<Schema>;
|
|
59
|
+
}
|
|
41
60
|
/**
|
|
42
61
|
* Returns properties required to hook into form events.
|
|
43
62
|
* Applied custom validation and define when error should be reported.
|
|
44
63
|
*
|
|
45
|
-
* @see https://github.com/edmundhung/conform/tree/v0.
|
|
64
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#useform
|
|
46
65
|
*/
|
|
47
|
-
export declare function useForm(config?: FormConfig):
|
|
66
|
+
export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>;
|
|
48
67
|
/**
|
|
49
68
|
* All the information of the field, including state and config.
|
|
50
69
|
*/
|
|
@@ -70,7 +89,7 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
|
|
|
70
89
|
/**
|
|
71
90
|
* An object describing the initial error of each field
|
|
72
91
|
*/
|
|
73
|
-
initialError?:
|
|
92
|
+
initialError?: Array<[string, string]>;
|
|
74
93
|
/**
|
|
75
94
|
* An object describing the constraint of each field
|
|
76
95
|
*/
|
|
@@ -83,7 +102,7 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
|
|
|
83
102
|
/**
|
|
84
103
|
* Returns all the information about the fieldset.
|
|
85
104
|
*
|
|
86
|
-
* @see https://github.com/edmundhung/conform/tree/v0.
|
|
105
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldset
|
|
87
106
|
*/
|
|
88
107
|
export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldsetConfig<Schema>): Fieldset<Schema>;
|
|
89
108
|
export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldConfig<Schema>): Fieldset<Schema>;
|
|
@@ -110,7 +129,7 @@ interface ListControl<Schema> {
|
|
|
110
129
|
* Returns a list of key and config, with a group of helpers
|
|
111
130
|
* configuring buttons for list manipulation
|
|
112
131
|
*
|
|
113
|
-
* @see https://github.com/edmundhung/conform/tree/v0.
|
|
132
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldlist
|
|
114
133
|
*/
|
|
115
134
|
export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
|
|
116
135
|
Array<{
|
|
@@ -122,7 +141,10 @@ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormEleme
|
|
|
122
141
|
interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
123
142
|
ref: RefObject<HTMLInputElement>;
|
|
124
143
|
}
|
|
125
|
-
interface InputControl {
|
|
144
|
+
interface InputControl<Element extends {
|
|
145
|
+
focus: () => void;
|
|
146
|
+
}> {
|
|
147
|
+
ref: RefObject<Element>;
|
|
126
148
|
value: string;
|
|
127
149
|
onChange: (eventOrValue: {
|
|
128
150
|
target: {
|
|
@@ -137,7 +159,9 @@ interface InputControl {
|
|
|
137
159
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
138
160
|
* introduces custom input mode.
|
|
139
161
|
*
|
|
140
|
-
* @see https://github.com/edmundhung/conform/tree/v0.
|
|
162
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usecontrolledinput
|
|
141
163
|
*/
|
|
142
|
-
export declare function useControlledInput<
|
|
164
|
+
export declare function useControlledInput<Element extends {
|
|
165
|
+
focus: () => void;
|
|
166
|
+
} = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps, InputControl<Element>];
|
|
143
167
|
export {};
|