@conform-to/react 0.1.0 → 0.3.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 +551 -6
- package/helpers.d.ts +4 -4
- package/helpers.js +27 -22
- package/hooks.d.ts +114 -35
- package/hooks.js +396 -279
- package/index.d.ts +1 -1
- package/index.js +4 -4
- package/module/helpers.js +27 -22
- package/module/hooks.js +398 -281
- package/module/index.js +1 -1
- package/package.json +2 -2
package/module/hooks.js
CHANGED
|
@@ -1,375 +1,492 @@
|
|
|
1
1
|
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
|
|
2
|
-
import {
|
|
3
|
-
import { useRef, useState, useEffect
|
|
4
|
-
|
|
2
|
+
import { isFieldElement, listCommandKey, serializeListCommand, getFormElement, getKey, parseListCommand, updateList } from '@conform-to/dom';
|
|
3
|
+
import { useRef, useState, useEffect } from 'react';
|
|
4
|
+
import { input } from './helpers.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns properties required to hook into form events.
|
|
8
|
+
* Applied custom validation and define when error should be reported.
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#useform
|
|
11
|
+
*/
|
|
5
12
|
function useForm() {
|
|
13
|
+
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
6
14
|
var {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
noValidate = false,
|
|
10
|
-
fallbackNative = false,
|
|
11
|
-
initialReport = 'onSubmit'
|
|
12
|
-
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
15
|
+
validate
|
|
16
|
+
} = config;
|
|
13
17
|
var ref = useRef(null);
|
|
14
|
-
var [
|
|
15
|
-
|
|
16
|
-
var handleSubmit = event => {
|
|
17
|
-
if (!noValidate) {
|
|
18
|
-
setFieldState(event.currentTarget, {
|
|
19
|
-
touched: true
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
if (!shouldSkipValidate(event.nativeEvent) && !event.currentTarget.reportValidity()) {
|
|
23
|
-
return event.preventDefault();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(event);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
var handleReset = event => {
|
|
31
|
-
setFieldState(event.currentTarget, {
|
|
32
|
-
touched: false
|
|
33
|
-
});
|
|
34
|
-
onReset === null || onReset === void 0 ? void 0 : onReset(event);
|
|
35
|
-
};
|
|
36
|
-
|
|
18
|
+
var [noValidate, setNoValidate] = useState(config.noValidate || !config.fallbackNative);
|
|
37
19
|
useEffect(() => {
|
|
38
|
-
|
|
20
|
+
setNoValidate(true);
|
|
39
21
|
}, []);
|
|
40
22
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
// Initialize form validation messages
|
|
24
|
+
if (ref.current) {
|
|
25
|
+
validate === null || validate === void 0 ? void 0 : validate(ref.current);
|
|
26
|
+
} // Revalidate the form when input value is changed
|
|
27
|
+
|
|
44
28
|
|
|
45
|
-
var
|
|
46
|
-
var
|
|
29
|
+
var handleInput = event => {
|
|
30
|
+
var field = event.target;
|
|
31
|
+
var form = ref.current;
|
|
47
32
|
|
|
48
|
-
if (!
|
|
33
|
+
if (!form || !isFieldElement(field) || field.form !== form) {
|
|
49
34
|
return;
|
|
50
35
|
}
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
37
|
+
validate === null || validate === void 0 ? void 0 : validate(form);
|
|
38
|
+
|
|
39
|
+
if (!config.noValidate) {
|
|
40
|
+
if (config.initialReport === 'onChange') {
|
|
41
|
+
field.dataset.conformTouched = 'true';
|
|
42
|
+
} // Field validity might be changed due to cross reference
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
|
|
45
|
+
for (var _field of form.elements) {
|
|
46
|
+
if (isFieldElement(_field) && _field.dataset.conformTouched) {
|
|
47
|
+
// Report latest error for all touched fields
|
|
48
|
+
_field.checkValidity();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
60
51
|
}
|
|
61
52
|
};
|
|
62
53
|
|
|
63
54
|
var handleBlur = event => {
|
|
64
|
-
var
|
|
55
|
+
var field = event.target;
|
|
56
|
+
var form = ref.current;
|
|
65
57
|
|
|
66
|
-
if (!isFieldElement(
|
|
58
|
+
if (!form || !isFieldElement(field) || field.form !== form || config.noValidate || config.initialReport !== 'onBlur') {
|
|
67
59
|
return;
|
|
68
60
|
}
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
62
|
+
field.dataset.conformTouched = 'true';
|
|
63
|
+
field.reportValidity();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
var handleReset = event => {
|
|
67
|
+
var form = ref.current;
|
|
75
68
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
69
|
+
if (!form || event.target !== form) {
|
|
70
|
+
return;
|
|
71
|
+
} // Reset all field state
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
for (var field of form.elements) {
|
|
75
|
+
if (isFieldElement(field)) {
|
|
76
|
+
delete field.dataset.conformTouched;
|
|
77
|
+
}
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The reset event is triggered before form reset happens.
|
|
81
|
+
* This make sure the form to be revalidated with initial values.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
validate === null || validate === void 0 ? void 0 : validate(form);
|
|
87
|
+
}, 0);
|
|
79
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* The input event handler will be triggered in capturing phase in order to
|
|
91
|
+
* allow follow-up action in the bubble phase based on the latest validity
|
|
92
|
+
* E.g. `useFieldset` reset the error of valid field after checking the
|
|
93
|
+
* validity in the bubble phase.
|
|
94
|
+
*/
|
|
95
|
+
|
|
80
96
|
|
|
81
|
-
document.
|
|
82
|
-
document.
|
|
97
|
+
document.addEventListener('input', handleInput, true);
|
|
98
|
+
document.addEventListener('blur', handleBlur, true);
|
|
99
|
+
document.addEventListener('reset', handleReset);
|
|
83
100
|
return () => {
|
|
84
|
-
document.
|
|
85
|
-
document.
|
|
101
|
+
document.removeEventListener('input', handleInput, true);
|
|
102
|
+
document.removeEventListener('blur', handleBlur, true);
|
|
103
|
+
document.removeEventListener('reset', handleReset);
|
|
86
104
|
};
|
|
87
|
-
}, [
|
|
105
|
+
}, [validate, config.initialReport, config.noValidate]);
|
|
88
106
|
return {
|
|
89
107
|
ref,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
noValidate,
|
|
109
|
+
|
|
110
|
+
onSubmit(event) {
|
|
111
|
+
var form = event.currentTarget;
|
|
112
|
+
var nativeEvent = event.nativeEvent;
|
|
113
|
+
var submitter = nativeEvent.submitter instanceof HTMLButtonElement || nativeEvent.submitter instanceof HTMLInputElement ? nativeEvent.submitter : null; // Validating the form with the submitter value
|
|
114
|
+
|
|
115
|
+
validate === null || validate === void 0 ? void 0 : validate(form, submitter);
|
|
116
|
+
/**
|
|
117
|
+
* It checks defaultPrevented to confirm if the submission is intentional
|
|
118
|
+
* This is utilized by `useFieldList` to modify the list state when the submit
|
|
119
|
+
* event is captured and revalidate the form with new fields without triggering
|
|
120
|
+
* a form submission at the same time.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && !event.defaultPrevented) {
|
|
124
|
+
// Mark all fields as touched
|
|
125
|
+
for (var field of form.elements) {
|
|
126
|
+
if (isFieldElement(field)) {
|
|
127
|
+
field.dataset.conformTouched = 'true';
|
|
109
128
|
}
|
|
129
|
+
} // Check the validity of the form
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
|
|
132
|
+
if (!event.currentTarget.reportValidity()) {
|
|
133
|
+
event.preventDefault();
|
|
114
134
|
}
|
|
135
|
+
}
|
|
115
136
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
var {
|
|
119
|
-
keys,
|
|
120
|
-
error
|
|
121
|
-
} = action.payload;
|
|
122
|
-
var nextState = state;
|
|
123
|
-
|
|
124
|
-
for (var _key of Object.keys(keys)) {
|
|
125
|
-
var prevError = state[_key];
|
|
126
|
-
var nextError = error === null || error === void 0 ? void 0 : error[_key];
|
|
127
|
-
|
|
128
|
-
if (typeof nextError === 'string' && prevError !== nextError) {
|
|
129
|
-
return _objectSpread2(_objectSpread2({}, nextState), {}, {
|
|
130
|
-
[_key]: nextError
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
137
|
+
if (!event.defaultPrevented) {
|
|
138
|
+
var _config$onSubmit;
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* All the information of the field, including state and config.
|
|
148
|
+
*/
|
|
137
149
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
fieldset
|
|
142
|
-
} = action.payload;
|
|
143
|
-
var updates = [];
|
|
150
|
+
function useFieldset(ref, config) {
|
|
151
|
+
var [error, setError] = useState(() => {
|
|
152
|
+
var result = {};
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
154
|
+
for (var [key, _error] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) {
|
|
155
|
+
var _config$initialError;
|
|
149
156
|
|
|
150
|
-
|
|
157
|
+
if (_error !== null && _error !== void 0 && _error.message) {
|
|
158
|
+
result[key] = _error.message;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
return result;
|
|
163
|
+
});
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
/**
|
|
166
|
+
* Reset the error state of each field if its validity is changed.
|
|
167
|
+
*
|
|
168
|
+
* This is a workaround as no official way is provided to notify
|
|
169
|
+
* when the validity of the field is changed from `invalid` to `valid`.
|
|
170
|
+
*/
|
|
171
|
+
var resetError = form => {
|
|
172
|
+
setError(prev => {
|
|
173
|
+
var next = prev;
|
|
174
|
+
|
|
175
|
+
for (var field of form.elements) {
|
|
176
|
+
if (isFieldElement(field)) {
|
|
177
|
+
var key = getKey(field.name, config === null || config === void 0 ? void 0 : config.name);
|
|
178
|
+
|
|
179
|
+
if (key) {
|
|
180
|
+
var _next$key, _next;
|
|
181
|
+
|
|
182
|
+
var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
|
|
183
|
+
var nextMessage = field.validationMessage;
|
|
184
|
+
/**
|
|
185
|
+
* Techincally, checking prevMessage not being empty while nextMessage being empty
|
|
186
|
+
* is sufficient for our usecase. It checks if the message is changed instead to allow
|
|
187
|
+
* the hook to be useful independently.
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
if (prevMessage !== '' && prevMessage !== nextMessage) {
|
|
191
|
+
next = _objectSpread2(_objectSpread2({}, next), {}, {
|
|
192
|
+
[key]: nextMessage
|
|
193
|
+
});
|
|
194
|
+
}
|
|
154
195
|
}
|
|
155
196
|
}
|
|
197
|
+
}
|
|
156
198
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
return next;
|
|
200
|
+
});
|
|
201
|
+
};
|
|
160
202
|
|
|
161
|
-
|
|
162
|
-
|
|
203
|
+
var handleInput = event => {
|
|
204
|
+
var form = getFormElement(ref.current);
|
|
205
|
+
var field = event.target;
|
|
163
206
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}, {}, () => Object.fromEntries(Object.keys(schema.fields).reduce((result, name) => {
|
|
170
|
-
var _config$error;
|
|
207
|
+
if (!form || !isFieldElement(field) || field.form !== form) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
171
210
|
|
|
172
|
-
|
|
211
|
+
resetError(form);
|
|
212
|
+
};
|
|
173
213
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
214
|
+
var invalidHandler = event => {
|
|
215
|
+
var form = getFormElement(ref.current);
|
|
216
|
+
var field = event.target;
|
|
177
217
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
var _schema$validate;
|
|
218
|
+
if (!form || !isFieldElement(field) || field.form !== form) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
182
221
|
|
|
183
|
-
|
|
222
|
+
var key = getKey(field.name, config === null || config === void 0 ? void 0 : config.name); // Update the error only if the field belongs to the fieldset
|
|
184
223
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
224
|
+
if (key) {
|
|
225
|
+
setError(prev => {
|
|
226
|
+
var _prev$key;
|
|
189
227
|
|
|
190
|
-
|
|
191
|
-
console.warn('No form element is linked to the fieldset; Do you forgot setting the form attribute?');
|
|
192
|
-
}
|
|
228
|
+
var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : '';
|
|
193
229
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
useEffect(() => {
|
|
204
|
-
dispatch({
|
|
205
|
-
type: 'migrate',
|
|
206
|
-
payload: {
|
|
207
|
-
keys: Object.keys(schema.fields),
|
|
208
|
-
error: config.error
|
|
230
|
+
if (prevMessage === field.validationMessage) {
|
|
231
|
+
return prev;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return _objectSpread2(_objectSpread2({}, prev), {}, {
|
|
235
|
+
[key]: field.validationMessage
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
event.preventDefault();
|
|
209
239
|
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
var _schema$validate2;
|
|
219
|
-
|
|
220
|
-
var fieldset = e.currentTarget;
|
|
221
|
-
(_schema$validate2 = schema.validate) === null || _schema$validate2 === void 0 ? void 0 : _schema$validate2.call(schema, fieldset);
|
|
222
|
-
dispatch({
|
|
223
|
-
type: 'cleanup',
|
|
224
|
-
payload: {
|
|
225
|
-
fieldset
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
var submitHandler = event => {
|
|
243
|
+
var form = getFormElement(ref.current);
|
|
244
|
+
|
|
245
|
+
if (!form || event.target !== form) {
|
|
246
|
+
return;
|
|
247
|
+
} // This helps resetting error that fullfilled by the submitter
|
|
229
248
|
|
|
230
|
-
onReset(e) {
|
|
231
|
-
setFieldState(e.currentTarget, {
|
|
232
|
-
touched: false
|
|
233
|
-
});
|
|
234
|
-
dispatch({
|
|
235
|
-
type: 'reset'
|
|
236
|
-
});
|
|
237
|
-
},
|
|
238
249
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
var key = Object.keys(schema.fields).find(key => (element === null || element === void 0 ? void 0 : element.name) === getName([e.currentTarget.name, key]));
|
|
250
|
+
resetError(form);
|
|
251
|
+
};
|
|
242
252
|
|
|
243
|
-
|
|
253
|
+
var resetHandler = event => {
|
|
254
|
+
var form = getFormElement(ref.current);
|
|
255
|
+
|
|
256
|
+
if (!form || event.target !== form) {
|
|
244
257
|
return;
|
|
245
|
-
}
|
|
258
|
+
}
|
|
246
259
|
|
|
260
|
+
setError({});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
document.addEventListener('input', handleInput); // The invalid event does not bubble and so listening on the capturing pharse is needed
|
|
264
|
+
|
|
265
|
+
document.addEventListener('invalid', invalidHandler, true);
|
|
266
|
+
document.addEventListener('submit', submitHandler);
|
|
267
|
+
document.addEventListener('reset', resetHandler);
|
|
268
|
+
return () => {
|
|
269
|
+
document.removeEventListener('input', handleInput);
|
|
270
|
+
document.removeEventListener('invalid', invalidHandler, true);
|
|
271
|
+
document.removeEventListener('submit', submitHandler);
|
|
272
|
+
document.removeEventListener('reset', resetHandler);
|
|
273
|
+
};
|
|
274
|
+
}, [ref, config === null || config === void 0 ? void 0 : config.name]);
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
setError(prev => {
|
|
277
|
+
var next = prev;
|
|
278
|
+
|
|
279
|
+
for (var [key, _error2] of Object.entries((_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {})) {
|
|
280
|
+
var _config$initialError2;
|
|
281
|
+
|
|
282
|
+
if (next[key] !== (_error2 === null || _error2 === void 0 ? void 0 : _error2.message)) {
|
|
283
|
+
var _error2$message;
|
|
247
284
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
payload: {
|
|
252
|
-
key,
|
|
253
|
-
message: element.validationMessage
|
|
285
|
+
next = _objectSpread2(_objectSpread2({}, next), {}, {
|
|
286
|
+
[key]: (_error2$message = _error2 === null || _error2 === void 0 ? void 0 : _error2.message) !== null && _error2$message !== void 0 ? _error2$message : ''
|
|
287
|
+
});
|
|
254
288
|
}
|
|
255
|
-
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return next;
|
|
292
|
+
});
|
|
293
|
+
}, [config === null || config === void 0 ? void 0 : config.name, config === null || config === void 0 ? void 0 : config.initialError]);
|
|
294
|
+
/**
|
|
295
|
+
* This allows us constructing the field at runtime as we have no information
|
|
296
|
+
* about which fields would be available. The proxy will also help tracking
|
|
297
|
+
* the usage of each field for optimization in the future.
|
|
298
|
+
*/
|
|
299
|
+
|
|
300
|
+
return new Proxy({}, {
|
|
301
|
+
get(_target, key) {
|
|
302
|
+
var _constraint, _config$defaultValue, _config$initialError3, _config$initialError4, _error$key;
|
|
303
|
+
|
|
304
|
+
if (typeof key !== 'string') {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
var constraint = config === null || config === void 0 ? void 0 : (_constraint = config.constraint) === null || _constraint === void 0 ? void 0 : _constraint[key];
|
|
309
|
+
var field = {
|
|
310
|
+
config: _objectSpread2({
|
|
311
|
+
name: config !== null && config !== void 0 && config.name ? "".concat(config.name, ".").concat(key) : key,
|
|
312
|
+
form: config === null || config === void 0 ? void 0 : config.form,
|
|
313
|
+
defaultValue: config === null || config === void 0 ? void 0 : (_config$defaultValue = config.defaultValue) === null || _config$defaultValue === void 0 ? void 0 : _config$defaultValue[key],
|
|
314
|
+
initialError: config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details
|
|
315
|
+
}, constraint),
|
|
316
|
+
error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : ''
|
|
317
|
+
};
|
|
318
|
+
return field;
|
|
256
319
|
}
|
|
257
320
|
|
|
258
|
-
}
|
|
259
|
-
error: Object.assign({}, config.error, errorMessage)
|
|
260
|
-
}))];
|
|
321
|
+
});
|
|
261
322
|
}
|
|
262
|
-
function useFieldList(config) {
|
|
263
|
-
var _config$initialValue$, _config$initialValue;
|
|
264
|
-
|
|
265
|
-
var size = (_config$initialValue$ = (_config$initialValue = config.initialValue) === null || _config$initialValue === void 0 ? void 0 : _config$initialValue.length) !== null && _config$initialValue$ !== void 0 ? _config$initialValue$ : 1;
|
|
266
|
-
var [keys, setKeys] = useState(() => [...Array(size).keys()]);
|
|
267
|
-
var list = useMemo(() => keys.map((key, index) => {
|
|
268
|
-
var _config$initialValue2, _config$error2;
|
|
269
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Returns a list of key and config, with a group of helpers
|
|
326
|
+
* configuring buttons for list manipulation
|
|
327
|
+
*
|
|
328
|
+
* @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usefieldlist
|
|
329
|
+
*/
|
|
330
|
+
function useFieldList(ref, config) {
|
|
331
|
+
var [entries, setEntries] = useState(() => {
|
|
332
|
+
var _config$defaultValue2;
|
|
333
|
+
|
|
334
|
+
return Object.entries((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [undefined]);
|
|
335
|
+
});
|
|
336
|
+
var list = entries.map((_ref, index) => {
|
|
337
|
+
var _config$defaultValue3, _config$initialError5, _config$initialError6;
|
|
338
|
+
|
|
339
|
+
var [key, defaultValue] = _ref;
|
|
270
340
|
return {
|
|
271
|
-
key
|
|
341
|
+
key,
|
|
272
342
|
config: _objectSpread2(_objectSpread2({}, config), {}, {
|
|
273
343
|
name: "".concat(config.name, "[").concat(index, "]"),
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
// @ts-expect-error
|
|
277
|
-
constraint: _objectSpread2(_objectSpread2({}, config.constraint), {}, {
|
|
278
|
-
multiple: false
|
|
279
|
-
})
|
|
344
|
+
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue3 = config.defaultValue) === null || _config$defaultValue3 === void 0 ? void 0 : _config$defaultValue3[index],
|
|
345
|
+
initialError: (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[index]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.details
|
|
280
346
|
})
|
|
281
347
|
};
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
348
|
+
});
|
|
349
|
+
/***
|
|
350
|
+
* This use proxy to capture all information about the command and
|
|
351
|
+
* have it encoded in the value.
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
var control = new Proxy({}, {
|
|
355
|
+
get(_target, type) {
|
|
356
|
+
return function () {
|
|
357
|
+
var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
358
|
+
return {
|
|
359
|
+
name: listCommandKey,
|
|
360
|
+
value: serializeListCommand(config.name, {
|
|
361
|
+
type,
|
|
362
|
+
payload
|
|
363
|
+
}),
|
|
364
|
+
form: config.form,
|
|
365
|
+
formNoValidate: true
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
}
|
|
290
369
|
|
|
291
|
-
|
|
292
|
-
|
|
370
|
+
});
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
setEntries(prevEntries => {
|
|
373
|
+
var _config$defaultValue4;
|
|
293
374
|
|
|
294
|
-
|
|
295
|
-
return _objectSpread2(_objectSpread2({}, createControlButton(config.name, 'append', {})), {}, {
|
|
296
|
-
onClick(e) {
|
|
297
|
-
setKeys(keys => [...keys, Date.now()]);
|
|
298
|
-
e.preventDefault();
|
|
299
|
-
}
|
|
375
|
+
var nextEntries = Object.entries((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : [undefined]);
|
|
300
376
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
377
|
+
if (prevEntries.length !== nextEntries.length) {
|
|
378
|
+
return nextEntries;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (var i = 0; i < prevEntries.length; i++) {
|
|
382
|
+
var [prevKey, prevValue] = prevEntries[i];
|
|
383
|
+
var [nextKey, nextValue] = nextEntries[i];
|
|
384
|
+
|
|
385
|
+
if (prevKey !== nextKey || prevValue !== nextValue) {
|
|
386
|
+
return nextEntries;
|
|
311
387
|
}
|
|
388
|
+
} // No need to rerender in this case
|
|
312
389
|
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
390
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
391
|
+
return prevEntries;
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
var submitHandler = event => {
|
|
395
|
+
var form = getFormElement(ref.current);
|
|
396
|
+
|
|
397
|
+
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== listCommandKey) {
|
|
398
|
+
return;
|
|
321
399
|
}
|
|
322
400
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
401
|
+
var [name, command] = parseListCommand(event.submitter.value);
|
|
402
|
+
|
|
403
|
+
if (name !== config.name) {
|
|
404
|
+
// Ensure the scope of the listener are limited to specific field name
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
switch (command.type) {
|
|
409
|
+
case 'append':
|
|
410
|
+
case 'prepend':
|
|
411
|
+
case 'replace':
|
|
412
|
+
command.payload.defaultValue = ["".concat(Date.now()), command.payload.defaultValue];
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
setEntries(entries => updateList([...(entries !== null && entries !== void 0 ? entries : [])], command));
|
|
417
|
+
event.preventDefault();
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
var resetHandler = event => {
|
|
421
|
+
var _config$defaultValue5;
|
|
422
|
+
|
|
423
|
+
var form = getFormElement(ref.current);
|
|
424
|
+
|
|
425
|
+
if (!form || event.target !== form) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
setEntries(Object.entries((_config$defaultValue5 = config.defaultValue) !== null && _config$defaultValue5 !== void 0 ? _config$defaultValue5 : []));
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
document.addEventListener('submit', submitHandler, true);
|
|
433
|
+
document.addEventListener('reset', resetHandler);
|
|
434
|
+
return () => {
|
|
435
|
+
document.removeEventListener('submit', submitHandler, true);
|
|
436
|
+
document.removeEventListener('reset', resetHandler);
|
|
437
|
+
};
|
|
438
|
+
}, [ref, config.name, config.defaultValue]);
|
|
439
|
+
return [list, control];
|
|
327
440
|
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Returns the properties required to configure a shadow input for validation.
|
|
444
|
+
* This is particular useful when integrating dropdown and datepicker whichs
|
|
445
|
+
* introduces custom input mode.
|
|
446
|
+
*
|
|
447
|
+
* @see https://github.com/edmundhung/conform/tree/v0.3.0-pre.0/packages/conform-react/README.md#usecontrolledinput
|
|
448
|
+
*/
|
|
328
449
|
function useControlledInput(field) {
|
|
329
|
-
var _field$
|
|
450
|
+
var _field$defaultValue;
|
|
330
451
|
|
|
331
|
-
var [value, setValue] = useState("".concat((_field$initialValue = field.initialValue) !== null && _field$initialValue !== void 0 ? _field$initialValue : ''));
|
|
332
|
-
var [shouldBlur, setShouldBlur] = useState(false);
|
|
333
452
|
var ref = useRef(null);
|
|
334
|
-
var
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
defaultValue: field.initialValue,
|
|
338
|
-
style: {
|
|
339
|
-
display: 'none'
|
|
340
|
-
},
|
|
341
|
-
'aria-hidden': true
|
|
342
|
-
})), [field.constraint, field.name, field.initialValue]);
|
|
343
|
-
useEffect(() => {
|
|
453
|
+
var [value, setValue] = useState("".concat((_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : ''));
|
|
454
|
+
|
|
455
|
+
var handleChange = eventOrValue => {
|
|
344
456
|
if (!ref.current) {
|
|
345
457
|
return;
|
|
346
458
|
}
|
|
347
459
|
|
|
348
|
-
|
|
460
|
+
var newValue = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
|
|
461
|
+
ref.current.value = newValue;
|
|
349
462
|
ref.current.dispatchEvent(new InputEvent('input', {
|
|
350
463
|
bubbles: true
|
|
351
464
|
}));
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
var _ref$current;
|
|
465
|
+
setValue(newValue);
|
|
466
|
+
};
|
|
355
467
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
468
|
+
var handleBlur = () => {
|
|
469
|
+
var _ref$current;
|
|
359
470
|
|
|
360
|
-
(_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.dispatchEvent(new FocusEvent('
|
|
471
|
+
(_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.dispatchEvent(new FocusEvent('blur', {
|
|
361
472
|
bubbles: true
|
|
362
473
|
}));
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
var handleInvalid = event => {
|
|
477
|
+
event.preventDefault();
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
return [_objectSpread2({
|
|
481
|
+
ref,
|
|
482
|
+
hidden: true
|
|
483
|
+
}, input(field, {
|
|
484
|
+
type: 'text'
|
|
485
|
+
})), {
|
|
366
486
|
value,
|
|
367
|
-
onChange:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
onBlur: () => {
|
|
371
|
-
setShouldBlur(true);
|
|
372
|
-
}
|
|
487
|
+
onChange: handleChange,
|
|
488
|
+
onBlur: handleBlur,
|
|
489
|
+
onInvalid: handleInvalid
|
|
373
490
|
}];
|
|
374
491
|
}
|
|
375
492
|
|