@conform-to/react 0.4.1 → 0.5.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 +223 -295
- package/helpers.d.ts +19 -6
- package/helpers.js +21 -14
- package/hooks.d.ts +22 -28
- package/hooks.js +109 -127
- package/index.d.ts +1 -1
- package/index.js +16 -0
- package/module/helpers.js +21 -14
- package/module/hooks.js +110 -128
- package/module/index.js +1 -1
- package/package.json +2 -2
package/helpers.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { FieldConfig } from '@conform-to/dom';
|
|
2
|
+
import type { HTMLInputTypeAttribute } from 'react';
|
|
3
3
|
interface FieldProps {
|
|
4
|
+
id?: string;
|
|
4
5
|
name: string;
|
|
5
6
|
form?: string;
|
|
6
7
|
required?: boolean;
|
|
7
8
|
autoFocus?: boolean;
|
|
9
|
+
'aria-invalid': boolean;
|
|
10
|
+
'aria-describedby'?: string;
|
|
8
11
|
}
|
|
9
12
|
interface InputProps<Schema> extends FieldProps {
|
|
10
13
|
type?: HTMLInputTypeAttribute;
|
|
@@ -28,10 +31,20 @@ interface TextareaProps extends FieldProps {
|
|
|
28
31
|
maxLength?: number;
|
|
29
32
|
defaultValue?: string;
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
type
|
|
34
|
+
declare type InputOptions = {
|
|
35
|
+
type: 'checkbox' | 'radio';
|
|
33
36
|
value?: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'file';
|
|
39
|
+
value?: never;
|
|
40
|
+
} | {
|
|
41
|
+
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden' | 'file'>;
|
|
42
|
+
value?: never;
|
|
43
|
+
};
|
|
44
|
+
export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: {
|
|
45
|
+
type: 'file';
|
|
34
46
|
}): InputProps<Schema>;
|
|
35
|
-
export declare function
|
|
36
|
-
export declare function
|
|
47
|
+
export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
|
|
48
|
+
export declare function select<Schema>(config: FieldConfig<Schema>): SelectProps;
|
|
49
|
+
export declare function textarea<Schema>(config: FieldConfig<Schema>): TextareaProps;
|
|
37
50
|
export {};
|
package/helpers.js
CHANGED
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
function input(config) {
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
value
|
|
9
|
-
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
10
|
-
var isCheckboxOrRadio = type === 'checkbox' || type === 'radio';
|
|
6
|
+
var _config$initialError;
|
|
7
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
11
8
|
var attributes = {
|
|
12
|
-
|
|
9
|
+
id: config.id,
|
|
10
|
+
type: options.type,
|
|
13
11
|
name: config.name,
|
|
14
12
|
form: config.form,
|
|
15
13
|
required: config.required,
|
|
@@ -19,27 +17,33 @@ function input(config) {
|
|
|
19
17
|
max: config.max,
|
|
20
18
|
step: config.step,
|
|
21
19
|
pattern: config.pattern,
|
|
22
|
-
multiple: config.multiple
|
|
20
|
+
multiple: config.multiple,
|
|
21
|
+
'aria-invalid': Boolean((_config$initialError = config.initialError) === null || _config$initialError === void 0 ? void 0 : _config$initialError.length),
|
|
22
|
+
'aria-describedby': config.errorId
|
|
23
23
|
};
|
|
24
24
|
if (config.initialError && config.initialError.length > 0) {
|
|
25
25
|
attributes.autoFocus = true;
|
|
26
26
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
27
|
+
if (options.type === 'checkbox' || options.type === 'radio') {
|
|
28
|
+
var _options$value;
|
|
29
|
+
attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
|
|
29
30
|
attributes.defaultChecked = config.defaultValue === attributes.value;
|
|
30
|
-
} else {
|
|
31
|
+
} else if (options.type !== 'file') {
|
|
31
32
|
attributes.defaultValue = config.defaultValue;
|
|
32
33
|
}
|
|
33
34
|
return attributes;
|
|
34
35
|
}
|
|
35
36
|
function select(config) {
|
|
36
|
-
var _config$defaultValue;
|
|
37
|
+
var _config$defaultValue, _config$initialError2;
|
|
37
38
|
var attributes = {
|
|
39
|
+
id: config.id,
|
|
38
40
|
name: config.name,
|
|
39
41
|
form: config.form,
|
|
40
42
|
defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
|
|
41
43
|
required: config.required,
|
|
42
|
-
multiple: config.multiple
|
|
44
|
+
multiple: config.multiple,
|
|
45
|
+
'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
|
|
46
|
+
'aria-describedby': config.errorId
|
|
43
47
|
};
|
|
44
48
|
if (config.initialError && config.initialError.length > 0) {
|
|
45
49
|
attributes.autoFocus = true;
|
|
@@ -47,15 +51,18 @@ function select(config) {
|
|
|
47
51
|
return attributes;
|
|
48
52
|
}
|
|
49
53
|
function textarea(config) {
|
|
50
|
-
var _config$defaultValue2;
|
|
54
|
+
var _config$defaultValue2, _config$initialError3;
|
|
51
55
|
var attributes = {
|
|
56
|
+
id: config.id,
|
|
52
57
|
name: config.name,
|
|
53
58
|
form: config.form,
|
|
54
59
|
defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
|
|
55
60
|
required: config.required,
|
|
56
61
|
minLength: config.minLength,
|
|
57
62
|
maxLength: config.maxLength,
|
|
58
|
-
autoFocus: Boolean(config.initialError)
|
|
63
|
+
autoFocus: Boolean(config.initialError),
|
|
64
|
+
'aria-invalid': Boolean((_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : _config$initialError3.length),
|
|
65
|
+
'aria-describedby': config.errorId
|
|
59
66
|
};
|
|
60
67
|
if (config.initialError && config.initialError.length > 0) {
|
|
61
68
|
attributes.autoFocus = true;
|
package/hooks.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type
|
|
1
|
+
import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type Primitive, type Submission } from '@conform-to/dom';
|
|
2
2
|
import { type InputHTMLAttributes, type FormEvent, type RefObject } from 'react';
|
|
3
3
|
export interface FormConfig<Schema extends Record<string, any>> {
|
|
4
|
+
/**
|
|
5
|
+
* If the form id is provided, Id for label,
|
|
6
|
+
* input and error elements will be derived.
|
|
7
|
+
*/
|
|
8
|
+
id?: string;
|
|
4
9
|
/**
|
|
5
10
|
* Validation mode. Default to `client-only`.
|
|
6
11
|
*/
|
|
@@ -20,6 +25,10 @@ export interface FormConfig<Schema extends Record<string, any>> {
|
|
|
20
25
|
* An object describing the state from the last submission
|
|
21
26
|
*/
|
|
22
27
|
state?: Submission<Schema>;
|
|
28
|
+
/**
|
|
29
|
+
* An object describing the constraint of each field
|
|
30
|
+
*/
|
|
31
|
+
constraint?: FieldsetConstraint<Schema>;
|
|
23
32
|
/**
|
|
24
33
|
* Enable native validation before hydation.
|
|
25
34
|
*
|
|
@@ -53,10 +62,12 @@ export interface FormConfig<Schema extends Record<string, any>> {
|
|
|
53
62
|
*/
|
|
54
63
|
interface FormProps {
|
|
55
64
|
ref: RefObject<HTMLFormElement>;
|
|
65
|
+
id?: string;
|
|
56
66
|
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
|
57
67
|
noValidate: boolean;
|
|
58
68
|
}
|
|
59
69
|
interface Form<Schema extends Record<string, any>> {
|
|
70
|
+
id?: string;
|
|
60
71
|
ref: RefObject<HTMLFormElement>;
|
|
61
72
|
error: string;
|
|
62
73
|
props: FormProps;
|
|
@@ -66,9 +77,9 @@ interface Form<Schema extends Record<string, any>> {
|
|
|
66
77
|
* Returns properties required to hook into form events.
|
|
67
78
|
* Applied custom validation and define when error should be reported.
|
|
68
79
|
*
|
|
69
|
-
* @see https://
|
|
80
|
+
* @see https://conform.guide/api/react#useform
|
|
70
81
|
*/
|
|
71
|
-
export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema
|
|
82
|
+
export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): [Form<Schema>, Fieldset<Schema>];
|
|
72
83
|
/**
|
|
73
84
|
* All the information of the field, including state and config.
|
|
74
85
|
*/
|
|
@@ -107,38 +118,21 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
|
|
|
107
118
|
/**
|
|
108
119
|
* Returns all the information about the fieldset.
|
|
109
120
|
*
|
|
110
|
-
* @see https://
|
|
121
|
+
* @see https://conform.guide/api/react#usefieldset
|
|
111
122
|
*/
|
|
112
123
|
export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldsetConfig<Schema>): Fieldset<Schema>;
|
|
113
124
|
export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Schema>): Fieldset<Schema>;
|
|
114
|
-
interface CommandButtonProps {
|
|
115
|
-
name?: string;
|
|
116
|
-
value?: string;
|
|
117
|
-
form?: string;
|
|
118
|
-
formNoValidate: true;
|
|
119
|
-
}
|
|
120
|
-
declare type ListCommandPayload<Schema, Type extends ListCommand<FieldValue<Schema>>['type']> = Extract<ListCommand<FieldValue<Schema>>, {
|
|
121
|
-
type: Type;
|
|
122
|
-
}>['payload'];
|
|
123
125
|
/**
|
|
124
126
|
* Returns a list of key and config, with a group of helpers
|
|
125
127
|
* configuring buttons for list manipulation
|
|
126
128
|
*
|
|
127
|
-
* @see https://
|
|
129
|
+
* @see https://conform.guide/api/react#usefieldlist
|
|
128
130
|
*/
|
|
129
|
-
export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>):
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{
|
|
135
|
-
prepend(payload?: ListCommandPayload<Payload, 'prepend'>): CommandButtonProps;
|
|
136
|
-
append(payload?: ListCommandPayload<Payload, 'append'>): CommandButtonProps;
|
|
137
|
-
replace(payload: ListCommandPayload<Payload, 'replace'>): CommandButtonProps;
|
|
138
|
-
remove(payload: ListCommandPayload<Payload, 'remove'>): CommandButtonProps;
|
|
139
|
-
reorder(payload: ListCommandPayload<Payload, 'reorder'>): CommandButtonProps;
|
|
140
|
-
}
|
|
141
|
-
];
|
|
131
|
+
export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): Array<{
|
|
132
|
+
key: string;
|
|
133
|
+
error: string | undefined;
|
|
134
|
+
config: FieldConfig<Payload>;
|
|
135
|
+
}>;
|
|
142
136
|
interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
143
137
|
ref: RefObject<HTMLInputElement>;
|
|
144
138
|
}
|
|
@@ -160,7 +154,7 @@ interface InputControl<Element extends {
|
|
|
160
154
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
161
155
|
* introduces custom input mode.
|
|
162
156
|
*
|
|
163
|
-
* @see https://
|
|
157
|
+
* @see https://conform.guide/api/react#usecontrolledinput
|
|
164
158
|
*/
|
|
165
159
|
export declare function useControlledInput<Element extends {
|
|
166
160
|
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://
|
|
14
|
+
* @see https://conform.guide/api/react#useform
|
|
15
15
|
*/
|
|
16
16
|
function useForm() {
|
|
17
17
|
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
@@ -25,17 +25,26 @@ function useForm() {
|
|
|
25
25
|
})) !== null && _config$state$error$f !== void 0 ? _config$state$error$f : [];
|
|
26
26
|
return message !== null && message !== void 0 ? message : '';
|
|
27
27
|
});
|
|
28
|
-
var [
|
|
29
|
-
var
|
|
30
|
-
|
|
28
|
+
var [uncontrolledState, setUncontrolledState] = react.useState(() => {
|
|
29
|
+
var submission = config.state;
|
|
30
|
+
if (!submission) {
|
|
31
|
+
return {
|
|
32
|
+
defaultValue: config.defaultValue
|
|
33
|
+
};
|
|
34
|
+
}
|
|
31
35
|
return {
|
|
32
|
-
defaultValue:
|
|
33
|
-
initialError: error.filter(_ref2 => {
|
|
36
|
+
defaultValue: submission.value,
|
|
37
|
+
initialError: submission.error.filter(_ref2 => {
|
|
34
38
|
var [name] = _ref2;
|
|
35
|
-
return name !== '' && dom.
|
|
39
|
+
return name !== '' && dom.shouldValidate(submission, name);
|
|
36
40
|
})
|
|
37
41
|
};
|
|
38
42
|
});
|
|
43
|
+
var fieldsetConfig = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, uncontrolledState), {}, {
|
|
44
|
+
constraint: config.constraint,
|
|
45
|
+
form: config.id
|
|
46
|
+
});
|
|
47
|
+
var fieldset = useFieldset(ref, fieldsetConfig);
|
|
39
48
|
var [noValidate, setNoValidate] = react.useState(config.noValidate || !config.fallbackNative);
|
|
40
49
|
react.useEffect(() => {
|
|
41
50
|
configRef.current = config;
|
|
@@ -48,11 +57,7 @@ function useForm() {
|
|
|
48
57
|
if (!form || !config.state) {
|
|
49
58
|
return;
|
|
50
59
|
}
|
|
51
|
-
dom.
|
|
52
|
-
if (!form.reportValidity()) {
|
|
53
|
-
dom.focusFirstInvalidField(form);
|
|
54
|
-
}
|
|
55
|
-
dom.requestSubmit(form);
|
|
60
|
+
dom.reportSubmission(form, config.state);
|
|
56
61
|
}, [config.state]);
|
|
57
62
|
react.useEffect(() => {
|
|
58
63
|
// Revalidate the form when input value is changed
|
|
@@ -63,11 +68,8 @@ function useForm() {
|
|
|
63
68
|
if (!form || !dom.isFieldElement(field) || field.form !== form) {
|
|
64
69
|
return;
|
|
65
70
|
}
|
|
66
|
-
if (formConfig.initialReport === 'onChange') {
|
|
67
|
-
field.
|
|
68
|
-
}
|
|
69
|
-
if (field.dataset.conformTouched) {
|
|
70
|
-
dom.requestValidate(form, field.name);
|
|
71
|
+
if (field.dataset.conformTouched || formConfig.initialReport === 'onChange') {
|
|
72
|
+
dom.requestCommand(form, dom.validate(field.name));
|
|
71
73
|
}
|
|
72
74
|
};
|
|
73
75
|
var handleBlur = event => {
|
|
@@ -78,14 +80,13 @@ function useForm() {
|
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
82
|
if (formConfig.initialReport === 'onBlur' && !field.dataset.conformTouched) {
|
|
81
|
-
field.
|
|
82
|
-
dom.requestValidate(form, field.name);
|
|
83
|
+
dom.requestCommand(form, dom.validate(field.name));
|
|
83
84
|
}
|
|
84
85
|
};
|
|
85
86
|
var handleInvalid = event => {
|
|
86
87
|
var form = dom.getFormElement(ref.current);
|
|
87
88
|
var field = event.target;
|
|
88
|
-
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '') {
|
|
89
|
+
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '__form__') {
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
event.preventDefault();
|
|
@@ -104,11 +105,12 @@ function useForm() {
|
|
|
104
105
|
for (var field of form.elements) {
|
|
105
106
|
if (dom.isFieldElement(field)) {
|
|
106
107
|
delete field.dataset.conformTouched;
|
|
108
|
+
field.setAttribute('aria-invalid', 'false');
|
|
107
109
|
field.setCustomValidity('');
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
setError('');
|
|
111
|
-
|
|
113
|
+
setUncontrolledState({
|
|
112
114
|
defaultValue: formConfig.defaultValue,
|
|
113
115
|
initialError: []
|
|
114
116
|
});
|
|
@@ -131,22 +133,18 @@ function useForm() {
|
|
|
131
133
|
document.removeEventListener('reset', handleReset);
|
|
132
134
|
};
|
|
133
135
|
}, []);
|
|
134
|
-
|
|
136
|
+
var form = {
|
|
137
|
+
id: config.id,
|
|
135
138
|
ref,
|
|
136
139
|
error,
|
|
137
140
|
props: {
|
|
138
141
|
ref,
|
|
142
|
+
id: config.id,
|
|
139
143
|
noValidate,
|
|
140
144
|
onSubmit(event) {
|
|
141
145
|
var form = event.currentTarget;
|
|
142
146
|
var nativeEvent = event.nativeEvent;
|
|
143
147
|
var submitter = nativeEvent.submitter;
|
|
144
|
-
for (var element of form.elements) {
|
|
145
|
-
if (dom.isFieldElement(element) && element.name === '' && element.willValidate) {
|
|
146
|
-
setError(element.validationMessage);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
148
|
|
|
151
149
|
/**
|
|
152
150
|
* It checks defaultPrevented to confirm if the submission is intentional
|
|
@@ -154,8 +152,7 @@ function useForm() {
|
|
|
154
152
|
* event is captured and revalidate the form with new fields without triggering
|
|
155
153
|
* a form submission at the same time.
|
|
156
154
|
*/
|
|
157
|
-
if (
|
|
158
|
-
event.preventDefault();
|
|
155
|
+
if (event.defaultPrevented) {
|
|
159
156
|
return;
|
|
160
157
|
}
|
|
161
158
|
try {
|
|
@@ -176,29 +173,15 @@ function useForm() {
|
|
|
176
173
|
*
|
|
177
174
|
* This is mainly used to showcase the constraint validation API.
|
|
178
175
|
*/
|
|
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]);
|
|
176
|
+
for (var element of form.elements) {
|
|
177
|
+
if (dom.isFieldElement(element) && element.willValidate) {
|
|
178
|
+
element.setCustomValidity('');
|
|
179
|
+
submission.error.push([element.name, element.validationMessage]);
|
|
187
180
|
}
|
|
188
181
|
}
|
|
189
182
|
}
|
|
190
183
|
}
|
|
191
|
-
|
|
192
|
-
// Touch all fields only if the submitter is not a command button
|
|
193
|
-
if (submission.type === 'submit') {
|
|
194
|
-
for (var field of form.elements) {
|
|
195
|
-
if (dom.isFieldElement(field)) {
|
|
196
|
-
// Mark the field as touched
|
|
197
|
-
field.dataset.conformTouched = 'true';
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (!config.noValidate && !submitter.formNoValidate && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
|
|
184
|
+
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && dom.hasError(submission.error) || submission.type === 'validate' && config.mode !== 'server-validation') {
|
|
202
185
|
event.preventDefault();
|
|
203
186
|
} else {
|
|
204
187
|
var _config$onSubmit;
|
|
@@ -208,10 +191,7 @@ function useForm() {
|
|
|
208
191
|
});
|
|
209
192
|
}
|
|
210
193
|
if (event.defaultPrevented) {
|
|
211
|
-
dom.
|
|
212
|
-
if (!form.reportValidity()) {
|
|
213
|
-
dom.focusFirstInvalidField(form);
|
|
214
|
-
}
|
|
194
|
+
dom.reportSubmission(form, submission);
|
|
215
195
|
}
|
|
216
196
|
} catch (e) {
|
|
217
197
|
console.warn(e);
|
|
@@ -220,6 +200,7 @@ function useForm() {
|
|
|
220
200
|
},
|
|
221
201
|
config: fieldsetConfig
|
|
222
202
|
};
|
|
203
|
+
return [form, fieldset];
|
|
223
204
|
}
|
|
224
205
|
|
|
225
206
|
/**
|
|
@@ -280,6 +261,10 @@ function useFieldset(ref, config) {
|
|
|
280
261
|
// Update the error only if the field belongs to the fieldset
|
|
281
262
|
if (typeof key === 'string' && paths.length === 0) {
|
|
282
263
|
if (field.dataset.conformTouched) {
|
|
264
|
+
// Update the aria attribute only if it is set
|
|
265
|
+
if (field.getAttribute('aria-invalid')) {
|
|
266
|
+
field.setAttribute('aria-invalid', field.validationMessage !== '' ? 'true' : 'false');
|
|
267
|
+
}
|
|
283
268
|
setError(prev => {
|
|
284
269
|
var _prev$key;
|
|
285
270
|
var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : '';
|
|
@@ -294,38 +279,6 @@ function useFieldset(ref, config) {
|
|
|
294
279
|
event.preventDefault();
|
|
295
280
|
}
|
|
296
281
|
};
|
|
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
282
|
var resetHandler = event => {
|
|
330
283
|
var _fieldsetConfig$defau;
|
|
331
284
|
var form = dom.getFormElement(ref.current);
|
|
@@ -343,11 +296,9 @@ function useFieldset(ref, config) {
|
|
|
343
296
|
|
|
344
297
|
// The invalid event does not bubble and so listening on the capturing pharse is needed
|
|
345
298
|
document.addEventListener('invalid', invalidHandler, true);
|
|
346
|
-
document.addEventListener('submit', submitHandler);
|
|
347
299
|
document.addEventListener('reset', resetHandler);
|
|
348
300
|
return () => {
|
|
349
301
|
document.removeEventListener('invalid', invalidHandler, true);
|
|
350
|
-
document.removeEventListener('submit', submitHandler);
|
|
351
302
|
document.removeEventListener('reset', resetHandler);
|
|
352
303
|
};
|
|
353
304
|
}, [ref]);
|
|
@@ -368,21 +319,26 @@ function useFieldset(ref, config) {
|
|
|
368
319
|
var field = {
|
|
369
320
|
config: _rollupPluginBabelHelpers.objectSpread2({
|
|
370
321
|
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
|
|
371
|
-
form: fieldsetConfig.form,
|
|
372
322
|
defaultValue: uncontrolledState.defaultValue[key],
|
|
373
323
|
initialError: uncontrolledState.initialError[key]
|
|
374
324
|
}, constraint),
|
|
375
325
|
error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : ''
|
|
376
326
|
};
|
|
327
|
+
if (fieldsetConfig.form) {
|
|
328
|
+
field.config.form = fieldsetConfig.form;
|
|
329
|
+
field.config.id = "".concat(fieldsetConfig.form, "-").concat(field.config.name);
|
|
330
|
+
field.config.errorId = "".concat(field.config.id, "-error");
|
|
331
|
+
}
|
|
377
332
|
return field;
|
|
378
333
|
}
|
|
379
334
|
});
|
|
380
335
|
}
|
|
336
|
+
|
|
381
337
|
/**
|
|
382
338
|
* Returns a list of key and config, with a group of helpers
|
|
383
339
|
* configuring buttons for list manipulation
|
|
384
340
|
*
|
|
385
|
-
* @see https://
|
|
341
|
+
* @see https://conform.guide/api/react#usefieldlist
|
|
386
342
|
*/
|
|
387
343
|
function useFieldList(ref, config) {
|
|
388
344
|
var configRef = react.useRef(config);
|
|
@@ -408,48 +364,40 @@ function useFieldList(ref, config) {
|
|
|
408
364
|
initialError
|
|
409
365
|
};
|
|
410
366
|
});
|
|
367
|
+
var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => error === null || error === void 0 ? void 0 : error[0][1]));
|
|
411
368
|
var [entries, setEntries] = react.useState(() => {
|
|
412
369
|
var _config$defaultValue3;
|
|
413
370
|
return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
|
|
414
371
|
});
|
|
415
|
-
var list = entries.map((_ref3, index) => {
|
|
416
|
-
var [key, defaultValue] = _ref3;
|
|
417
|
-
return {
|
|
418
|
-
key,
|
|
419
|
-
config: {
|
|
420
|
-
name: "".concat(config.name, "[").concat(index, "]"),
|
|
421
|
-
form: config.form,
|
|
422
|
-
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
|
|
423
|
-
initialError: uncontrolledState.initialError[index]
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
/***
|
|
429
|
-
* This use proxy to capture all information about the command and
|
|
430
|
-
* have it encoded in the value.
|
|
431
|
-
*/
|
|
432
|
-
var command = new Proxy({}, {
|
|
433
|
-
get(_target, type) {
|
|
434
|
-
return function () {
|
|
435
|
-
var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
436
|
-
return {
|
|
437
|
-
name: 'conform/list',
|
|
438
|
-
value: JSON.stringify({
|
|
439
|
-
type,
|
|
440
|
-
scope: config.name,
|
|
441
|
-
payload
|
|
442
|
-
}),
|
|
443
|
-
form: config.form,
|
|
444
|
-
formNoValidate: true
|
|
445
|
-
};
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
372
|
react.useEffect(() => {
|
|
450
373
|
configRef.current = config;
|
|
451
374
|
});
|
|
452
375
|
react.useEffect(() => {
|
|
376
|
+
var invalidHandler = event => {
|
|
377
|
+
var _configRef$current$na2;
|
|
378
|
+
var form = dom.getFormElement(ref.current);
|
|
379
|
+
var field = event.target;
|
|
380
|
+
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
|
|
381
|
+
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
|
|
385
|
+
|
|
386
|
+
// Update the error only if the field belongs to the fieldset
|
|
387
|
+
if (typeof index === 'number' && paths.length === 0) {
|
|
388
|
+
if (field.dataset.conformTouched) {
|
|
389
|
+
setError(prev => {
|
|
390
|
+
var _prev$index;
|
|
391
|
+
var prevMessage = (_prev$index = prev === null || prev === void 0 ? void 0 : prev[index]) !== null && _prev$index !== void 0 ? _prev$index : '';
|
|
392
|
+
if (prevMessage === field.validationMessage) {
|
|
393
|
+
return prev;
|
|
394
|
+
}
|
|
395
|
+
return [...prev.slice(0, index), field.validationMessage, ...prev.slice(index + 1)];
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
event.preventDefault();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
453
401
|
var submitHandler = event => {
|
|
454
402
|
var form = dom.getFormElement(ref.current);
|
|
455
403
|
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
|
|
@@ -476,6 +424,22 @@ function useFieldList(ref, config) {
|
|
|
476
424
|
}
|
|
477
425
|
}
|
|
478
426
|
});
|
|
427
|
+
setError(error => {
|
|
428
|
+
switch (command.type) {
|
|
429
|
+
case 'append':
|
|
430
|
+
case 'prepend':
|
|
431
|
+
case 'replace':
|
|
432
|
+
return dom.updateList([...error], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
|
|
433
|
+
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {
|
|
434
|
+
defaultValue: undefined
|
|
435
|
+
})
|
|
436
|
+
}));
|
|
437
|
+
default:
|
|
438
|
+
{
|
|
439
|
+
return dom.updateList([...error], command);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
479
443
|
event.preventDefault();
|
|
480
444
|
};
|
|
481
445
|
var resetHandler = event => {
|
|
@@ -490,24 +454,42 @@ function useFieldList(ref, config) {
|
|
|
490
454
|
initialError: []
|
|
491
455
|
});
|
|
492
456
|
setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
|
|
457
|
+
setError([]);
|
|
493
458
|
};
|
|
494
459
|
document.addEventListener('submit', submitHandler, true);
|
|
460
|
+
document.addEventListener('invalid', invalidHandler, true);
|
|
495
461
|
document.addEventListener('reset', resetHandler);
|
|
496
462
|
return () => {
|
|
497
463
|
document.removeEventListener('submit', submitHandler, true);
|
|
464
|
+
document.removeEventListener('invalid', invalidHandler, true);
|
|
498
465
|
document.removeEventListener('reset', resetHandler);
|
|
499
466
|
};
|
|
500
467
|
}, [ref]);
|
|
501
|
-
return
|
|
502
|
-
|
|
503
|
-
|
|
468
|
+
return entries.map((_ref3, index) => {
|
|
469
|
+
var [key, defaultValue] = _ref3;
|
|
470
|
+
var fieldConfig = {
|
|
471
|
+
name: "".concat(config.name, "[").concat(index, "]"),
|
|
472
|
+
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
|
|
473
|
+
initialError: uncontrolledState.initialError[index]
|
|
474
|
+
};
|
|
475
|
+
if (config.form) {
|
|
476
|
+
fieldConfig.form = config.form;
|
|
477
|
+
fieldConfig.id = "".concat(config.form, "-").concat(config.name);
|
|
478
|
+
fieldConfig.errorId = "".concat(fieldConfig.id, "-error");
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
key,
|
|
482
|
+
error: error[index],
|
|
483
|
+
config: fieldConfig
|
|
484
|
+
};
|
|
485
|
+
});
|
|
504
486
|
}
|
|
505
487
|
/**
|
|
506
488
|
* Returns the properties required to configure a shadow input for validation.
|
|
507
489
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
508
490
|
* introduces custom input mode.
|
|
509
491
|
*
|
|
510
|
-
* @see https://
|
|
492
|
+
* @see https://conform.guide/api/react#usecontrolledinput
|
|
511
493
|
*/
|
|
512
494
|
function useControlledInput(config) {
|
|
513
495
|
var _config$defaultValue4;
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type FieldConfig, type FieldsetConstraint, type Submission, getFormElements, hasError, parse, shouldValidate, } from '@conform-to/dom';
|
|
1
|
+
export { type FieldConfig, type FieldsetConstraint, type Submission, getFormElements, hasError, list, validate, requestCommand, requestSubmit, parse, shouldValidate, } from '@conform-to/dom';
|
|
2
2
|
export * from './hooks';
|
|
3
3
|
export * as conform from './helpers';
|