@conform-to/react 0.6.0-pre.0 → 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 +177 -131
- package/helpers.d.ts +10 -11
- package/helpers.js +47 -65
- package/hooks.d.ts +14 -22
- package/hooks.js +47 -44
- package/index.d.ts +2 -2
- package/index.js +2 -6
- package/module/helpers.js +45 -66
- package/module/hooks.js +48 -45
- package/module/index.js +1 -1
- 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
|
@@ -11,13 +11,12 @@
|
|
|
11
11
|
- [useFieldList](#usefieldlist)
|
|
12
12
|
- [useInputEvent](#useinputevent)
|
|
13
13
|
- [conform](#conform)
|
|
14
|
+
- [parse](#parse)
|
|
15
|
+
- [validateConstraint](#validateconstraint)
|
|
14
16
|
- [list](#list)
|
|
15
17
|
- [validate](#validate)
|
|
16
18
|
- [requestIntent](#requestintent)
|
|
17
|
-
- [
|
|
18
|
-
- [hasError](#haserror)
|
|
19
|
-
- [parse](#parse)
|
|
20
|
-
- [shouldValidate](#shouldvalidate)
|
|
19
|
+
- [isFieldElement](#isfieldelement)
|
|
21
20
|
|
|
22
21
|
<!-- /aside -->
|
|
23
22
|
|
|
@@ -27,9 +26,9 @@ By default, the browser calls the [reportValidity()](https://developer.mozilla.o
|
|
|
27
26
|
|
|
28
27
|
This hook enhances the form validation behaviour by:
|
|
29
28
|
|
|
30
|
-
- Enabling customizing
|
|
31
|
-
- Capturing
|
|
32
|
-
- 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.
|
|
33
32
|
|
|
34
33
|
```tsx
|
|
35
34
|
import { useForm } from '@conform-to/react';
|
|
@@ -53,17 +52,17 @@ function LoginForm() {
|
|
|
53
52
|
/**
|
|
54
53
|
* An object representing the initial value of the form.
|
|
55
54
|
*/
|
|
56
|
-
defaultValue: undefined
|
|
55
|
+
defaultValue: undefined,
|
|
57
56
|
|
|
58
57
|
/**
|
|
59
|
-
*
|
|
58
|
+
* The last submission result from the server
|
|
60
59
|
*/
|
|
61
|
-
|
|
60
|
+
lastSubmission: undefined,
|
|
62
61
|
|
|
63
62
|
/**
|
|
64
63
|
* An object describing the constraint of each field
|
|
65
64
|
*/
|
|
66
|
-
constraint: undefined
|
|
65
|
+
constraint: undefined,
|
|
67
66
|
|
|
68
67
|
/**
|
|
69
68
|
* Enable native validation before hydation.
|
|
@@ -90,7 +89,7 @@ function LoginForm() {
|
|
|
90
89
|
/**
|
|
91
90
|
* The submit event handler of the form.
|
|
92
91
|
*/
|
|
93
|
-
onSubmit(event, { formData, submission }) {
|
|
92
|
+
onSubmit(event, { formData, submission, action, encType, method }) {
|
|
94
93
|
// ...
|
|
95
94
|
},
|
|
96
95
|
});
|
|
@@ -165,20 +164,20 @@ function Example() {
|
|
|
165
164
|
const [form, { address }] = useForm<{ address: Address }>();
|
|
166
165
|
const { city, zipcode, street, country } = useFieldset(
|
|
167
166
|
form.ref,
|
|
168
|
-
address
|
|
167
|
+
address,
|
|
169
168
|
);
|
|
170
169
|
|
|
171
170
|
return (
|
|
172
171
|
<form {...form.props}>
|
|
173
172
|
<fieldset>
|
|
174
173
|
<legned>Address</legend>
|
|
175
|
-
<input {...conform.input(street
|
|
174
|
+
<input {...conform.input(street)} />
|
|
176
175
|
<div>{street.error}</div>
|
|
177
|
-
<input {...conform.input(zipcode
|
|
176
|
+
<input {...conform.input(zipcode)} />
|
|
178
177
|
<div>{zipcode.error}</div>
|
|
179
|
-
<input {...conform.input(city
|
|
178
|
+
<input {...conform.input(city)} />
|
|
180
179
|
<div>{city.error}</div>
|
|
181
|
-
<input {...conform.input(country
|
|
180
|
+
<input {...conform.input(country)} />
|
|
182
181
|
<div>{country.error}</div>
|
|
183
182
|
</fieldset>
|
|
184
183
|
<button>Submit</button>
|
|
@@ -204,7 +203,7 @@ function Fieldset(config: FieldConfig<Address>) {
|
|
|
204
203
|
<details>
|
|
205
204
|
<summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
|
|
206
205
|
|
|
207
|
-
**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.
|
|
208
207
|
|
|
209
208
|
```tsx
|
|
210
209
|
function ExampleForm() {
|
|
@@ -231,7 +230,7 @@ function ExampleForm() {
|
|
|
231
230
|
|
|
232
231
|
### useFieldList
|
|
233
232
|
|
|
234
|
-
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.
|
|
235
234
|
|
|
236
235
|
```tsx
|
|
237
236
|
import { useForm, useFieldList, list } from '@conform-to/react';
|
|
@@ -245,27 +244,25 @@ type Schema = {
|
|
|
245
244
|
|
|
246
245
|
function Example() {
|
|
247
246
|
const [form, { items }] = useForm<Schema>();
|
|
248
|
-
const
|
|
247
|
+
const itemsList = useFieldList(form.ref, items);
|
|
249
248
|
|
|
250
249
|
return (
|
|
251
250
|
<fieldset ref={ref}>
|
|
252
|
-
{
|
|
251
|
+
{itemsList.map((item, index) => (
|
|
253
252
|
<div key={item.key}>
|
|
254
253
|
{/* Setup an input per item */}
|
|
255
|
-
<input {...conform.input(item
|
|
254
|
+
<input {...conform.input(item)} />
|
|
256
255
|
|
|
257
256
|
{/* Error of each item */}
|
|
258
257
|
<span>{item.error}</span>
|
|
259
258
|
|
|
260
259
|
{/* Setup a delete button (Note: It is `items` not `item`) */}
|
|
261
|
-
<button {...list.remove(items.
|
|
260
|
+
<button {...list.remove(items.name, { index })}>Delete</button>
|
|
262
261
|
</div>
|
|
263
262
|
))}
|
|
264
263
|
|
|
265
264
|
{/* Setup a button that can append a new row with optional default value */}
|
|
266
|
-
<button {...list.append(items.
|
|
267
|
-
add
|
|
268
|
-
</button>
|
|
265
|
+
<button {...list.append(items.name, { defaultValue: '' })}>add</button>
|
|
269
266
|
</fieldset>
|
|
270
267
|
);
|
|
271
268
|
}
|
|
@@ -284,9 +281,9 @@ import { useState, useRef } from 'react';
|
|
|
284
281
|
|
|
285
282
|
function MuiForm() {
|
|
286
283
|
const [form, { category }] = useForm();
|
|
287
|
-
const [value, setValue] = useState(category.
|
|
284
|
+
const [value, setValue] = useState(category.defaultValue ?? '');
|
|
288
285
|
const [ref, control] = useInputEvent({
|
|
289
|
-
onReset: () => setValue(category.
|
|
286
|
+
onReset: () => setValue(category.defaultValue ?? ''),
|
|
290
287
|
});
|
|
291
288
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
292
289
|
|
|
@@ -295,7 +292,7 @@ function MuiForm() {
|
|
|
295
292
|
{/* Render a shadow input somewhere */}
|
|
296
293
|
<input
|
|
297
294
|
ref={ref}
|
|
298
|
-
{...conform.input(category
|
|
295
|
+
{...conform.input(category, { hidden: true })}
|
|
299
296
|
onChange={(e) => setValue(e.target.value)}
|
|
300
297
|
onFocus={() => inputRef.current?.focus()}
|
|
301
298
|
/>
|
|
@@ -323,9 +320,9 @@ function MuiForm() {
|
|
|
323
320
|
|
|
324
321
|
### conform
|
|
325
322
|
|
|
326
|
-
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).
|
|
327
324
|
|
|
328
|
-
You
|
|
325
|
+
You can also create a wrapper on top if you need to integrate with custom input component.
|
|
329
326
|
|
|
330
327
|
Before:
|
|
331
328
|
|
|
@@ -339,31 +336,31 @@ function Example() {
|
|
|
339
336
|
<form {...form.props}>
|
|
340
337
|
<input
|
|
341
338
|
type="text"
|
|
342
|
-
name={title.
|
|
343
|
-
form={title.
|
|
344
|
-
defaultValue={title.
|
|
345
|
-
requried={title.
|
|
346
|
-
minLength={title.
|
|
347
|
-
maxLength={title.
|
|
348
|
-
min={title.
|
|
349
|
-
max={title.
|
|
350
|
-
multiple={title.
|
|
351
|
-
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}
|
|
352
349
|
/>
|
|
353
350
|
<textarea
|
|
354
|
-
name={description.
|
|
355
|
-
form={description.
|
|
356
|
-
defaultValue={description.
|
|
357
|
-
requried={description.
|
|
358
|
-
minLength={description.
|
|
359
|
-
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}
|
|
360
357
|
/>
|
|
361
358
|
<select
|
|
362
|
-
name={category.
|
|
363
|
-
form={category.
|
|
364
|
-
defaultValue={category.
|
|
365
|
-
requried={category.
|
|
366
|
-
multiple={category.
|
|
359
|
+
name={category.name}
|
|
360
|
+
form={category.form}
|
|
361
|
+
defaultValue={category.defaultValue}
|
|
362
|
+
requried={category.required}
|
|
363
|
+
multiple={category.multiple}
|
|
367
364
|
>
|
|
368
365
|
{/* ... */}
|
|
369
366
|
</select>
|
|
@@ -382,9 +379,9 @@ function Example() {
|
|
|
382
379
|
|
|
383
380
|
return (
|
|
384
381
|
<form {...form.props}>
|
|
385
|
-
<input {...conform.input(title
|
|
386
|
-
<textarea {...conform.textarea(description
|
|
387
|
-
<select {...conform.select(category
|
|
382
|
+
<input {...conform.input(title, { type: 'text' })} />
|
|
383
|
+
<textarea {...conform.textarea(description)} />
|
|
384
|
+
<select {...conform.select(category)}>{/* ... */}</select>
|
|
388
385
|
</form>
|
|
389
386
|
);
|
|
390
387
|
}
|
|
@@ -392,9 +389,117 @@ function Example() {
|
|
|
392
389
|
|
|
393
390
|
---
|
|
394
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
|
+
|
|
395
500
|
### list
|
|
396
501
|
|
|
397
|
-
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).
|
|
398
503
|
|
|
399
504
|
```tsx
|
|
400
505
|
import { list } from '@conform-to/react';
|
|
@@ -427,7 +532,7 @@ function Example() {
|
|
|
427
532
|
|
|
428
533
|
### validate
|
|
429
534
|
|
|
430
|
-
It returns the properties required to configure
|
|
535
|
+
It returns the properties required to configure an intent button for [validation](/docs/commands.md#validation).
|
|
431
536
|
|
|
432
537
|
```tsx
|
|
433
538
|
import { validate } from '@conform-to/react';
|
|
@@ -463,7 +568,7 @@ import DragAndDrop from 'awesome-dnd-example';
|
|
|
463
568
|
|
|
464
569
|
export default function Todos() {
|
|
465
570
|
const [form, { tasks }] = useForm();
|
|
466
|
-
const taskList = useFieldList(form.ref, tasks
|
|
571
|
+
const taskList = useFieldList(form.ref, tasks);
|
|
467
572
|
|
|
468
573
|
const handleDrop = (from, to) =>
|
|
469
574
|
requestIntent(form.ref.current, list.reorder({ from, to }));
|
|
@@ -473,7 +578,7 @@ export default function Todos() {
|
|
|
473
578
|
<DragAndDrop onDrop={handleDrop}>
|
|
474
579
|
{taskList.map((task, index) => (
|
|
475
580
|
<div key={task.key}>
|
|
476
|
-
<input {...conform.input(task
|
|
581
|
+
<input {...conform.input(task)} />
|
|
477
582
|
</div>
|
|
478
583
|
))}
|
|
479
584
|
</DragAndDrop>
|
|
@@ -485,81 +590,22 @@ export default function Todos() {
|
|
|
485
590
|
|
|
486
591
|
---
|
|
487
592
|
|
|
488
|
-
###
|
|
593
|
+
### isFieldElement
|
|
489
594
|
|
|
490
|
-
|
|
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.
|
|
491
596
|
|
|
492
597
|
```tsx
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
for (const element of getFormElements(form)) {
|
|
501
|
-
switch (element.name) {
|
|
502
|
-
case 'email': {
|
|
503
|
-
if (element.validity.valueMissing) {
|
|
504
|
-
submission.error.push([element.name, 'Email is required']);
|
|
505
|
-
} else if (element.validity.typeMismatch) {
|
|
506
|
-
submission.error.push([element.name, 'Email is invalid']);
|
|
507
|
-
}
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
case 'password': {
|
|
511
|
-
if (element.validity.valueMissing) {
|
|
512
|
-
submission.error.push([element.name, 'Password is required']);
|
|
513
|
-
}
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
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
|
|
516
604
|
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
// ....
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
// ...
|
|
605
|
+
}}
|
|
606
|
+
>
|
|
607
|
+
{/* ... */}
|
|
608
|
+
</form>
|
|
609
|
+
);
|
|
526
610
|
}
|
|
527
611
|
```
|
|
528
|
-
|
|
529
|
-
---
|
|
530
|
-
|
|
531
|
-
### parse
|
|
532
|
-
|
|
533
|
-
It parses the formData based on the [naming convention](/docs/submission).
|
|
534
|
-
|
|
535
|
-
```tsx
|
|
536
|
-
import { parse } from '@conform-to/react';
|
|
537
|
-
|
|
538
|
-
const formData = new FormData();
|
|
539
|
-
const submission = parse(formData);
|
|
540
|
-
|
|
541
|
-
console.log(submission);
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
---
|
|
545
|
-
|
|
546
|
-
### shouldValidate
|
|
547
|
-
|
|
548
|
-
This helper checks if the scope of validation includes a specific field by checking the submission:
|
|
549
|
-
|
|
550
|
-
```tsx
|
|
551
|
-
import { shouldValidate } from '@conform-to/react';
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* The submission intent give us hint on what should be valdiated.
|
|
555
|
-
* If the intent is 'validate/:field', only the field with name matching must be validated.
|
|
556
|
-
* If the intent is undefined, everything should be validated (Default submission)
|
|
557
|
-
*/
|
|
558
|
-
const intent = 'validate/email';
|
|
559
|
-
|
|
560
|
-
// This will log 'true'
|
|
561
|
-
console.log(shouldValidate(intent, 'email'));
|
|
562
|
-
|
|
563
|
-
// This will log 'false'
|
|
564
|
-
console.log(shouldValidate(intent, 'password'));
|
|
565
|
-
```
|
package/helpers.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type FieldConfig,
|
|
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,11 +25,11 @@ 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;
|
|
@@ -46,12 +46,11 @@ 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
|
|
57
|
-
export { VALIDATION_UNDEFINED, VALIDATION_SKIPPED };
|
|
56
|
+
export { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED };
|
package/helpers.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
|
|
5
6
|
var dom = require('@conform-to/dom');
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -19,90 +20,72 @@ var hiddenStyle = {
|
|
|
19
20
|
whiteSpace: 'nowrap',
|
|
20
21
|
border: 0
|
|
21
22
|
};
|
|
22
|
-
function
|
|
23
|
-
var _config$
|
|
24
|
-
var
|
|
25
|
-
var attributes = {
|
|
23
|
+
function getFormControlProps(config, options) {
|
|
24
|
+
var _config$error;
|
|
25
|
+
var props = {
|
|
26
26
|
id: config.id,
|
|
27
|
-
type: options.type,
|
|
28
27
|
name: config.name,
|
|
29
28
|
form: config.form,
|
|
30
|
-
required: config.required
|
|
29
|
+
required: config.required
|
|
30
|
+
};
|
|
31
|
+
if (config.id) {
|
|
32
|
+
props.id = config.id;
|
|
33
|
+
props['aria-describedby'] = config.errorId;
|
|
34
|
+
}
|
|
35
|
+
if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
|
|
36
|
+
props['aria-invalid'] = true;
|
|
37
|
+
}
|
|
38
|
+
if (config.initialError && Object.entries(config.initialError).length > 0) {
|
|
39
|
+
props.autoFocus = true;
|
|
40
|
+
}
|
|
41
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
42
|
+
props.style = hiddenStyle;
|
|
43
|
+
props.tabIndex = -1;
|
|
44
|
+
props['aria-hidden'] = true;
|
|
45
|
+
}
|
|
46
|
+
return props;
|
|
47
|
+
}
|
|
48
|
+
function input(config) {
|
|
49
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
50
|
+
var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
|
|
51
|
+
type: options.type,
|
|
31
52
|
minLength: config.minLength,
|
|
32
53
|
maxLength: config.maxLength,
|
|
33
54
|
min: config.min,
|
|
34
55
|
max: config.max,
|
|
35
56
|
step: config.step,
|
|
36
57
|
pattern: config.pattern,
|
|
37
|
-
multiple: config.multiple
|
|
38
|
-
|
|
39
|
-
'aria-describedby': config.errorId
|
|
40
|
-
};
|
|
41
|
-
if (options !== null && options !== void 0 && options.hidden) {
|
|
42
|
-
attributes.style = hiddenStyle;
|
|
43
|
-
attributes.tabIndex = -1;
|
|
44
|
-
attributes['aria-hidden'] = true;
|
|
45
|
-
}
|
|
46
|
-
if (config.initialError && Object.entries(config.initialError).length > 0) {
|
|
47
|
-
attributes.autoFocus = true;
|
|
48
|
-
}
|
|
58
|
+
multiple: config.multiple
|
|
59
|
+
});
|
|
49
60
|
if (options.type === 'checkbox' || options.type === 'radio') {
|
|
50
61
|
var _options$value;
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
props.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
|
|
63
|
+
props.defaultChecked = config.defaultValue === props.value;
|
|
53
64
|
} else if (options.type !== 'file') {
|
|
54
|
-
|
|
65
|
+
props.defaultValue = config.defaultValue;
|
|
55
66
|
}
|
|
56
|
-
return
|
|
67
|
+
return props;
|
|
57
68
|
}
|
|
58
69
|
function select(config, options) {
|
|
59
|
-
var
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
|
|
65
|
-
required: config.required,
|
|
66
|
-
multiple: config.multiple,
|
|
67
|
-
'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
|
|
68
|
-
'aria-describedby': config.errorId
|
|
69
|
-
};
|
|
70
|
-
if (options !== null && options !== void 0 && options.hidden) {
|
|
71
|
-
attributes.style = hiddenStyle;
|
|
72
|
-
attributes.tabIndex = -1;
|
|
73
|
-
attributes['aria-hidden'] = true;
|
|
74
|
-
}
|
|
75
|
-
if (config.initialError && Object.entries(config.initialError).length > 0) {
|
|
76
|
-
attributes.autoFocus = true;
|
|
77
|
-
}
|
|
78
|
-
return attributes;
|
|
70
|
+
var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
|
|
71
|
+
defaultValue: config.defaultValue,
|
|
72
|
+
multiple: config.multiple
|
|
73
|
+
});
|
|
74
|
+
return props;
|
|
79
75
|
}
|
|
80
76
|
function textarea(config, options) {
|
|
81
|
-
var
|
|
82
|
-
|
|
83
|
-
id: config.id,
|
|
84
|
-
name: config.name,
|
|
85
|
-
form: config.form,
|
|
86
|
-
defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
|
|
87
|
-
required: config.required,
|
|
77
|
+
var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
|
|
78
|
+
defaultValue: config.defaultValue,
|
|
88
79
|
minLength: config.minLength,
|
|
89
|
-
maxLength: config.maxLength
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'aria-describedby': config.errorId
|
|
93
|
-
};
|
|
94
|
-
if (options !== null && options !== void 0 && options.hidden) {
|
|
95
|
-
attributes.style = hiddenStyle;
|
|
96
|
-
attributes.tabIndex = -1;
|
|
97
|
-
attributes['aria-hidden'] = true;
|
|
98
|
-
}
|
|
99
|
-
if (config.initialError && Object.entries(config.initialError).length > 0) {
|
|
100
|
-
attributes.autoFocus = true;
|
|
101
|
-
}
|
|
102
|
-
return attributes;
|
|
80
|
+
maxLength: config.maxLength
|
|
81
|
+
});
|
|
82
|
+
return props;
|
|
103
83
|
}
|
|
104
|
-
var intent = '__intent__';
|
|
105
84
|
|
|
85
|
+
Object.defineProperty(exports, 'INTENT', {
|
|
86
|
+
enumerable: true,
|
|
87
|
+
get: function () { return dom.INTENT; }
|
|
88
|
+
});
|
|
106
89
|
Object.defineProperty(exports, 'VALIDATION_SKIPPED', {
|
|
107
90
|
enumerable: true,
|
|
108
91
|
get: function () { return dom.VALIDATION_SKIPPED; }
|
|
@@ -112,6 +95,5 @@ Object.defineProperty(exports, 'VALIDATION_UNDEFINED', {
|
|
|
112
95
|
get: function () { return dom.VALIDATION_UNDEFINED; }
|
|
113
96
|
});
|
|
114
97
|
exports.input = input;
|
|
115
|
-
exports.intent = intent;
|
|
116
98
|
exports.select = select;
|
|
117
99
|
exports.textarea = textarea;
|