@conform-to/react 0.5.1 → 0.6.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 +182 -214
- package/_virtual/_rollupPluginBabelHelpers.js +17 -0
- package/helpers.d.ts +11 -11
- package/helpers.js +57 -63
- package/hooks.d.ts +24 -58
- package/hooks.js +126 -204
- package/index.d.ts +2 -2
- package/index.js +8 -17
- package/module/_virtual/_rollupPluginBabelHelpers.js +16 -1
- package/module/helpers.js +45 -63
- package/module/hooks.js +128 -205
- package/module/index.js +2 -2
- package/package.json +2 -2
- package/base.d.ts +0 -17
- package/base.js +0 -112
- package/module/base.js +0 -107
package/README.md
CHANGED
|
@@ -10,15 +10,13 @@
|
|
|
10
10
|
- [useFieldset](#usefieldset)
|
|
11
11
|
- [useFieldList](#usefieldlist)
|
|
12
12
|
- [useInputEvent](#useinputevent)
|
|
13
|
-
- [useControlledInput](#usecontrolledinput)
|
|
14
13
|
- [conform](#conform)
|
|
14
|
+
- [parse](#parse)
|
|
15
|
+
- [validateConstraint](#validateconstraint)
|
|
15
16
|
- [list](#list)
|
|
16
17
|
- [validate](#validate)
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [hasError](#haserror)
|
|
20
|
-
- [parse](#parse)
|
|
21
|
-
- [shouldValidate](#shouldvalidate)
|
|
18
|
+
- [requestIntent](#requestintent)
|
|
19
|
+
- [isFieldElement](#isfieldelement)
|
|
22
20
|
|
|
23
21
|
<!-- /aside -->
|
|
24
22
|
|
|
@@ -28,9 +26,9 @@ By default, the browser calls the [reportValidity()](https://developer.mozilla.o
|
|
|
28
26
|
|
|
29
27
|
This hook enhances the form validation behaviour by:
|
|
30
28
|
|
|
31
|
-
- Enabling customizing
|
|
32
|
-
- Capturing
|
|
33
|
-
- Preparing all properties required to configure the
|
|
29
|
+
- Enabling customizing validation logic.
|
|
30
|
+
- Capturing error message and removes the error bubbles.
|
|
31
|
+
- Preparing all properties required to configure the form elements.
|
|
34
32
|
|
|
35
33
|
```tsx
|
|
36
34
|
import { useForm } from '@conform-to/react';
|
|
@@ -43,14 +41,6 @@ function LoginForm() {
|
|
|
43
41
|
*/
|
|
44
42
|
id: undefined,
|
|
45
43
|
|
|
46
|
-
/**
|
|
47
|
-
* Validation mode.
|
|
48
|
-
* Support "client-only" or "server-validation".
|
|
49
|
-
*
|
|
50
|
-
* Default to `client-only`.
|
|
51
|
-
*/
|
|
52
|
-
mode: 'client-only',
|
|
53
|
-
|
|
54
44
|
/**
|
|
55
45
|
* Define when the error should be reported initially.
|
|
56
46
|
* Support "onSubmit", "onChange", "onBlur".
|
|
@@ -62,17 +52,17 @@ function LoginForm() {
|
|
|
62
52
|
/**
|
|
63
53
|
* An object representing the initial value of the form.
|
|
64
54
|
*/
|
|
65
|
-
defaultValue: undefined
|
|
55
|
+
defaultValue: undefined,
|
|
66
56
|
|
|
67
57
|
/**
|
|
68
|
-
*
|
|
58
|
+
* The last submission result from the server
|
|
69
59
|
*/
|
|
70
|
-
|
|
60
|
+
lastSubmission: undefined,
|
|
71
61
|
|
|
72
62
|
/**
|
|
73
63
|
* An object describing the constraint of each field
|
|
74
64
|
*/
|
|
75
|
-
constraint: undefined
|
|
65
|
+
constraint: undefined,
|
|
76
66
|
|
|
77
67
|
/**
|
|
78
68
|
* Enable native validation before hydation.
|
|
@@ -99,7 +89,7 @@ function LoginForm() {
|
|
|
99
89
|
/**
|
|
100
90
|
* The submit event handler of the form.
|
|
101
91
|
*/
|
|
102
|
-
onSubmit(event, { formData, submission }) {
|
|
92
|
+
onSubmit(event, { formData, submission, action, encType, method }) {
|
|
103
93
|
// ...
|
|
104
94
|
},
|
|
105
95
|
});
|
|
@@ -174,20 +164,20 @@ function Example() {
|
|
|
174
164
|
const [form, { address }] = useForm<{ address: Address }>();
|
|
175
165
|
const { city, zipcode, street, country } = useFieldset(
|
|
176
166
|
form.ref,
|
|
177
|
-
address
|
|
167
|
+
address,
|
|
178
168
|
);
|
|
179
169
|
|
|
180
170
|
return (
|
|
181
171
|
<form {...form.props}>
|
|
182
172
|
<fieldset>
|
|
183
173
|
<legned>Address</legend>
|
|
184
|
-
<input {...conform.input(street
|
|
174
|
+
<input {...conform.input(street)} />
|
|
185
175
|
<div>{street.error}</div>
|
|
186
|
-
<input {...conform.input(zipcode
|
|
176
|
+
<input {...conform.input(zipcode)} />
|
|
187
177
|
<div>{zipcode.error}</div>
|
|
188
|
-
<input {...conform.input(city
|
|
178
|
+
<input {...conform.input(city)} />
|
|
189
179
|
<div>{city.error}</div>
|
|
190
|
-
<input {...conform.input(country
|
|
180
|
+
<input {...conform.input(country)} />
|
|
191
181
|
<div>{country.error}</div>
|
|
192
182
|
</fieldset>
|
|
193
183
|
<button>Submit</button>
|
|
@@ -213,7 +203,7 @@ function Fieldset(config: FieldConfig<Address>) {
|
|
|
213
203
|
<details>
|
|
214
204
|
<summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
|
|
215
205
|
|
|
216
|
-
**conform** utilises the DOM as its context provider / input registry, which maintains a link between each input / button / fieldset with the form through the [form property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#properties). The ref object allows it to restrict the scope to elements associated to the same form only.
|
|
206
|
+
**conform** utilises the DOM as its context provider / input registry, which maintains a link between each input / button / fieldset with the form through the [form property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#properties). The ref object allows it to restrict the scope to form elements associated to the same form only.
|
|
217
207
|
|
|
218
208
|
```tsx
|
|
219
209
|
function ExampleForm() {
|
|
@@ -240,7 +230,7 @@ function ExampleForm() {
|
|
|
240
230
|
|
|
241
231
|
### useFieldList
|
|
242
232
|
|
|
243
|
-
This hook enables you to work with [array](/docs/configuration.md#array) and support [list](#list)
|
|
233
|
+
This hook enables you to work with [array](/docs/configuration.md#array) and support the [list](#list) intent button builder to modify a list. It can also be used with [useFieldset](#usefieldset) for [nested list](/docs/configuration.md#nested-list) at the same time.
|
|
244
234
|
|
|
245
235
|
```tsx
|
|
246
236
|
import { useForm, useFieldList, list } from '@conform-to/react';
|
|
@@ -254,27 +244,25 @@ type Schema = {
|
|
|
254
244
|
|
|
255
245
|
function Example() {
|
|
256
246
|
const [form, { items }] = useForm<Schema>();
|
|
257
|
-
const
|
|
247
|
+
const itemsList = useFieldList(form.ref, items);
|
|
258
248
|
|
|
259
249
|
return (
|
|
260
250
|
<fieldset ref={ref}>
|
|
261
|
-
{
|
|
251
|
+
{itemsList.map((item, index) => (
|
|
262
252
|
<div key={item.key}>
|
|
263
253
|
{/* Setup an input per item */}
|
|
264
|
-
<input {...conform.input(item
|
|
254
|
+
<input {...conform.input(item)} />
|
|
265
255
|
|
|
266
256
|
{/* Error of each item */}
|
|
267
257
|
<span>{item.error}</span>
|
|
268
258
|
|
|
269
259
|
{/* Setup a delete button (Note: It is `items` not `item`) */}
|
|
270
|
-
<button {...list.remove(items.
|
|
260
|
+
<button {...list.remove(items.name, { index })}>Delete</button>
|
|
271
261
|
</div>
|
|
272
262
|
))}
|
|
273
263
|
|
|
274
264
|
{/* Setup a button that can append a new row with optional default value */}
|
|
275
|
-
<button {...list.append(items.
|
|
276
|
-
add
|
|
277
|
-
</button>
|
|
265
|
+
<button {...list.append(items.name, { defaultValue: '' })}>add</button>
|
|
278
266
|
</fieldset>
|
|
279
267
|
);
|
|
280
268
|
}
|
|
@@ -293,9 +281,9 @@ import { useState, useRef } from 'react';
|
|
|
293
281
|
|
|
294
282
|
function MuiForm() {
|
|
295
283
|
const [form, { category }] = useForm();
|
|
296
|
-
const [value, setValue] = useState(category.
|
|
284
|
+
const [value, setValue] = useState(category.defaultValue ?? '');
|
|
297
285
|
const [ref, control] = useInputEvent({
|
|
298
|
-
onReset: () => setValue(category.
|
|
286
|
+
onReset: () => setValue(category.defaultValue ?? ''),
|
|
299
287
|
});
|
|
300
288
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
301
289
|
|
|
@@ -304,7 +292,7 @@ function MuiForm() {
|
|
|
304
292
|
{/* Render a shadow input somewhere */}
|
|
305
293
|
<input
|
|
306
294
|
ref={ref}
|
|
307
|
-
{...conform.input(category
|
|
295
|
+
{...conform.input(category, { hidden: true })}
|
|
308
296
|
onChange={(e) => setValue(e.target.value)}
|
|
309
297
|
onFocus={() => inputRef.current?.focus()}
|
|
310
298
|
/>
|
|
@@ -330,54 +318,11 @@ function MuiForm() {
|
|
|
330
318
|
|
|
331
319
|
---
|
|
332
320
|
|
|
333
|
-
### useControlledInput
|
|
334
|
-
|
|
335
|
-
> This API is deprecated and replaced with the [useInputEvent](#useinputevent) hook.
|
|
336
|
-
|
|
337
|
-
It returns the properties required to configure a shadow input for validation and helper to integrate it. This is particularly useful when [integrating custom input components](/docs/integrations.md#custom-input-component) like dropdown and datepicker.
|
|
338
|
-
|
|
339
|
-
```tsx
|
|
340
|
-
import { useForm, useControlledInput } from '@conform-to/react';
|
|
341
|
-
import { Select, MenuItem } from '@mui/material';
|
|
342
|
-
|
|
343
|
-
function MuiForm() {
|
|
344
|
-
const [form, { category }] = useForm();
|
|
345
|
-
const [inputProps, control] = useControlledInput(category.config);
|
|
346
|
-
|
|
347
|
-
return (
|
|
348
|
-
<form {...form.props}>
|
|
349
|
-
{/* Render a shadow input somewhere */}
|
|
350
|
-
<input {...inputProps} />
|
|
351
|
-
|
|
352
|
-
{/* MUI Select is a controlled component */}
|
|
353
|
-
<TextField
|
|
354
|
-
label="Category"
|
|
355
|
-
inputRef={control.ref}
|
|
356
|
-
value={control.value}
|
|
357
|
-
onChange={control.onChange}
|
|
358
|
-
onBlur={control.onBlur}
|
|
359
|
-
inputProps={{
|
|
360
|
-
onInvalid: control.onInvalid,
|
|
361
|
-
}}
|
|
362
|
-
select
|
|
363
|
-
>
|
|
364
|
-
<MenuItem value="">Please select</MenuItem>
|
|
365
|
-
<MenuItem value="a">Category A</MenuItem>
|
|
366
|
-
<MenuItem value="b">Category B</MenuItem>
|
|
367
|
-
<MenuItem value="c">Category C</MenuItem>
|
|
368
|
-
</TextField>
|
|
369
|
-
</form>
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
321
|
### conform
|
|
377
322
|
|
|
378
|
-
It provides several helpers to remove the boilerplate when configuring a form control.
|
|
323
|
+
It provides several helpers to remove the boilerplate when configuring a form control and derives attributes for [accessibility](/docs/accessibility.md#configuration) concerns and helps [focus management](/docs/focus-management.md#focusing-before-javascript-is-loaded).
|
|
379
324
|
|
|
380
|
-
You
|
|
325
|
+
You can also create a wrapper on top if you need to integrate with custom input component.
|
|
381
326
|
|
|
382
327
|
Before:
|
|
383
328
|
|
|
@@ -391,31 +336,31 @@ function Example() {
|
|
|
391
336
|
<form {...form.props}>
|
|
392
337
|
<input
|
|
393
338
|
type="text"
|
|
394
|
-
name={title.
|
|
395
|
-
form={title.
|
|
396
|
-
defaultValue={title.
|
|
397
|
-
requried={title.
|
|
398
|
-
minLength={title.
|
|
399
|
-
maxLength={title.
|
|
400
|
-
min={title.
|
|
401
|
-
max={title.
|
|
402
|
-
multiple={title.
|
|
403
|
-
pattern={title.
|
|
339
|
+
name={title.name}
|
|
340
|
+
form={title.form}
|
|
341
|
+
defaultValue={title.defaultValue}
|
|
342
|
+
requried={title.required}
|
|
343
|
+
minLength={title.minLength}
|
|
344
|
+
maxLength={title.maxLength}
|
|
345
|
+
min={title.min}
|
|
346
|
+
max={title.max}
|
|
347
|
+
multiple={title.multiple}
|
|
348
|
+
pattern={title.pattern}
|
|
404
349
|
/>
|
|
405
350
|
<textarea
|
|
406
|
-
name={description.
|
|
407
|
-
form={description.
|
|
408
|
-
defaultValue={description.
|
|
409
|
-
requried={description.
|
|
410
|
-
minLength={description.
|
|
411
|
-
maxLength={description.
|
|
351
|
+
name={description.name}
|
|
352
|
+
form={description.form}
|
|
353
|
+
defaultValue={description.defaultValue}
|
|
354
|
+
requried={description.required}
|
|
355
|
+
minLength={description.minLength}
|
|
356
|
+
maxLength={description.maxLength}
|
|
412
357
|
/>
|
|
413
358
|
<select
|
|
414
|
-
name={category.
|
|
415
|
-
form={category.
|
|
416
|
-
defaultValue={category.
|
|
417
|
-
requried={category.
|
|
418
|
-
multiple={category.
|
|
359
|
+
name={category.name}
|
|
360
|
+
form={category.form}
|
|
361
|
+
defaultValue={category.defaultValue}
|
|
362
|
+
requried={category.required}
|
|
363
|
+
multiple={category.multiple}
|
|
419
364
|
>
|
|
420
365
|
{/* ... */}
|
|
421
366
|
</select>
|
|
@@ -434,9 +379,9 @@ function Example() {
|
|
|
434
379
|
|
|
435
380
|
return (
|
|
436
381
|
<form {...form.props}>
|
|
437
|
-
<input {...conform.input(title
|
|
438
|
-
<textarea {...conform.textarea(description
|
|
439
|
-
<select {...conform.select(category
|
|
382
|
+
<input {...conform.input(title, { type: 'text' })} />
|
|
383
|
+
<textarea {...conform.textarea(description)} />
|
|
384
|
+
<select {...conform.select(category)}>{/* ... */}</select>
|
|
440
385
|
</form>
|
|
441
386
|
);
|
|
442
387
|
}
|
|
@@ -444,9 +389,117 @@ function Example() {
|
|
|
444
389
|
|
|
445
390
|
---
|
|
446
391
|
|
|
392
|
+
### parse
|
|
393
|
+
|
|
394
|
+
It parses the formData based on the [naming convention](/docs/configuration.md#naming-convention) with the validation result from the resolver.
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
import { parse } from '@conform-to/react';
|
|
398
|
+
|
|
399
|
+
const formData = new FormData();
|
|
400
|
+
const submission = parse(formData, {
|
|
401
|
+
resolve({ email, password }) {
|
|
402
|
+
const error: Record<string, string> = {};
|
|
403
|
+
|
|
404
|
+
if (typeof email !== 'string') {
|
|
405
|
+
error.email = 'Email is required';
|
|
406
|
+
} else if (!/^[^@]+@[^@]+$/.test(email)) {
|
|
407
|
+
error.email = 'Email is invalid';
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (typeof password !== 'string') {
|
|
411
|
+
error.password = 'Password is required';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (error.email || error.password) {
|
|
415
|
+
return { error };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
value: { email, password },
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### validateConstraint
|
|
428
|
+
|
|
429
|
+
This enable Constraint Validation with ability to enable custom constraint using data-attribute and customizing error messages. By default, the error message would be the attribute that triggered the error (e.g. `required` / `type` / 'minLength' etc).
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { useForm, validateConstraint } from '@conform-to/react';
|
|
433
|
+
import { Form } from 'react-router-dom';
|
|
434
|
+
|
|
435
|
+
export default function SignupForm() {
|
|
436
|
+
const [form, { email, password, confirmPassword }] = useForm({
|
|
437
|
+
onValidate(context) {
|
|
438
|
+
// This enables validating each field based on the validity state and custom cosntraint if defined
|
|
439
|
+
return validateConstraint(
|
|
440
|
+
...context,
|
|
441
|
+
constraint: {
|
|
442
|
+
// Define custom constraint
|
|
443
|
+
match(value, { formData, attributeValue }) {
|
|
444
|
+
// Check if the value of the field match the value of another field
|
|
445
|
+
return value === formData.get(attributeValue);
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<Form method="post" {...form.props}>
|
|
453
|
+
<div>
|
|
454
|
+
<label>Email</label>
|
|
455
|
+
<input
|
|
456
|
+
name="email"
|
|
457
|
+
type="email"
|
|
458
|
+
required
|
|
459
|
+
pattern="[^@]+@[^@]+\\.[^@]+"
|
|
460
|
+
/>
|
|
461
|
+
{email.error === 'required' ? (
|
|
462
|
+
<div>Email is required</div>
|
|
463
|
+
) : email.error === 'type' ? (
|
|
464
|
+
<div>Email is invalid</div>
|
|
465
|
+
) : null}
|
|
466
|
+
</div>
|
|
467
|
+
<div>
|
|
468
|
+
<label>Password</label>
|
|
469
|
+
<input
|
|
470
|
+
name="password"
|
|
471
|
+
type="password"
|
|
472
|
+
required
|
|
473
|
+
/>
|
|
474
|
+
{password.error === 'required' ? (
|
|
475
|
+
<div>Password is required</div>
|
|
476
|
+
) : null}
|
|
477
|
+
</div>
|
|
478
|
+
<div>
|
|
479
|
+
<label>Confirm Password</label>
|
|
480
|
+
<input
|
|
481
|
+
name="confirmPassword"
|
|
482
|
+
type="password"
|
|
483
|
+
required
|
|
484
|
+
data-constraint-match="password"
|
|
485
|
+
/>
|
|
486
|
+
{confirmPassword.error === 'required' ? (
|
|
487
|
+
<div>Confirm Password is required</div>
|
|
488
|
+
) : confirmPassword.error === 'match' ? (
|
|
489
|
+
<div>Password does not match</div>
|
|
490
|
+
) : null}
|
|
491
|
+
</div>
|
|
492
|
+
<button>Signup</button>
|
|
493
|
+
</Form>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
447
500
|
### list
|
|
448
501
|
|
|
449
|
-
It provides serveral helpers to configure
|
|
502
|
+
It provides serveral helpers to configure an intent button for [modifying a list](/docs/commands.md#modifying-a-list).
|
|
450
503
|
|
|
451
504
|
```tsx
|
|
452
505
|
import { list } from '@conform-to/react';
|
|
@@ -479,7 +532,7 @@ function Example() {
|
|
|
479
532
|
|
|
480
533
|
### validate
|
|
481
534
|
|
|
482
|
-
It returns the properties required to configure
|
|
535
|
+
It returns the properties required to configure an intent button for [validation](/docs/commands.md#validation).
|
|
483
536
|
|
|
484
537
|
```tsx
|
|
485
538
|
import { validate } from '@conform-to/react';
|
|
@@ -499,9 +552,9 @@ function Example() {
|
|
|
499
552
|
|
|
500
553
|
---
|
|
501
554
|
|
|
502
|
-
###
|
|
555
|
+
### requestIntent
|
|
503
556
|
|
|
504
|
-
It lets you [trigger
|
|
557
|
+
It lets you [trigger an intent](/docs/commands.md#triggering-an-intent) without requiring users to click on a button. It supports both [list](#list) and [validate](#validate) intent.
|
|
505
558
|
|
|
506
559
|
```tsx
|
|
507
560
|
import {
|
|
@@ -509,23 +562,23 @@ import {
|
|
|
509
562
|
useFieldList,
|
|
510
563
|
conform,
|
|
511
564
|
list,
|
|
512
|
-
|
|
565
|
+
requestIntent,
|
|
513
566
|
} from '@conform-to/react';
|
|
514
567
|
import DragAndDrop from 'awesome-dnd-example';
|
|
515
568
|
|
|
516
569
|
export default function Todos() {
|
|
517
570
|
const [form, { tasks }] = useForm();
|
|
518
|
-
const taskList = useFieldList(form.ref, tasks
|
|
571
|
+
const taskList = useFieldList(form.ref, tasks);
|
|
519
572
|
|
|
520
573
|
const handleDrop = (from, to) =>
|
|
521
|
-
|
|
574
|
+
requestIntent(form.ref.current, list.reorder({ from, to }));
|
|
522
575
|
|
|
523
576
|
return (
|
|
524
577
|
<form {...form.props}>
|
|
525
578
|
<DragAndDrop onDrop={handleDrop}>
|
|
526
579
|
{taskList.map((task, index) => (
|
|
527
580
|
<div key={task.key}>
|
|
528
|
-
<input {...conform.input(task
|
|
581
|
+
<input {...conform.input(task)} />
|
|
529
582
|
</div>
|
|
530
583
|
))}
|
|
531
584
|
</DragAndDrop>
|
|
@@ -537,107 +590,22 @@ export default function Todos() {
|
|
|
537
590
|
|
|
538
591
|
---
|
|
539
592
|
|
|
540
|
-
###
|
|
593
|
+
### isFieldElement
|
|
541
594
|
|
|
542
|
-
|
|
595
|
+
This is an utility for checking if the provided element is a form element (_input_ / _select_ / _textarea_ or _button_) which also works as a type guard.
|
|
543
596
|
|
|
544
597
|
```tsx
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
for (const element of getFormElements(form)) {
|
|
553
|
-
switch (element.name) {
|
|
554
|
-
case 'email': {
|
|
555
|
-
if (element.validity.valueMissing) {
|
|
556
|
-
submission.error.push([element.name, 'Email is required']);
|
|
557
|
-
} else if (element.validity.typeMismatch) {
|
|
558
|
-
submission.error.push([element.name, 'Email is invalid']);
|
|
559
|
-
}
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
case 'password': {
|
|
563
|
-
if (element.validity.valueMissing) {
|
|
564
|
-
submission.error.push([element.name, 'Password is required']);
|
|
565
|
-
}
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
598
|
+
function Example() {
|
|
599
|
+
return (
|
|
600
|
+
<form
|
|
601
|
+
onFocus={(event) => {
|
|
602
|
+
if (isFieldElement(event.target)) {
|
|
603
|
+
// event.target is now considered one of the form elements type
|
|
568
604
|
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
// ....
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// ...
|
|
605
|
+
}}
|
|
606
|
+
>
|
|
607
|
+
{/* ... */}
|
|
608
|
+
</form>
|
|
609
|
+
);
|
|
578
610
|
}
|
|
579
611
|
```
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
### hasError
|
|
584
|
-
|
|
585
|
-
This helper checks if there is any message defined in error array with the provided name.
|
|
586
|
-
|
|
587
|
-
```ts
|
|
588
|
-
import { hasError } from '@conform-to/react';
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Assume the error looks like this:
|
|
592
|
-
*/
|
|
593
|
-
const error = [['email', 'Email is required']];
|
|
594
|
-
|
|
595
|
-
// This will log `true`
|
|
596
|
-
console.log(hasError(error, 'email'));
|
|
597
|
-
|
|
598
|
-
// This will log `false`
|
|
599
|
-
console.log(hasError(error, 'password'));
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
---
|
|
603
|
-
|
|
604
|
-
### parse
|
|
605
|
-
|
|
606
|
-
It parses the formData based on the [naming convention](/docs/submission).
|
|
607
|
-
|
|
608
|
-
```tsx
|
|
609
|
-
import { parse } from '@conform-to/react';
|
|
610
|
-
|
|
611
|
-
const formData = new FormData();
|
|
612
|
-
const submission = parse(formData);
|
|
613
|
-
|
|
614
|
-
console.log(submission);
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
---
|
|
618
|
-
|
|
619
|
-
### shouldValidate
|
|
620
|
-
|
|
621
|
-
This helper checks if the scope of validation includes a specific field by checking the submission:
|
|
622
|
-
|
|
623
|
-
```tsx
|
|
624
|
-
import { shouldValidate } from '@conform-to/react';
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* The submission type and intent give us hint on what should be valdiated.
|
|
628
|
-
* If the type is 'validate', only the field with name matching the metadata must be validated.
|
|
629
|
-
* If the type is 'submit', everything should be validated (Default submission)
|
|
630
|
-
*/
|
|
631
|
-
const submission = {
|
|
632
|
-
context: 'validate',
|
|
633
|
-
intent: 'email',
|
|
634
|
-
value: {},
|
|
635
|
-
error: [],
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
// This will log 'true'
|
|
639
|
-
console.log(shouldValidate(submission, 'email'));
|
|
640
|
-
|
|
641
|
-
// This will log 'false'
|
|
642
|
-
console.log(shouldValidate(submission, 'password'));
|
|
643
|
-
```
|
|
@@ -24,6 +24,7 @@ function _objectSpread2(target) {
|
|
|
24
24
|
return target;
|
|
25
25
|
}
|
|
26
26
|
function _defineProperty(obj, key, value) {
|
|
27
|
+
key = _toPropertyKey(key);
|
|
27
28
|
if (key in obj) {
|
|
28
29
|
Object.defineProperty(obj, key, {
|
|
29
30
|
value: value,
|
|
@@ -36,6 +37,22 @@ function _defineProperty(obj, key, value) {
|
|
|
36
37
|
}
|
|
37
38
|
return obj;
|
|
38
39
|
}
|
|
40
|
+
function _toPrimitive(input, hint) {
|
|
41
|
+
if (typeof input !== "object" || input === null) return input;
|
|
42
|
+
var prim = input[Symbol.toPrimitive];
|
|
43
|
+
if (prim !== undefined) {
|
|
44
|
+
var res = prim.call(input, hint || "default");
|
|
45
|
+
if (typeof res !== "object") return res;
|
|
46
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
47
|
+
}
|
|
48
|
+
return (hint === "string" ? String : Number)(input);
|
|
49
|
+
}
|
|
50
|
+
function _toPropertyKey(arg) {
|
|
51
|
+
var key = _toPrimitive(arg, "string");
|
|
52
|
+
return typeof key === "symbol" ? key : String(key);
|
|
53
|
+
}
|
|
39
54
|
|
|
40
55
|
exports.defineProperty = _defineProperty;
|
|
41
56
|
exports.objectSpread2 = _objectSpread2;
|
|
57
|
+
exports.toPrimitive = _toPrimitive;
|
|
58
|
+
exports.toPropertyKey = _toPropertyKey;
|
package/helpers.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FieldConfig, type Primitive, VALIDATION_UNDEFINED, VALIDATION_SKIPPED, INTENT } from '@conform-to/dom';
|
|
2
2
|
import type { CSSProperties, HTMLInputTypeAttribute } from 'react';
|
|
3
|
-
interface
|
|
3
|
+
interface FormControlProps {
|
|
4
4
|
id?: string;
|
|
5
5
|
name: string;
|
|
6
6
|
form?: string;
|
|
@@ -8,11 +8,11 @@ interface FieldProps {
|
|
|
8
8
|
autoFocus?: boolean;
|
|
9
9
|
tabIndex?: number;
|
|
10
10
|
style?: CSSProperties;
|
|
11
|
-
'aria-invalid': boolean;
|
|
12
11
|
'aria-describedby'?: string;
|
|
12
|
+
'aria-invalid'?: boolean;
|
|
13
13
|
'aria-hidden'?: boolean;
|
|
14
14
|
}
|
|
15
|
-
interface InputProps<Schema> extends
|
|
15
|
+
interface InputProps<Schema> extends FormControlProps {
|
|
16
16
|
type?: HTMLInputTypeAttribute;
|
|
17
17
|
minLength?: number;
|
|
18
18
|
maxLength?: number;
|
|
@@ -25,16 +25,16 @@ interface InputProps<Schema> extends FieldProps {
|
|
|
25
25
|
defaultChecked?: boolean;
|
|
26
26
|
defaultValue?: string;
|
|
27
27
|
}
|
|
28
|
-
interface SelectProps extends
|
|
28
|
+
interface SelectProps extends FormControlProps {
|
|
29
29
|
defaultValue?: string | number | readonly string[] | undefined;
|
|
30
30
|
multiple?: boolean;
|
|
31
31
|
}
|
|
32
|
-
interface TextareaProps extends
|
|
32
|
+
interface TextareaProps extends FormControlProps {
|
|
33
33
|
minLength?: number;
|
|
34
34
|
maxLength?: number;
|
|
35
35
|
defaultValue?: string;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
type InputOptions = {
|
|
38
38
|
type: 'checkbox' | 'radio';
|
|
39
39
|
hidden?: boolean;
|
|
40
40
|
value?: string;
|
|
@@ -46,11 +46,11 @@ declare type InputOptions = {
|
|
|
46
46
|
export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: {
|
|
47
47
|
type: 'file';
|
|
48
48
|
}): InputProps<Schema>;
|
|
49
|
-
export declare function input<Schema extends
|
|
50
|
-
export declare function select
|
|
49
|
+
export declare function input<Schema extends Primitive>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
|
|
50
|
+
export declare function select(config: FieldConfig<Primitive | Primitive[]>, options?: {
|
|
51
51
|
hidden?: boolean;
|
|
52
52
|
}): SelectProps;
|
|
53
|
-
export declare function textarea
|
|
53
|
+
export declare function textarea(config: FieldConfig<Primitive>, options?: {
|
|
54
54
|
hidden?: boolean;
|
|
55
55
|
}): TextareaProps;
|
|
56
|
-
export {};
|
|
56
|
+
export { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED };
|