@conform-to/react 0.4.0 → 0.5.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 +58 -60
- package/base.d.ts +17 -0
- package/base.js +112 -0
- package/helpers.d.ts +46 -7
- package/helpers.js +6 -9
- package/hooks.d.ts +5 -4
- package/hooks.js +61 -67
- package/index.d.ts +1 -1
- package/module/base.js +107 -0
- package/module/helpers.js +6 -9
- package/module/hooks.js +62 -68
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -324,7 +324,7 @@ function ExampleForm() {
|
|
|
324
324
|
|
|
325
325
|
### useFieldList
|
|
326
326
|
|
|
327
|
-
It returns a list of key and
|
|
327
|
+
It returns a list of key, config and error, with helpers to configure [list command](/docs/submission.md#list-command) button.
|
|
328
328
|
|
|
329
329
|
```tsx
|
|
330
330
|
import { useFieldset, useFieldList } from '@conform-to/react';
|
|
@@ -333,33 +333,24 @@ import { useRef } from 'react';
|
|
|
333
333
|
/**
|
|
334
334
|
* Consider the schema as follow:
|
|
335
335
|
*/
|
|
336
|
-
type
|
|
337
|
-
|
|
338
|
-
isbn: string;
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
type Collection = {
|
|
342
|
-
books: Book[];
|
|
336
|
+
type Schema = {
|
|
337
|
+
list: string[];
|
|
343
338
|
};
|
|
344
339
|
|
|
345
340
|
function CollectionFieldset() {
|
|
346
|
-
const ref = useRef();
|
|
347
|
-
const
|
|
348
|
-
const [
|
|
341
|
+
const ref = useRef<HTMLFieldsetElement>(null);
|
|
342
|
+
const fieldset = useFieldset<Collection>(ref);
|
|
343
|
+
const [list, command] = useFieldList(ref, fieldset.list.config);
|
|
349
344
|
|
|
350
345
|
return (
|
|
351
346
|
<fieldset ref={ref}>
|
|
352
|
-
{
|
|
353
|
-
<div key={
|
|
354
|
-
{/*
|
|
355
|
-
<input
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
<input
|
|
360
|
-
name={`${book.config.name}.isbn`}
|
|
361
|
-
defaultValue={book.config.defaultValue.isbn}
|
|
362
|
-
/>
|
|
347
|
+
{list.map((item, index) => (
|
|
348
|
+
<div key={item.key}>
|
|
349
|
+
{/* Setup an input per item */}
|
|
350
|
+
<input {...conform.input(item.config)} />
|
|
351
|
+
|
|
352
|
+
{/* Error of each book */}
|
|
353
|
+
<span>{item.error}</span>
|
|
363
354
|
|
|
364
355
|
{/* To setup a delete button */}
|
|
365
356
|
<button {...command.remove({ index })}>Delete</button>
|
|
@@ -367,79 +358,86 @@ function CollectionFieldset() {
|
|
|
367
358
|
))}
|
|
368
359
|
|
|
369
360
|
{/* To setup a button that can append a new row with optional default value */}
|
|
370
|
-
<button {...command.append({ defaultValue:
|
|
371
|
-
add
|
|
372
|
-
</button>
|
|
361
|
+
<button {...command.append({ defaultValue: '' })}>add</button>
|
|
373
362
|
</fieldset>
|
|
374
363
|
);
|
|
375
364
|
}
|
|
376
365
|
```
|
|
377
366
|
|
|
378
|
-
This hook can also be used in combination with `useFieldset`
|
|
367
|
+
This hook can also be used in combination with `useFieldset` for nested list:
|
|
379
368
|
|
|
380
369
|
```tsx
|
|
381
|
-
import {
|
|
370
|
+
import {
|
|
371
|
+
type FieldConfig,
|
|
372
|
+
useForm,
|
|
373
|
+
useFieldset,
|
|
374
|
+
useFieldList,
|
|
375
|
+
} from '@conform-to/react';
|
|
382
376
|
import { useRef } from 'react';
|
|
383
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Consider the schema as follow:
|
|
380
|
+
*/
|
|
381
|
+
type Schema = {
|
|
382
|
+
list: Array<Item>;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
type Item = {
|
|
386
|
+
title: string;
|
|
387
|
+
description: string;
|
|
388
|
+
};
|
|
389
|
+
|
|
384
390
|
function CollectionFieldset() {
|
|
385
|
-
const ref = useRef();
|
|
386
|
-
const
|
|
387
|
-
const [
|
|
391
|
+
const ref = useRef<HTMLFieldsetElement>(null);
|
|
392
|
+
const fieldset = useFieldset<Collection>(ref);
|
|
393
|
+
const [list, command] = useFieldList(ref, fieldset.list.config);
|
|
388
394
|
|
|
389
395
|
return (
|
|
390
396
|
<fieldset ref={ref}>
|
|
391
|
-
{
|
|
392
|
-
<div key={
|
|
393
|
-
{/*
|
|
394
|
-
<
|
|
395
|
-
|
|
396
|
-
{/* To setup a delete button */}
|
|
397
|
-
<button {...command.remove({ index })}>Delete</button>
|
|
397
|
+
{list.map((item, index) => (
|
|
398
|
+
<div key={item.key}>
|
|
399
|
+
{/* Pass the item config to another fieldset*/}
|
|
400
|
+
<ItemFieldset {...item.config} />
|
|
398
401
|
</div>
|
|
399
402
|
))}
|
|
400
|
-
|
|
401
|
-
{/* To setup a button that can append a new row */}
|
|
402
|
-
<button {...command.append()}>add</button>
|
|
403
403
|
</fieldset>
|
|
404
404
|
);
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
* options with the component props instead
|
|
411
|
-
*/
|
|
412
|
-
function BookFieldset({ name, form, defaultValue, error }) {
|
|
413
|
-
const ref = useRef();
|
|
414
|
-
const { name, isbn } = useFieldset(ref, {
|
|
415
|
-
name,
|
|
416
|
-
form,
|
|
417
|
-
defaultValue,
|
|
418
|
-
error,
|
|
419
|
-
});
|
|
407
|
+
function ItemFieldset(config: FieldConfig<Item>) {
|
|
408
|
+
const ref = useRef<HTMLFieldsetElement>(null);
|
|
409
|
+
const { title, description } = useFieldset(ref, config);
|
|
420
410
|
|
|
421
|
-
return
|
|
411
|
+
return (
|
|
412
|
+
<fieldset ref={ref}>
|
|
413
|
+
<input {...conform.input(title.config)} />
|
|
414
|
+
<span>{title.error}</span>
|
|
415
|
+
|
|
416
|
+
<input {...conform.input(description.config)} />
|
|
417
|
+
<span>{description.error}</span>
|
|
418
|
+
</fieldset>
|
|
419
|
+
);
|
|
422
420
|
}
|
|
423
421
|
```
|
|
424
422
|
|
|
425
423
|
<details>
|
|
426
|
-
<summary>What can I do with `
|
|
424
|
+
<summary>What can I do with `command`?</summary>
|
|
427
425
|
|
|
428
426
|
```tsx
|
|
429
427
|
// To append a new row with optional defaultValue
|
|
430
|
-
<button {...
|
|
428
|
+
<button {...command.append({ defaultValue })}>Append</button>;
|
|
431
429
|
|
|
432
430
|
// To prepend a new row with optional defaultValue
|
|
433
|
-
<button {...
|
|
431
|
+
<button {...command.prepend({ defaultValue })}>Prepend</button>;
|
|
434
432
|
|
|
435
433
|
// To remove a row by index
|
|
436
|
-
<button {...
|
|
434
|
+
<button {...command.remove({ index })}>Remove</button>;
|
|
437
435
|
|
|
438
436
|
// To replace a row with another defaultValue
|
|
439
|
-
<button {...
|
|
437
|
+
<button {...command.replace({ index, defaultValue })}>Replace</button>;
|
|
440
438
|
|
|
441
439
|
// To reorder a particular row to an another index
|
|
442
|
-
<button {...
|
|
440
|
+
<button {...command.reorder({ from, to })}>Reorder</button>;
|
|
443
441
|
```
|
|
444
442
|
|
|
445
443
|
</details>
|
package/base.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type InputHTMLAttributes, type RefObject } from 'react';
|
|
2
|
+
declare type EventLikeOrString = {
|
|
3
|
+
target: {
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
} | string;
|
|
7
|
+
declare type InputControl = {
|
|
8
|
+
onChange: (eventLikeOrString: EventLikeOrString) => void;
|
|
9
|
+
onInput: (eventLikeOrString: EventLikeOrString) => void;
|
|
10
|
+
onFocus: () => void;
|
|
11
|
+
onBlur: () => void;
|
|
12
|
+
};
|
|
13
|
+
export declare function useInputControl(ref: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>): InputControl;
|
|
14
|
+
export declare const BaseInput: import("react").ForwardRefExoticComponent<{
|
|
15
|
+
name: string;
|
|
16
|
+
} & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "type" | "value" | "defaultValue"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
17
|
+
export {};
|
package/base.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
|
|
8
|
+
var _excluded = ["hidden", "className", "style"];
|
|
9
|
+
/**
|
|
10
|
+
* Triggering react custom change event
|
|
11
|
+
* Solution based on dom-testing-library
|
|
12
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
13
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
14
|
+
*/
|
|
15
|
+
function setNativeValue(element, value) {
|
|
16
|
+
var {
|
|
17
|
+
set: valueSetter
|
|
18
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
19
|
+
var prototype = Object.getPrototypeOf(element);
|
|
20
|
+
var {
|
|
21
|
+
set: prototypeValueSetter
|
|
22
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
23
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
24
|
+
prototypeValueSetter.call(element, value);
|
|
25
|
+
} else {
|
|
26
|
+
if (valueSetter) {
|
|
27
|
+
valueSetter.call(element, value);
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error('The given element does not have a value setter');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function useInputControl(ref) {
|
|
34
|
+
function getInputElement() {
|
|
35
|
+
var $input = ref.current;
|
|
36
|
+
if (!$input) {
|
|
37
|
+
console.warn('input ref is not available; Maybe you forget to setup the ref?');
|
|
38
|
+
}
|
|
39
|
+
return $input;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
onChange(eventLikeOrString) {
|
|
43
|
+
var $input = getInputElement();
|
|
44
|
+
var value = typeof eventLikeOrString === 'string' ? eventLikeOrString : eventLikeOrString.target.value;
|
|
45
|
+
if ($input && $input.value !== value) {
|
|
46
|
+
$input.dispatchEvent(new InputEvent('beforeinput', {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
cancelable: true
|
|
49
|
+
}));
|
|
50
|
+
setNativeValue($input, value);
|
|
51
|
+
$input.dispatchEvent(new InputEvent('input', {
|
|
52
|
+
bubbles: true,
|
|
53
|
+
cancelable: true
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
onInput(eventLikeOrString) {
|
|
58
|
+
this.onChange(eventLikeOrString);
|
|
59
|
+
},
|
|
60
|
+
onFocus() {
|
|
61
|
+
var $input = getInputElement();
|
|
62
|
+
if ($input) {
|
|
63
|
+
$input.dispatchEvent(new FocusEvent('focusin', {
|
|
64
|
+
bubbles: true,
|
|
65
|
+
cancelable: true
|
|
66
|
+
}));
|
|
67
|
+
$input.dispatchEvent(new FocusEvent('focus', {
|
|
68
|
+
cancelable: true
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
onBlur() {
|
|
73
|
+
var $input = getInputElement();
|
|
74
|
+
if ($input) {
|
|
75
|
+
$input.dispatchEvent(new FocusEvent('focusout', {
|
|
76
|
+
bubbles: true,
|
|
77
|
+
cancelable: true
|
|
78
|
+
}));
|
|
79
|
+
$input.dispatchEvent(new FocusEvent('blur', {
|
|
80
|
+
cancelable: true
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function BaseInputImpl(_ref, ref) {
|
|
87
|
+
var {
|
|
88
|
+
hidden = true,
|
|
89
|
+
className,
|
|
90
|
+
style
|
|
91
|
+
} = _ref,
|
|
92
|
+
props = _rollupPluginBabelHelpers.objectWithoutProperties(_ref, _excluded);
|
|
93
|
+
return /*#__PURE__*/react.createElement('input', _rollupPluginBabelHelpers.objectSpread2({
|
|
94
|
+
ref,
|
|
95
|
+
className: hidden ? '' : className,
|
|
96
|
+
style: hidden ? {
|
|
97
|
+
position: 'absolute',
|
|
98
|
+
width: '1px',
|
|
99
|
+
height: '1px',
|
|
100
|
+
padding: 0,
|
|
101
|
+
margin: '-1px',
|
|
102
|
+
overflow: 'hidden',
|
|
103
|
+
clip: 'rect(0,0,0,0)',
|
|
104
|
+
whiteSpace: 'nowrap',
|
|
105
|
+
borderWidth: 0
|
|
106
|
+
} : style
|
|
107
|
+
}, props));
|
|
108
|
+
}
|
|
109
|
+
var BaseInput = /*#__PURE__*/react.forwardRef(BaseInputImpl);
|
|
110
|
+
|
|
111
|
+
exports.BaseInput = BaseInput;
|
|
112
|
+
exports.useInputControl = useInputControl;
|
package/helpers.d.ts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { FieldConfig } from '@conform-to/dom';
|
|
2
|
+
import type { HTMLInputTypeAttribute } from 'react';
|
|
3
|
+
interface FieldProps {
|
|
4
|
+
name: string;
|
|
5
|
+
form?: string;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
autoFocus?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface InputProps<Schema> extends FieldProps {
|
|
10
|
+
type?: HTMLInputTypeAttribute;
|
|
11
|
+
minLength?: number;
|
|
12
|
+
maxLength?: number;
|
|
13
|
+
min?: Schema extends number ? number : string | number;
|
|
14
|
+
max?: Schema extends number ? number : string | number;
|
|
15
|
+
step?: Schema extends number ? number : string | number;
|
|
16
|
+
pattern?: string;
|
|
17
|
+
multiple?: boolean;
|
|
5
18
|
value?: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
defaultChecked?: boolean;
|
|
20
|
+
defaultValue?: string;
|
|
21
|
+
}
|
|
22
|
+
interface SelectProps extends FieldProps {
|
|
23
|
+
defaultValue?: string | number | readonly string[] | undefined;
|
|
24
|
+
multiple?: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface TextareaProps extends FieldProps {
|
|
27
|
+
minLength?: number;
|
|
28
|
+
maxLength?: number;
|
|
29
|
+
defaultValue?: string;
|
|
30
|
+
}
|
|
31
|
+
declare type InputOptions = {
|
|
32
|
+
type: 'checkbox' | 'radio';
|
|
33
|
+
value?: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'file';
|
|
36
|
+
value?: never;
|
|
37
|
+
} | {
|
|
38
|
+
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden' | 'file'>;
|
|
39
|
+
value?: never;
|
|
40
|
+
};
|
|
41
|
+
export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: {
|
|
42
|
+
type: 'file';
|
|
43
|
+
}): InputProps<Schema>;
|
|
44
|
+
export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
|
|
45
|
+
export declare function select<Schema>(config: FieldConfig<Schema>): SelectProps;
|
|
46
|
+
export declare function textarea<Schema>(config: FieldConfig<Schema>): TextareaProps;
|
|
47
|
+
export {};
|
package/helpers.js
CHANGED
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
function input(config) {
|
|
6
|
-
var {
|
|
7
|
-
type,
|
|
8
|
-
value
|
|
9
|
-
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
10
|
-
var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
|
|
6
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
11
7
|
var attributes = {
|
|
12
|
-
type,
|
|
8
|
+
type: options.type,
|
|
13
9
|
name: config.name,
|
|
14
10
|
form: config.form,
|
|
15
11
|
required: config.required,
|
|
@@ -24,10 +20,11 @@ function input(config) {
|
|
|
24
20
|
if (config.initialError && config.initialError.length > 0) {
|
|
25
21
|
attributes.autoFocus = true;
|
|
26
22
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
23
|
+
if (options.type === 'checkbox' || options.type === 'radio') {
|
|
24
|
+
var _options$value;
|
|
25
|
+
attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
|
|
29
26
|
attributes.defaultChecked = config.defaultValue === attributes.value;
|
|
30
|
-
} else {
|
|
27
|
+
} else if (options.type !== 'file') {
|
|
31
28
|
attributes.defaultValue = config.defaultValue;
|
|
32
29
|
}
|
|
33
30
|
return attributes;
|
package/hooks.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ interface Form<Schema extends Record<string, any>> {
|
|
|
66
66
|
* Returns properties required to hook into form events.
|
|
67
67
|
* Applied custom validation and define when error should be reported.
|
|
68
68
|
*
|
|
69
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
69
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#useform
|
|
70
70
|
*/
|
|
71
71
|
export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>;
|
|
72
72
|
/**
|
|
@@ -107,7 +107,7 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
|
|
|
107
107
|
/**
|
|
108
108
|
* Returns all the information about the fieldset.
|
|
109
109
|
*
|
|
110
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
110
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldset
|
|
111
111
|
*/
|
|
112
112
|
export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldsetConfig<Schema>): Fieldset<Schema>;
|
|
113
113
|
export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Schema>): Fieldset<Schema>;
|
|
@@ -124,11 +124,12 @@ declare type ListCommandPayload<Schema, Type extends ListCommand<FieldValue<Sche
|
|
|
124
124
|
* Returns a list of key and config, with a group of helpers
|
|
125
125
|
* configuring buttons for list manipulation
|
|
126
126
|
*
|
|
127
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
127
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldlist
|
|
128
128
|
*/
|
|
129
129
|
export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
|
|
130
130
|
Array<{
|
|
131
131
|
key: string;
|
|
132
|
+
error: string | undefined;
|
|
132
133
|
config: FieldConfig<Payload>;
|
|
133
134
|
}>,
|
|
134
135
|
{
|
|
@@ -160,7 +161,7 @@ interface InputControl<Element extends {
|
|
|
160
161
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
161
162
|
* introduces custom input mode.
|
|
162
163
|
*
|
|
163
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
164
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usecontrolledinput
|
|
164
165
|
*/
|
|
165
166
|
export declare function useControlledInput<Element extends {
|
|
166
167
|
focus: () => void;
|
package/hooks.js
CHANGED
|
@@ -11,7 +11,7 @@ var helpers = require('./helpers.js');
|
|
|
11
11
|
* Returns properties required to hook into form events.
|
|
12
12
|
* Applied custom validation and define when error should be reported.
|
|
13
13
|
*
|
|
14
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
14
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.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] : {};
|
|
@@ -48,11 +48,7 @@ function useForm() {
|
|
|
48
48
|
if (!form || !config.state) {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
dom.
|
|
52
|
-
if (!form.reportValidity()) {
|
|
53
|
-
dom.focusFirstInvalidField(form);
|
|
54
|
-
}
|
|
55
|
-
dom.requestSubmit(form);
|
|
51
|
+
dom.reportSubmission(form, config.state);
|
|
56
52
|
}, [config.state]);
|
|
57
53
|
react.useEffect(() => {
|
|
58
54
|
// Revalidate the form when input value is changed
|
|
@@ -85,7 +81,7 @@ function useForm() {
|
|
|
85
81
|
var handleInvalid = event => {
|
|
86
82
|
var form = dom.getFormElement(ref.current);
|
|
87
83
|
var field = event.target;
|
|
88
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '') {
|
|
84
|
+
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '__form__') {
|
|
89
85
|
return;
|
|
90
86
|
}
|
|
91
87
|
event.preventDefault();
|
|
@@ -141,12 +137,6 @@ function useForm() {
|
|
|
141
137
|
var form = event.currentTarget;
|
|
142
138
|
var nativeEvent = event.nativeEvent;
|
|
143
139
|
var submitter = nativeEvent.submitter;
|
|
144
|
-
for (var element of form.elements) {
|
|
145
|
-
if (dom.isFieldElement(element) && element.name === '') {
|
|
146
|
-
setError(element.validationMessage);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
140
|
|
|
151
141
|
/**
|
|
152
142
|
* It checks defaultPrevented to confirm if the submission is intentional
|
|
@@ -154,8 +144,7 @@ function useForm() {
|
|
|
154
144
|
* event is captured and revalidate the form with new fields without triggering
|
|
155
145
|
* a form submission at the same time.
|
|
156
146
|
*/
|
|
157
|
-
if (
|
|
158
|
-
event.preventDefault();
|
|
147
|
+
if (event.defaultPrevented) {
|
|
159
148
|
return;
|
|
160
149
|
}
|
|
161
150
|
try {
|
|
@@ -176,14 +165,10 @@ function useForm() {
|
|
|
176
165
|
*
|
|
177
166
|
* This is mainly used to showcase the constraint validation API.
|
|
178
167
|
*/
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
184
|
-
for (var _element of form.elements) {
|
|
185
|
-
if (dom.isFieldElement(_element) && _element.willValidate) {
|
|
186
|
-
submission.error.push([_element.name, _element.validationMessage]);
|
|
168
|
+
for (var element of form.elements) {
|
|
169
|
+
if (dom.isFieldElement(element) && element.willValidate) {
|
|
170
|
+
element.setCustomValidity('');
|
|
171
|
+
submission.error.push([element.name, element.validationMessage]);
|
|
187
172
|
}
|
|
188
173
|
}
|
|
189
174
|
}
|
|
@@ -198,7 +183,7 @@ function useForm() {
|
|
|
198
183
|
}
|
|
199
184
|
}
|
|
200
185
|
}
|
|
201
|
-
if (!config.noValidate && !submitter.formNoValidate && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
|
|
186
|
+
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
|
|
202
187
|
event.preventDefault();
|
|
203
188
|
} else {
|
|
204
189
|
var _config$onSubmit;
|
|
@@ -208,10 +193,7 @@ function useForm() {
|
|
|
208
193
|
});
|
|
209
194
|
}
|
|
210
195
|
if (event.defaultPrevented) {
|
|
211
|
-
dom.
|
|
212
|
-
if (!form.reportValidity()) {
|
|
213
|
-
dom.focusFirstInvalidField(form);
|
|
214
|
-
}
|
|
196
|
+
dom.reportSubmission(form, submission);
|
|
215
197
|
}
|
|
216
198
|
} catch (e) {
|
|
217
199
|
console.warn(e);
|
|
@@ -294,38 +276,6 @@ function useFieldset(ref, config) {
|
|
|
294
276
|
event.preventDefault();
|
|
295
277
|
}
|
|
296
278
|
};
|
|
297
|
-
var submitHandler = event => {
|
|
298
|
-
var form = dom.getFormElement(ref.current);
|
|
299
|
-
if (!form || event.target !== form) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Reset the error state of each field if its validity is changed.
|
|
305
|
-
*
|
|
306
|
-
* This is a workaround as no official way is provided to notify
|
|
307
|
-
* when the validity of the field is changed from `invalid` to `valid`.
|
|
308
|
-
*/
|
|
309
|
-
setError(prev => {
|
|
310
|
-
var _configRef$current$na2;
|
|
311
|
-
var next = prev;
|
|
312
|
-
var fieldsetName = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
313
|
-
for (var field of form.elements) {
|
|
314
|
-
if (dom.isFieldElement(field) && field.name.startsWith(fieldsetName)) {
|
|
315
|
-
var _next$key, _next;
|
|
316
|
-
var key = fieldsetName ? field.name.slice(fieldsetName.length + 1) : field.name;
|
|
317
|
-
var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
|
|
318
|
-
var nextMessage = field.validationMessage;
|
|
319
|
-
if (prevMessage !== '' && nextMessage === '') {
|
|
320
|
-
next = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, next), {}, {
|
|
321
|
-
[key]: ''
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return next;
|
|
327
|
-
});
|
|
328
|
-
};
|
|
329
279
|
var resetHandler = event => {
|
|
330
280
|
var _fieldsetConfig$defau;
|
|
331
281
|
var form = dom.getFormElement(ref.current);
|
|
@@ -343,11 +293,9 @@ function useFieldset(ref, config) {
|
|
|
343
293
|
|
|
344
294
|
// The invalid event does not bubble and so listening on the capturing pharse is needed
|
|
345
295
|
document.addEventListener('invalid', invalidHandler, true);
|
|
346
|
-
document.addEventListener('submit', submitHandler);
|
|
347
296
|
document.addEventListener('reset', resetHandler);
|
|
348
297
|
return () => {
|
|
349
298
|
document.removeEventListener('invalid', invalidHandler, true);
|
|
350
|
-
document.removeEventListener('submit', submitHandler);
|
|
351
299
|
document.removeEventListener('reset', resetHandler);
|
|
352
300
|
};
|
|
353
301
|
}, [ref]);
|
|
@@ -382,7 +330,7 @@ function useFieldset(ref, config) {
|
|
|
382
330
|
* Returns a list of key and config, with a group of helpers
|
|
383
331
|
* configuring buttons for list manipulation
|
|
384
332
|
*
|
|
385
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
333
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldlist
|
|
386
334
|
*/
|
|
387
335
|
function useFieldList(ref, config) {
|
|
388
336
|
var configRef = react.useRef(config);
|
|
@@ -408,6 +356,7 @@ function useFieldList(ref, config) {
|
|
|
408
356
|
initialError
|
|
409
357
|
};
|
|
410
358
|
});
|
|
359
|
+
var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => error === null || error === void 0 ? void 0 : error[0][1]));
|
|
411
360
|
var [entries, setEntries] = react.useState(() => {
|
|
412
361
|
var _config$defaultValue3;
|
|
413
362
|
return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
|
|
@@ -416,6 +365,7 @@ function useFieldList(ref, config) {
|
|
|
416
365
|
var [key, defaultValue] = _ref3;
|
|
417
366
|
return {
|
|
418
367
|
key,
|
|
368
|
+
error: error[index],
|
|
419
369
|
config: {
|
|
420
370
|
name: "".concat(config.name, "[").concat(index, "]"),
|
|
421
371
|
form: config.form,
|
|
@@ -450,6 +400,31 @@ function useFieldList(ref, config) {
|
|
|
450
400
|
configRef.current = config;
|
|
451
401
|
});
|
|
452
402
|
react.useEffect(() => {
|
|
403
|
+
var invalidHandler = event => {
|
|
404
|
+
var _configRef$current$na2;
|
|
405
|
+
var form = dom.getFormElement(ref.current);
|
|
406
|
+
var field = event.target;
|
|
407
|
+
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
408
|
+
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
|
|
412
|
+
|
|
413
|
+
// Update the error only if the field belongs to the fieldset
|
|
414
|
+
if (typeof index === 'number' && paths.length === 0) {
|
|
415
|
+
if (field.dataset.conformTouched) {
|
|
416
|
+
setError(prev => {
|
|
417
|
+
var _prev$index;
|
|
418
|
+
var prevMessage = (_prev$index = prev === null || prev === void 0 ? void 0 : prev[index]) !== null && _prev$index !== void 0 ? _prev$index : '';
|
|
419
|
+
if (prevMessage === field.validationMessage) {
|
|
420
|
+
return prev;
|
|
421
|
+
}
|
|
422
|
+
return [...prev.slice(0, index), field.validationMessage, ...prev.slice(index + 1)];
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
event.preventDefault();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
453
428
|
var submitHandler = event => {
|
|
454
429
|
var form = dom.getFormElement(ref.current);
|
|
455
430
|
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
|
|
@@ -476,6 +451,22 @@ function useFieldList(ref, config) {
|
|
|
476
451
|
}
|
|
477
452
|
}
|
|
478
453
|
});
|
|
454
|
+
setError(error => {
|
|
455
|
+
switch (command.type) {
|
|
456
|
+
case 'append':
|
|
457
|
+
case 'prepend':
|
|
458
|
+
case 'replace':
|
|
459
|
+
return dom.updateList([...error], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
|
|
460
|
+
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
|
|
461
|
+
defaultValue: undefined
|
|
462
|
+
})
|
|
463
|
+
}));
|
|
464
|
+
default:
|
|
465
|
+
{
|
|
466
|
+
return dom.updateList([...error], command);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
});
|
|
479
470
|
event.preventDefault();
|
|
480
471
|
};
|
|
481
472
|
var resetHandler = event => {
|
|
@@ -490,11 +481,14 @@ function useFieldList(ref, config) {
|
|
|
490
481
|
initialError: []
|
|
491
482
|
});
|
|
492
483
|
setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
|
|
484
|
+
setError([]);
|
|
493
485
|
};
|
|
494
486
|
document.addEventListener('submit', submitHandler, true);
|
|
487
|
+
document.addEventListener('invalid', invalidHandler, true);
|
|
495
488
|
document.addEventListener('reset', resetHandler);
|
|
496
489
|
return () => {
|
|
497
490
|
document.removeEventListener('submit', submitHandler, true);
|
|
491
|
+
document.removeEventListener('invalid', invalidHandler, true);
|
|
498
492
|
document.removeEventListener('reset', resetHandler);
|
|
499
493
|
};
|
|
500
494
|
}, [ref]);
|
|
@@ -507,7 +501,7 @@ function useFieldList(ref, config) {
|
|
|
507
501
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
508
502
|
* introduces custom input mode.
|
|
509
503
|
*
|
|
510
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
504
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usecontrolledinput
|
|
511
505
|
*/
|
|
512
506
|
function useControlledInput(config) {
|
|
513
507
|
var _config$defaultValue4;
|
|
@@ -573,13 +567,13 @@ function useControlledInput(config) {
|
|
|
573
567
|
whiteSpace: 'nowrap',
|
|
574
568
|
borderWidth: 0
|
|
575
569
|
},
|
|
570
|
+
tabIndex: -1,
|
|
571
|
+
'aria-hidden': true,
|
|
576
572
|
onFocus() {
|
|
577
573
|
var _inputRef$current;
|
|
578
574
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
579
575
|
}
|
|
580
|
-
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState), {
|
|
581
|
-
type: 'text'
|
|
582
|
-
})), {
|
|
576
|
+
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState))), {
|
|
583
577
|
ref: inputRef,
|
|
584
578
|
value,
|
|
585
579
|
onChange: handleChange,
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type FieldsetConstraint, type Submission, getFormElements, hasError, parse, shouldValidate, } from '@conform-to/dom';
|
|
1
|
+
export { type FieldConfig, type FieldsetConstraint, type Submission, getFormElements, hasError, parse, shouldValidate, } from '@conform-to/dom';
|
|
2
2
|
export * from './hooks';
|
|
3
3
|
export * as conform from './helpers';
|
package/module/base.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
|
|
2
|
+
import { forwardRef, createElement } from 'react';
|
|
3
|
+
|
|
4
|
+
var _excluded = ["hidden", "className", "style"];
|
|
5
|
+
/**
|
|
6
|
+
* Triggering react custom change event
|
|
7
|
+
* Solution based on dom-testing-library
|
|
8
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
9
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
10
|
+
*/
|
|
11
|
+
function setNativeValue(element, value) {
|
|
12
|
+
var {
|
|
13
|
+
set: valueSetter
|
|
14
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
15
|
+
var prototype = Object.getPrototypeOf(element);
|
|
16
|
+
var {
|
|
17
|
+
set: prototypeValueSetter
|
|
18
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
19
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
20
|
+
prototypeValueSetter.call(element, value);
|
|
21
|
+
} else {
|
|
22
|
+
if (valueSetter) {
|
|
23
|
+
valueSetter.call(element, value);
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error('The given element does not have a value setter');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function useInputControl(ref) {
|
|
30
|
+
function getInputElement() {
|
|
31
|
+
var $input = ref.current;
|
|
32
|
+
if (!$input) {
|
|
33
|
+
console.warn('input ref is not available; Maybe you forget to setup the ref?');
|
|
34
|
+
}
|
|
35
|
+
return $input;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
onChange(eventLikeOrString) {
|
|
39
|
+
var $input = getInputElement();
|
|
40
|
+
var value = typeof eventLikeOrString === 'string' ? eventLikeOrString : eventLikeOrString.target.value;
|
|
41
|
+
if ($input && $input.value !== value) {
|
|
42
|
+
$input.dispatchEvent(new InputEvent('beforeinput', {
|
|
43
|
+
bubbles: true,
|
|
44
|
+
cancelable: true
|
|
45
|
+
}));
|
|
46
|
+
setNativeValue($input, value);
|
|
47
|
+
$input.dispatchEvent(new InputEvent('input', {
|
|
48
|
+
bubbles: true,
|
|
49
|
+
cancelable: true
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
onInput(eventLikeOrString) {
|
|
54
|
+
this.onChange(eventLikeOrString);
|
|
55
|
+
},
|
|
56
|
+
onFocus() {
|
|
57
|
+
var $input = getInputElement();
|
|
58
|
+
if ($input) {
|
|
59
|
+
$input.dispatchEvent(new FocusEvent('focusin', {
|
|
60
|
+
bubbles: true,
|
|
61
|
+
cancelable: true
|
|
62
|
+
}));
|
|
63
|
+
$input.dispatchEvent(new FocusEvent('focus', {
|
|
64
|
+
cancelable: true
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
onBlur() {
|
|
69
|
+
var $input = getInputElement();
|
|
70
|
+
if ($input) {
|
|
71
|
+
$input.dispatchEvent(new FocusEvent('focusout', {
|
|
72
|
+
bubbles: true,
|
|
73
|
+
cancelable: true
|
|
74
|
+
}));
|
|
75
|
+
$input.dispatchEvent(new FocusEvent('blur', {
|
|
76
|
+
cancelable: true
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function BaseInputImpl(_ref, ref) {
|
|
83
|
+
var {
|
|
84
|
+
hidden = true,
|
|
85
|
+
className,
|
|
86
|
+
style
|
|
87
|
+
} = _ref,
|
|
88
|
+
props = _objectWithoutProperties(_ref, _excluded);
|
|
89
|
+
return /*#__PURE__*/createElement('input', _objectSpread2({
|
|
90
|
+
ref,
|
|
91
|
+
className: hidden ? '' : className,
|
|
92
|
+
style: hidden ? {
|
|
93
|
+
position: 'absolute',
|
|
94
|
+
width: '1px',
|
|
95
|
+
height: '1px',
|
|
96
|
+
padding: 0,
|
|
97
|
+
margin: '-1px',
|
|
98
|
+
overflow: 'hidden',
|
|
99
|
+
clip: 'rect(0,0,0,0)',
|
|
100
|
+
whiteSpace: 'nowrap',
|
|
101
|
+
borderWidth: 0
|
|
102
|
+
} : style
|
|
103
|
+
}, props));
|
|
104
|
+
}
|
|
105
|
+
var BaseInput = /*#__PURE__*/forwardRef(BaseInputImpl);
|
|
106
|
+
|
|
107
|
+
export { BaseInput, useInputControl };
|
package/module/helpers.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
function input(config) {
|
|
2
|
-
var {
|
|
3
|
-
type,
|
|
4
|
-
value
|
|
5
|
-
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
6
|
-
var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
|
|
2
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
7
3
|
var attributes = {
|
|
8
|
-
type,
|
|
4
|
+
type: options.type,
|
|
9
5
|
name: config.name,
|
|
10
6
|
form: config.form,
|
|
11
7
|
required: config.required,
|
|
@@ -20,10 +16,11 @@ function input(config) {
|
|
|
20
16
|
if (config.initialError && config.initialError.length > 0) {
|
|
21
17
|
attributes.autoFocus = true;
|
|
22
18
|
}
|
|
23
|
-
if (
|
|
24
|
-
|
|
19
|
+
if (options.type === 'checkbox' || options.type === 'radio') {
|
|
20
|
+
var _options$value;
|
|
21
|
+
attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
|
|
25
22
|
attributes.defaultChecked = config.defaultValue === attributes.value;
|
|
26
|
-
} else {
|
|
23
|
+
} else if (options.type !== 'file') {
|
|
27
24
|
attributes.defaultValue = config.defaultValue;
|
|
28
25
|
}
|
|
29
26
|
return attributes;
|
package/module/hooks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
|
|
2
|
-
import { getSubmissionType,
|
|
2
|
+
import { getSubmissionType, reportSubmission, getFormData, parse, isFieldElement, hasError, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom';
|
|
3
3
|
import { useRef, useState, useEffect } from 'react';
|
|
4
4
|
import { input } from './helpers.js';
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ import { input } from './helpers.js';
|
|
|
7
7
|
* Returns properties required to hook into form events.
|
|
8
8
|
* Applied custom validation and define when error should be reported.
|
|
9
9
|
*
|
|
10
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
10
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.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] : {};
|
|
@@ -44,11 +44,7 @@ function useForm() {
|
|
|
44
44
|
if (!form || !config.state) {
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
if (!form.reportValidity()) {
|
|
49
|
-
focusFirstInvalidField(form);
|
|
50
|
-
}
|
|
51
|
-
requestSubmit(form);
|
|
47
|
+
reportSubmission(form, config.state);
|
|
52
48
|
}, [config.state]);
|
|
53
49
|
useEffect(() => {
|
|
54
50
|
// Revalidate the form when input value is changed
|
|
@@ -81,7 +77,7 @@ function useForm() {
|
|
|
81
77
|
var handleInvalid = event => {
|
|
82
78
|
var form = getFormElement(ref.current);
|
|
83
79
|
var field = event.target;
|
|
84
|
-
if (!form || !isFieldElement(field) || field.form !== form || field.name !== '') {
|
|
80
|
+
if (!form || !isFieldElement(field) || field.form !== form || field.name !== '__form__') {
|
|
85
81
|
return;
|
|
86
82
|
}
|
|
87
83
|
event.preventDefault();
|
|
@@ -137,12 +133,6 @@ function useForm() {
|
|
|
137
133
|
var form = event.currentTarget;
|
|
138
134
|
var nativeEvent = event.nativeEvent;
|
|
139
135
|
var submitter = nativeEvent.submitter;
|
|
140
|
-
for (var element of form.elements) {
|
|
141
|
-
if (isFieldElement(element) && element.name === '') {
|
|
142
|
-
setError(element.validationMessage);
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
136
|
|
|
147
137
|
/**
|
|
148
138
|
* It checks defaultPrevented to confirm if the submission is intentional
|
|
@@ -150,8 +140,7 @@ function useForm() {
|
|
|
150
140
|
* event is captured and revalidate the form with new fields without triggering
|
|
151
141
|
* a form submission at the same time.
|
|
152
142
|
*/
|
|
153
|
-
if (
|
|
154
|
-
event.preventDefault();
|
|
143
|
+
if (event.defaultPrevented) {
|
|
155
144
|
return;
|
|
156
145
|
}
|
|
157
146
|
try {
|
|
@@ -172,14 +161,10 @@ function useForm() {
|
|
|
172
161
|
*
|
|
173
162
|
* This is mainly used to showcase the constraint validation API.
|
|
174
163
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
180
|
-
for (var _element of form.elements) {
|
|
181
|
-
if (isFieldElement(_element) && _element.willValidate) {
|
|
182
|
-
submission.error.push([_element.name, _element.validationMessage]);
|
|
164
|
+
for (var element of form.elements) {
|
|
165
|
+
if (isFieldElement(element) && element.willValidate) {
|
|
166
|
+
element.setCustomValidity('');
|
|
167
|
+
submission.error.push([element.name, element.validationMessage]);
|
|
183
168
|
}
|
|
184
169
|
}
|
|
185
170
|
}
|
|
@@ -194,7 +179,7 @@ function useForm() {
|
|
|
194
179
|
}
|
|
195
180
|
}
|
|
196
181
|
}
|
|
197
|
-
if (!config.noValidate && !submitter.formNoValidate && hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
|
|
182
|
+
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
|
|
198
183
|
event.preventDefault();
|
|
199
184
|
} else {
|
|
200
185
|
var _config$onSubmit;
|
|
@@ -204,10 +189,7 @@ function useForm() {
|
|
|
204
189
|
});
|
|
205
190
|
}
|
|
206
191
|
if (event.defaultPrevented) {
|
|
207
|
-
|
|
208
|
-
if (!form.reportValidity()) {
|
|
209
|
-
focusFirstInvalidField(form);
|
|
210
|
-
}
|
|
192
|
+
reportSubmission(form, submission);
|
|
211
193
|
}
|
|
212
194
|
} catch (e) {
|
|
213
195
|
console.warn(e);
|
|
@@ -290,38 +272,6 @@ function useFieldset(ref, config) {
|
|
|
290
272
|
event.preventDefault();
|
|
291
273
|
}
|
|
292
274
|
};
|
|
293
|
-
var submitHandler = event => {
|
|
294
|
-
var form = getFormElement(ref.current);
|
|
295
|
-
if (!form || event.target !== form) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Reset the error state of each field if its validity is changed.
|
|
301
|
-
*
|
|
302
|
-
* This is a workaround as no official way is provided to notify
|
|
303
|
-
* when the validity of the field is changed from `invalid` to `valid`.
|
|
304
|
-
*/
|
|
305
|
-
setError(prev => {
|
|
306
|
-
var _configRef$current$na2;
|
|
307
|
-
var next = prev;
|
|
308
|
-
var fieldsetName = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
309
|
-
for (var field of form.elements) {
|
|
310
|
-
if (isFieldElement(field) && field.name.startsWith(fieldsetName)) {
|
|
311
|
-
var _next$key, _next;
|
|
312
|
-
var key = fieldsetName ? field.name.slice(fieldsetName.length + 1) : field.name;
|
|
313
|
-
var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
|
|
314
|
-
var nextMessage = field.validationMessage;
|
|
315
|
-
if (prevMessage !== '' && nextMessage === '') {
|
|
316
|
-
next = _objectSpread2(_objectSpread2({}, next), {}, {
|
|
317
|
-
[key]: ''
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return next;
|
|
323
|
-
});
|
|
324
|
-
};
|
|
325
275
|
var resetHandler = event => {
|
|
326
276
|
var _fieldsetConfig$defau;
|
|
327
277
|
var form = getFormElement(ref.current);
|
|
@@ -339,11 +289,9 @@ function useFieldset(ref, config) {
|
|
|
339
289
|
|
|
340
290
|
// The invalid event does not bubble and so listening on the capturing pharse is needed
|
|
341
291
|
document.addEventListener('invalid', invalidHandler, true);
|
|
342
|
-
document.addEventListener('submit', submitHandler);
|
|
343
292
|
document.addEventListener('reset', resetHandler);
|
|
344
293
|
return () => {
|
|
345
294
|
document.removeEventListener('invalid', invalidHandler, true);
|
|
346
|
-
document.removeEventListener('submit', submitHandler);
|
|
347
295
|
document.removeEventListener('reset', resetHandler);
|
|
348
296
|
};
|
|
349
297
|
}, [ref]);
|
|
@@ -378,7 +326,7 @@ function useFieldset(ref, config) {
|
|
|
378
326
|
* Returns a list of key and config, with a group of helpers
|
|
379
327
|
* configuring buttons for list manipulation
|
|
380
328
|
*
|
|
381
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
329
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usefieldlist
|
|
382
330
|
*/
|
|
383
331
|
function useFieldList(ref, config) {
|
|
384
332
|
var configRef = useRef(config);
|
|
@@ -404,6 +352,7 @@ function useFieldList(ref, config) {
|
|
|
404
352
|
initialError
|
|
405
353
|
};
|
|
406
354
|
});
|
|
355
|
+
var [error, setError] = useState(() => uncontrolledState.initialError.map(error => error === null || error === void 0 ? void 0 : error[0][1]));
|
|
407
356
|
var [entries, setEntries] = useState(() => {
|
|
408
357
|
var _config$defaultValue3;
|
|
409
358
|
return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
|
|
@@ -412,6 +361,7 @@ function useFieldList(ref, config) {
|
|
|
412
361
|
var [key, defaultValue] = _ref3;
|
|
413
362
|
return {
|
|
414
363
|
key,
|
|
364
|
+
error: error[index],
|
|
415
365
|
config: {
|
|
416
366
|
name: "".concat(config.name, "[").concat(index, "]"),
|
|
417
367
|
form: config.form,
|
|
@@ -446,6 +396,31 @@ function useFieldList(ref, config) {
|
|
|
446
396
|
configRef.current = config;
|
|
447
397
|
});
|
|
448
398
|
useEffect(() => {
|
|
399
|
+
var invalidHandler = event => {
|
|
400
|
+
var _configRef$current$na2;
|
|
401
|
+
var form = getFormElement(ref.current);
|
|
402
|
+
var field = event.target;
|
|
403
|
+
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
404
|
+
if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
var [index, ...paths] = getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
|
|
408
|
+
|
|
409
|
+
// Update the error only if the field belongs to the fieldset
|
|
410
|
+
if (typeof index === 'number' && paths.length === 0) {
|
|
411
|
+
if (field.dataset.conformTouched) {
|
|
412
|
+
setError(prev => {
|
|
413
|
+
var _prev$index;
|
|
414
|
+
var prevMessage = (_prev$index = prev === null || prev === void 0 ? void 0 : prev[index]) !== null && _prev$index !== void 0 ? _prev$index : '';
|
|
415
|
+
if (prevMessage === field.validationMessage) {
|
|
416
|
+
return prev;
|
|
417
|
+
}
|
|
418
|
+
return [...prev.slice(0, index), field.validationMessage, ...prev.slice(index + 1)];
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
event.preventDefault();
|
|
422
|
+
}
|
|
423
|
+
};
|
|
449
424
|
var submitHandler = event => {
|
|
450
425
|
var form = getFormElement(ref.current);
|
|
451
426
|
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
|
|
@@ -472,6 +447,22 @@ function useFieldList(ref, config) {
|
|
|
472
447
|
}
|
|
473
448
|
}
|
|
474
449
|
});
|
|
450
|
+
setError(error => {
|
|
451
|
+
switch (command.type) {
|
|
452
|
+
case 'append':
|
|
453
|
+
case 'prepend':
|
|
454
|
+
case 'replace':
|
|
455
|
+
return updateList([...error], _objectSpread2(_objectSpread2({}, command), {}, {
|
|
456
|
+
payload: _objectSpread2(_objectSpread2({}, command.payload), {}, {
|
|
457
|
+
defaultValue: undefined
|
|
458
|
+
})
|
|
459
|
+
}));
|
|
460
|
+
default:
|
|
461
|
+
{
|
|
462
|
+
return updateList([...error], command);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
475
466
|
event.preventDefault();
|
|
476
467
|
};
|
|
477
468
|
var resetHandler = event => {
|
|
@@ -486,11 +477,14 @@ function useFieldList(ref, config) {
|
|
|
486
477
|
initialError: []
|
|
487
478
|
});
|
|
488
479
|
setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
|
|
480
|
+
setError([]);
|
|
489
481
|
};
|
|
490
482
|
document.addEventListener('submit', submitHandler, true);
|
|
483
|
+
document.addEventListener('invalid', invalidHandler, true);
|
|
491
484
|
document.addEventListener('reset', resetHandler);
|
|
492
485
|
return () => {
|
|
493
486
|
document.removeEventListener('submit', submitHandler, true);
|
|
487
|
+
document.removeEventListener('invalid', invalidHandler, true);
|
|
494
488
|
document.removeEventListener('reset', resetHandler);
|
|
495
489
|
};
|
|
496
490
|
}, [ref]);
|
|
@@ -503,7 +497,7 @@ function useFieldList(ref, config) {
|
|
|
503
497
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
504
498
|
* introduces custom input mode.
|
|
505
499
|
*
|
|
506
|
-
* @see https://github.com/edmundhung/conform/tree/v0.4.
|
|
500
|
+
* @see https://github.com/edmundhung/conform/tree/v0.4.1/packages/conform-react/README.md#usecontrolledinput
|
|
507
501
|
*/
|
|
508
502
|
function useControlledInput(config) {
|
|
509
503
|
var _config$defaultValue4;
|
|
@@ -569,13 +563,13 @@ function useControlledInput(config) {
|
|
|
569
563
|
whiteSpace: 'nowrap',
|
|
570
564
|
borderWidth: 0
|
|
571
565
|
},
|
|
566
|
+
tabIndex: -1,
|
|
567
|
+
'aria-hidden': true,
|
|
572
568
|
onFocus() {
|
|
573
569
|
var _inputRef$current;
|
|
574
570
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
575
571
|
}
|
|
576
|
-
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState), {
|
|
577
|
-
type: 'text'
|
|
578
|
-
})), {
|
|
572
|
+
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState))), {
|
|
579
573
|
ref: inputRef,
|
|
580
574
|
value,
|
|
581
575
|
onChange: handleChange,
|
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.
|
|
5
|
+
"version": "0.5.0-pre.0",
|
|
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.
|
|
22
|
+
"@conform-to/dom": "0.5.0-pre.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"react": ">=16.8"
|