@conform-to/react 0.3.1 → 0.4.0-pre.1

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/module/hooks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
2
- import { isFieldElement, listCommandKey, serializeListCommand, getFormElement, getKey, parseListCommand, updateList } from '@conform-to/dom';
2
+ import { getSubmissionType, setFormError, focusFirstInvalidField, requestSubmit, isFieldElement, getFormData, parse, 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,64 +7,108 @@ 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.3.1/packages/conform-react/README.md#useform
10
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.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] : {};
14
- var {
15
- validate
16
- } = config;
14
+ var configRef = useRef(config);
17
15
  var ref = useRef(null);
16
+ var [error, setError] = useState(() => {
17
+ var _config$state$error$f, _config$state, _config$state$error;
18
+
19
+ var [, message] = (_config$state$error$f = (_config$state = config.state) === null || _config$state === void 0 ? void 0 : (_config$state$error = _config$state.error) === null || _config$state$error === void 0 ? void 0 : _config$state$error.find(_ref => {
20
+ var [key] = _ref;
21
+ return key === '';
22
+ })) !== null && _config$state$error$f !== void 0 ? _config$state$error$f : [];
23
+ return message !== null && message !== void 0 ? message : '';
24
+ });
25
+ var [fieldsetConfig, setFieldsetConfig] = useState(() => {
26
+ var _config$state$error2, _config$state2, _config$state$value, _config$state3;
27
+
28
+ var error = (_config$state$error2 = (_config$state2 = config.state) === null || _config$state2 === void 0 ? void 0 : _config$state2.error) !== null && _config$state$error2 !== void 0 ? _config$state$error2 : [];
29
+ return {
30
+ defaultValue: (_config$state$value = (_config$state3 = config.state) === null || _config$state3 === void 0 ? void 0 : _config$state3.value) !== null && _config$state$value !== void 0 ? _config$state$value : config.defaultValue,
31
+ initialError: error.filter(_ref2 => {
32
+ var [name] = _ref2;
33
+ return name !== '' && getSubmissionType(name) === null;
34
+ })
35
+ };
36
+ });
18
37
  var [noValidate, setNoValidate] = useState(config.noValidate || !config.fallbackNative);
38
+ useEffect(() => {
39
+ configRef.current = config;
40
+ });
19
41
  useEffect(() => {
20
42
  setNoValidate(true);
21
43
  }, []);
22
44
  useEffect(() => {
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
45
+ var form = ref.current;
27
46
 
47
+ if (!form || !config.state) {
48
+ return;
49
+ }
28
50
 
51
+ setFormError(form, config.state);
52
+
53
+ if (!form.reportValidity()) {
54
+ focusFirstInvalidField(form);
55
+ }
56
+
57
+ requestSubmit(form);
58
+ }, [config.state]);
59
+ useEffect(() => {
60
+ // Revalidate the form when input value is changed
29
61
  var handleInput = event => {
30
62
  var field = event.target;
31
63
  var form = ref.current;
64
+ var formConfig = configRef.current;
32
65
 
33
66
  if (!form || !isFieldElement(field) || field.form !== form) {
34
67
  return;
35
68
  }
36
69
 
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
43
-
70
+ if (formConfig.initialReport === 'onChange') {
71
+ field.dataset.conformTouched = 'true';
72
+ }
44
73
 
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
- }
74
+ if (field.dataset.conformTouched) {
75
+ requestValidate(form, field.name);
51
76
  }
52
77
  };
53
78
 
54
79
  var handleBlur = event => {
55
80
  var field = event.target;
56
81
  var form = ref.current;
82
+ var formConfig = configRef.current;
83
+
84
+ if (!form || !isFieldElement(field) || field.form !== form) {
85
+ return;
86
+ }
87
+
88
+ if (formConfig.initialReport === 'onBlur' && !field.dataset.conformTouched) {
89
+ field.dataset.conformTouched = 'true';
90
+ requestValidate(form, field.name);
91
+ }
92
+ };
93
+
94
+ var handleInvalid = event => {
95
+ var form = getFormElement(ref.current);
96
+ var field = event.target;
57
97
 
58
- if (!form || !isFieldElement(field) || field.form !== form || config.noValidate || config.initialReport !== 'onBlur') {
98
+ if (!form || !isFieldElement(field) || field.form !== form || field.name !== '') {
59
99
  return;
60
100
  }
61
101
 
62
- field.dataset.conformTouched = 'true';
63
- field.reportValidity();
102
+ event.preventDefault();
103
+
104
+ if (field.dataset.conformTouched) {
105
+ setError(field.validationMessage);
106
+ }
64
107
  };
65
108
 
66
109
  var handleReset = event => {
67
110
  var form = ref.current;
111
+ var formConfig = configRef.current;
68
112
 
69
113
  if (!form || event.target !== form) {
70
114
  return;
@@ -74,17 +118,15 @@ function useForm() {
74
118
  for (var field of form.elements) {
75
119
  if (isFieldElement(field)) {
76
120
  delete field.dataset.conformTouched;
121
+ field.setCustomValidity('');
77
122
  }
78
123
  }
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
124
 
85
- setTimeout(() => {
86
- validate === null || validate === void 0 ? void 0 : validate(form);
87
- }, 0);
125
+ setError('');
126
+ setFieldsetConfig({
127
+ defaultValue: formConfig.defaultValue,
128
+ initialError: []
129
+ });
88
130
  };
89
131
  /**
90
132
  * The input event handler will be triggered in capturing phase in order to
@@ -96,58 +138,93 @@ function useForm() {
96
138
 
97
139
  document.addEventListener('input', handleInput, true);
98
140
  document.addEventListener('blur', handleBlur, true);
141
+ document.addEventListener('invalid', handleInvalid, true);
99
142
  document.addEventListener('reset', handleReset);
100
143
  return () => {
101
144
  document.removeEventListener('input', handleInput, true);
102
145
  document.removeEventListener('blur', handleBlur, true);
146
+ document.removeEventListener('invalid', handleInvalid, true);
103
147
  document.removeEventListener('reset', handleReset);
104
148
  };
105
- }, [validate, config.initialReport, config.noValidate]);
149
+ }, []);
106
150
  return {
107
151
  ref,
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
- */
152
+ error,
153
+ props: {
154
+ ref,
155
+ noValidate,
156
+
157
+ onSubmit(event) {
158
+ var form = event.currentTarget;
159
+ var nativeEvent = event.nativeEvent;
160
+ var submitter = nativeEvent.submitter;
161
+
162
+ for (var element of form.elements) {
163
+ if (isFieldElement(element) && element.name === '') {
164
+ setError(element.validationMessage);
165
+ break;
166
+ }
167
+ }
168
+ /**
169
+ * It checks defaultPrevented to confirm if the submission is intentional
170
+ * This is utilized by `useFieldList` to modify the list state when the submit
171
+ * event is captured and revalidate the form with new fields without triggering
172
+ * a form submission at the same time.
173
+ */
122
174
 
123
- if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && !event.defaultPrevented) {
124
- var focused = false;
125
175
 
126
- for (var field of form.elements) {
127
- if (isFieldElement(field)) {
128
- // Mark the field as touched
129
- field.dataset.conformTouched = 'true'; // Focus on the first invalid field
176
+ if (!submitter || event.defaultPrevented) {
177
+ event.preventDefault();
178
+ return;
179
+ }
130
180
 
131
- if (!focused && !field.validity.valid && field.tagName !== 'BUTTON') {
132
- field.focus();
133
- focused = true;
181
+ var formData = getFormData(form, submitter);
182
+ var submission = parse(formData);
183
+ var context = {
184
+ form,
185
+ formData,
186
+ submission
187
+ }; // Touch all fields only if the submitter is not a command button
188
+
189
+ if (!submission.type) {
190
+ for (var field of form.elements) {
191
+ if (isFieldElement(field)) {
192
+ // Mark the field as touched
193
+ field.dataset.conformTouched = 'true';
134
194
  }
135
195
  }
136
- } // Check the validity of the form
196
+ }
137
197
 
198
+ try {
199
+ if (!config.noValidate && !submitter.formNoValidate) {
200
+ var _config$onValidate;
138
201
 
139
- if (!event.currentTarget.reportValidity()) {
140
- event.preventDefault();
202
+ (_config$onValidate = config.onValidate) === null || _config$onValidate === void 0 ? void 0 : _config$onValidate.call(config, context);
203
+
204
+ if (!form.reportValidity()) {
205
+ focusFirstInvalidField(form);
206
+ event.preventDefault();
207
+ }
208
+ }
209
+ } catch (e) {
210
+ if (e !== form) {
211
+ console.warn(e);
212
+ }
141
213
  }
142
- }
143
214
 
144
- if (!event.defaultPrevented) {
145
- var _config$onSubmit;
215
+ if (!event.defaultPrevented) {
216
+ if (config.mode !== 'server-validation' && submission.type === 'validate') {
217
+ event.preventDefault();
218
+ } else {
219
+ var _config$onSubmit;
146
220
 
147
- (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event);
221
+ (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context);
222
+ }
223
+ }
148
224
  }
149
- }
150
225
 
226
+ },
227
+ config: fieldsetConfig
151
228
  };
152
229
  }
153
230
  /**
@@ -155,93 +232,86 @@ function useForm() {
155
232
  */
156
233
 
157
234
  function useFieldset(ref, config) {
158
- var [error, setError] = useState(() => {
159
- var result = {};
235
+ var configRef = useRef(config);
236
+ var [uncontrolledState, setUncontrolledState] = useState( // @ts-expect-error
237
+ () => {
238
+ var _config$defaultValue;
160
239
 
161
- 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 : {})) {
240
+ var initialError = {};
241
+
242
+ for (var [name, message] of (_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : []) {
162
243
  var _config$initialError;
163
244
 
164
- if (_error !== null && _error !== void 0 && _error.message) {
165
- result[key] = _error.message;
166
- }
167
- }
245
+ var [key, ...paths] = getPaths(name);
168
246
 
169
- return result;
170
- });
171
- useEffect(() => {
172
- /**
173
- * Reset the error state of each field if its validity is changed.
174
- *
175
- * This is a workaround as no official way is provided to notify
176
- * when the validity of the field is changed from `invalid` to `valid`.
177
- */
178
- var resetError = form => {
179
- setError(prev => {
180
- var next = prev;
247
+ if (typeof key === 'string') {
248
+ var _initialError$key;
181
249
 
182
- for (var field of form.elements) {
183
- if (isFieldElement(field)) {
184
- var key = getKey(field.name, config === null || config === void 0 ? void 0 : config.name);
185
-
186
- if (key) {
187
- var _next$key, _next;
188
-
189
- var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
190
- var nextMessage = field.validationMessage;
191
- /**
192
- * Techincally, checking prevMessage not being empty while nextMessage being empty
193
- * is sufficient for our usecase. It checks if the message is changed instead to allow
194
- * the hook to be useful independently.
195
- */
196
-
197
- if (prevMessage !== '' && prevMessage !== nextMessage) {
198
- next = _objectSpread2(_objectSpread2({}, next), {}, {
199
- [key]: nextMessage
200
- });
201
- }
202
- }
203
- }
250
+ var scopedName = getName(paths);
251
+ var entries = (_initialError$key = initialError[key]) !== null && _initialError$key !== void 0 ? _initialError$key : [];
252
+
253
+ if (scopedName === '' && entries.length > 0 && entries[0][0] !== '') {
254
+ initialError[key] = [[scopedName, message], ...entries];
255
+ } else {
256
+ initialError[key] = [...entries, [scopedName, message]];
204
257
  }
258
+ }
259
+ }
205
260
 
206
- return next;
207
- });
261
+ return {
262
+ defaultValue: (_config$defaultValue = config === null || config === void 0 ? void 0 : config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : {},
263
+ initialError
208
264
  };
265
+ });
266
+ var [error, setError] = useState(() => {
267
+ var result = {};
209
268
 
210
- var handleInput = event => {
211
- var form = getFormElement(ref.current);
212
- var field = event.target;
269
+ for (var [key, entries] of Object.entries(uncontrolledState.initialError)) {
270
+ var _entries$;
213
271
 
214
- if (!form || !isFieldElement(field) || field.form !== form) {
215
- return;
216
- }
272
+ var [name, message] = (_entries$ = entries === null || entries === void 0 ? void 0 : entries[0]) !== null && _entries$ !== void 0 ? _entries$ : [];
217
273
 
218
- resetError(form);
219
- };
274
+ if (name === '') {
275
+ result[key] = message !== null && message !== void 0 ? message : '';
276
+ }
277
+ }
220
278
 
279
+ return result;
280
+ });
281
+ useEffect(() => {
282
+ configRef.current = config;
283
+ });
284
+ useEffect(() => {
221
285
  var invalidHandler = event => {
286
+ var _configRef$current$na, _configRef$current;
287
+
222
288
  var form = getFormElement(ref.current);
223
289
  var field = event.target;
290
+ var fieldsetName = (_configRef$current$na = (_configRef$current = configRef.current) === null || _configRef$current === void 0 ? void 0 : _configRef$current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
224
291
 
225
- if (!form || !isFieldElement(field) || field.form !== form) {
292
+ if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
226
293
  return;
227
294
  }
228
295
 
229
- 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
296
+ var [key, ...paths] = getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name); // Update the error only if the field belongs to the fieldset
230
297
 
231
- if (key) {
232
- setError(prev => {
233
- var _prev$key;
298
+ if (typeof key === 'string' && paths.length === 0) {
299
+ if (field.dataset.conformTouched) {
300
+ setError(prev => {
301
+ var _prev$key;
234
302
 
235
- var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : '';
303
+ var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : '';
236
304
 
237
- if (prevMessage === field.validationMessage) {
238
- return prev;
239
- }
305
+ if (prevMessage === field.validationMessage) {
306
+ return prev;
307
+ }
240
308
 
241
- return _objectSpread2(_objectSpread2({}, prev), {}, {
242
- [key]: field.validationMessage
309
+ return _objectSpread2(_objectSpread2({}, prev), {}, {
310
+ [key]: field.validationMessage
311
+ });
243
312
  });
244
- });
313
+ }
314
+
245
315
  event.preventDefault();
246
316
  }
247
317
  };
@@ -251,53 +321,69 @@ function useFieldset(ref, config) {
251
321
 
252
322
  if (!form || event.target !== form) {
253
323
  return;
254
- } // This helps resetting error that fullfilled by the submitter
324
+ }
325
+ /**
326
+ * Reset the error state of each field if its validity is changed.
327
+ *
328
+ * This is a workaround as no official way is provided to notify
329
+ * when the validity of the field is changed from `invalid` to `valid`.
330
+ */
331
+
255
332
 
333
+ setError(prev => {
334
+ var _configRef$current$na2, _configRef$current2;
335
+
336
+ var next = prev;
337
+ var fieldsetName = (_configRef$current$na2 = (_configRef$current2 = configRef.current) === null || _configRef$current2 === void 0 ? void 0 : _configRef$current2.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
338
+
339
+ for (var field of form.elements) {
340
+ if (isFieldElement(field) && field.name.startsWith(fieldsetName)) {
341
+ var _next$key, _next;
342
+
343
+ var key = fieldsetName ? field.name.slice(fieldsetName.length + 1) : field.name;
344
+ var prevMessage = (_next$key = (_next = next) === null || _next === void 0 ? void 0 : _next[key]) !== null && _next$key !== void 0 ? _next$key : '';
345
+ var nextMessage = field.validationMessage;
346
+
347
+ if (prevMessage !== '' && nextMessage === '') {
348
+ next = _objectSpread2(_objectSpread2({}, next), {}, {
349
+ [key]: ''
350
+ });
351
+ }
352
+ }
353
+ }
256
354
 
257
- resetError(form);
355
+ return next;
356
+ });
258
357
  };
259
358
 
260
359
  var resetHandler = event => {
360
+ var _fieldsetConfig$defau;
361
+
261
362
  var form = getFormElement(ref.current);
262
363
 
263
364
  if (!form || event.target !== form) {
264
365
  return;
265
366
  }
266
367
 
368
+ var fieldsetConfig = configRef.current;
369
+ setUncontrolledState({
370
+ // @ts-expect-error
371
+ defaultValue: (_fieldsetConfig$defau = fieldsetConfig === null || fieldsetConfig === void 0 ? void 0 : fieldsetConfig.defaultValue) !== null && _fieldsetConfig$defau !== void 0 ? _fieldsetConfig$defau : {},
372
+ initialError: {}
373
+ });
267
374
  setError({});
268
- };
375
+ }; // The invalid event does not bubble and so listening on the capturing pharse is needed
269
376
 
270
- document.addEventListener('input', handleInput); // The invalid event does not bubble and so listening on the capturing pharse is needed
271
377
 
272
378
  document.addEventListener('invalid', invalidHandler, true);
273
379
  document.addEventListener('submit', submitHandler);
274
380
  document.addEventListener('reset', resetHandler);
275
381
  return () => {
276
- document.removeEventListener('input', handleInput);
277
382
  document.removeEventListener('invalid', invalidHandler, true);
278
383
  document.removeEventListener('submit', submitHandler);
279
384
  document.removeEventListener('reset', resetHandler);
280
385
  };
281
- }, [ref, config === null || config === void 0 ? void 0 : config.name]);
282
- useEffect(() => {
283
- setError(prev => {
284
- var next = prev;
285
-
286
- 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 : {})) {
287
- var _config$initialError2;
288
-
289
- if (next[key] !== (_error2 === null || _error2 === void 0 ? void 0 : _error2.message)) {
290
- var _error2$message;
291
-
292
- next = _objectSpread2(_objectSpread2({}, next), {}, {
293
- [key]: (_error2$message = _error2 === null || _error2 === void 0 ? void 0 : _error2.message) !== null && _error2$message !== void 0 ? _error2$message : ''
294
- });
295
- }
296
- }
297
-
298
- return next;
299
- });
300
- }, [config === null || config === void 0 ? void 0 : config.name, config === null || config === void 0 ? void 0 : config.initialError]);
386
+ }, [ref]);
301
387
  /**
302
388
  * This allows us constructing the field at runtime as we have no information
303
389
  * about which fields would be available. The proxy will also help tracking
@@ -306,19 +392,20 @@ function useFieldset(ref, config) {
306
392
 
307
393
  return new Proxy({}, {
308
394
  get(_target, key) {
309
- var _constraint, _config$defaultValue, _config$initialError$, _config$initialError3, _config$initialError4, _config$initialError5, _config$initialError6, _error$key;
395
+ var _fieldsetConfig$const, _error$key;
310
396
 
311
397
  if (typeof key !== 'string') {
312
398
  return;
313
399
  }
314
400
 
315
- var constraint = config === null || config === void 0 ? void 0 : (_constraint = config.constraint) === null || _constraint === void 0 ? void 0 : _constraint[key];
401
+ var fieldsetConfig = config !== null && config !== void 0 ? config : {};
402
+ var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key];
316
403
  var field = {
317
404
  config: _objectSpread2({
318
- name: config !== null && config !== void 0 && config.name ? "".concat(config.name, ".").concat(key) : key,
319
- form: config === null || config === void 0 ? void 0 : config.form,
320
- defaultValue: config === null || config === void 0 ? void 0 : (_config$defaultValue = config.defaultValue) === null || _config$defaultValue === void 0 ? void 0 : _config$defaultValue[key],
321
- initialError: (_config$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) !== null && _config$initialError$ !== void 0 ? _config$initialError$ : config === null || config === void 0 ? void 0 : (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[key]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.message
405
+ name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
406
+ form: fieldsetConfig.form,
407
+ defaultValue: uncontrolledState.defaultValue[key],
408
+ initialError: uncontrolledState.initialError[key]
322
409
  }, constraint),
323
410
  error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : ''
324
411
  };
@@ -332,25 +419,55 @@ function useFieldset(ref, config) {
332
419
  * Returns a list of key and config, with a group of helpers
333
420
  * configuring buttons for list manipulation
334
421
  *
335
- * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist
422
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usefieldlist
336
423
  */
337
424
  function useFieldList(ref, config) {
338
- var [entries, setEntries] = useState(() => {
425
+ var configRef = useRef(config);
426
+ var [uncontrolledState, setUncontrolledState] = useState(() => {
339
427
  var _config$defaultValue2;
340
428
 
341
- return Object.entries((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [undefined]);
429
+ var initialError = [];
430
+
431
+ for (var [name, message] of (_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : []) {
432
+ var _config$initialError2;
433
+
434
+ var [index, ...paths] = getPaths(name);
435
+
436
+ if (typeof index === 'number') {
437
+ var _initialError$index;
438
+
439
+ var scopedName = getName(paths);
440
+
441
+ var _entries = (_initialError$index = initialError[index]) !== null && _initialError$index !== void 0 ? _initialError$index : [];
442
+
443
+ if (scopedName === '' && _entries.length > 0 && _entries[0][0] !== '') {
444
+ initialError[index] = [[scopedName, message], ..._entries];
445
+ } else {
446
+ initialError[index] = [..._entries, [scopedName, message]];
447
+ }
448
+ }
449
+ }
450
+
451
+ return {
452
+ defaultValue: (_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [],
453
+ initialError
454
+ };
342
455
  });
343
- var list = entries.map((_ref, index) => {
344
- var _config$defaultValue3, _config$initialError7, _config$initialError8;
456
+ var [entries, setEntries] = useState(() => {
457
+ var _config$defaultValue3;
345
458
 
346
- var [key, defaultValue] = _ref;
459
+ return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]);
460
+ });
461
+ var list = entries.map((_ref3, index) => {
462
+ var [key, defaultValue] = _ref3;
347
463
  return {
348
464
  key,
349
- config: _objectSpread2(_objectSpread2({}, config), {}, {
465
+ config: {
350
466
  name: "".concat(config.name, "[").concat(index, "]"),
351
- defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue3 = config.defaultValue) === null || _config$defaultValue3 === void 0 ? void 0 : _config$defaultValue3[index],
352
- initialError: (_config$initialError7 = config.initialError) === null || _config$initialError7 === void 0 ? void 0 : (_config$initialError8 = _config$initialError7[index]) === null || _config$initialError8 === void 0 ? void 0 : _config$initialError8.details
353
- })
467
+ form: config.form,
468
+ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
469
+ initialError: uncontrolledState.initialError[index]
470
+ }
354
471
  };
355
472
  });
356
473
  /***
@@ -363,9 +480,10 @@ function useFieldList(ref, config) {
363
480
  return function () {
364
481
  var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
365
482
  return {
366
- name: listCommandKey,
367
- value: serializeListCommand(config.name, {
483
+ name: 'conform/list',
484
+ value: JSON.stringify({
368
485
  type,
486
+ scope: config.name,
369
487
  payload
370
488
  }),
371
489
  form: config.form,
@@ -376,56 +494,45 @@ function useFieldList(ref, config) {
376
494
 
377
495
  });
378
496
  useEffect(() => {
379
- setEntries(prevEntries => {
380
- var _config$defaultValue4;
381
-
382
- var nextEntries = Object.entries((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : [undefined]);
383
-
384
- if (prevEntries.length !== nextEntries.length) {
385
- return nextEntries;
386
- }
387
-
388
- for (var i = 0; i < prevEntries.length; i++) {
389
- var [prevKey, prevValue] = prevEntries[i];
390
- var [nextKey, nextValue] = nextEntries[i];
391
-
392
- if (prevKey !== nextKey || prevValue !== nextValue) {
393
- return nextEntries;
394
- }
395
- } // No need to rerender in this case
396
-
397
-
398
- return prevEntries;
399
- });
400
-
497
+ configRef.current = config;
498
+ });
499
+ useEffect(() => {
401
500
  var submitHandler = event => {
402
501
  var form = getFormElement(ref.current);
403
502
 
404
- if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== listCommandKey) {
503
+ if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') {
405
504
  return;
406
505
  }
407
506
 
408
- var [name, command] = parseListCommand(event.submitter.value);
507
+ var command = parseListCommand(event.submitter.value);
409
508
 
410
- if (name !== config.name) {
509
+ if (command.scope !== configRef.current.name) {
411
510
  // Ensure the scope of the listener are limited to specific field name
412
511
  return;
413
512
  }
414
513
 
415
- switch (command.type) {
416
- case 'append':
417
- case 'prepend':
418
- case 'replace':
419
- command.payload.defaultValue = ["".concat(Date.now()), command.payload.defaultValue];
420
- break;
421
- }
422
-
423
- setEntries(entries => updateList([...(entries !== null && entries !== void 0 ? entries : [])], command));
514
+ setEntries(entries => {
515
+ switch (command.type) {
516
+ case 'append':
517
+ case 'prepend':
518
+ case 'replace':
519
+ return updateList([...(entries !== null && entries !== void 0 ? entries : [])], _objectSpread2(_objectSpread2({}, command), {}, {
520
+ payload: _objectSpread2(_objectSpread2({}, command.payload), {}, {
521
+ defaultValue: ["".concat(Date.now()), command.payload.defaultValue]
522
+ })
523
+ }));
524
+
525
+ default:
526
+ {
527
+ return updateList([...(entries !== null && entries !== void 0 ? entries : [])], command);
528
+ }
529
+ }
530
+ });
424
531
  event.preventDefault();
425
532
  };
426
533
 
427
534
  var resetHandler = event => {
428
- var _config$defaultValue5;
535
+ var _fieldConfig$defaultV, _fieldConfig$defaultV2;
429
536
 
430
537
  var form = getFormElement(ref.current);
431
538
 
@@ -433,7 +540,12 @@ function useFieldList(ref, config) {
433
540
  return;
434
541
  }
435
542
 
436
- setEntries(Object.entries((_config$defaultValue5 = config.defaultValue) !== null && _config$defaultValue5 !== void 0 ? _config$defaultValue5 : []));
543
+ var fieldConfig = configRef.current;
544
+ setUncontrolledState({
545
+ defaultValue: (_fieldConfig$defaultV = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV !== void 0 ? _fieldConfig$defaultV : [],
546
+ initialError: []
547
+ });
548
+ setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined]));
437
549
  };
438
550
 
439
551
  document.addEventListener('submit', submitHandler, true);
@@ -442,7 +554,7 @@ function useFieldList(ref, config) {
442
554
  document.removeEventListener('submit', submitHandler, true);
443
555
  document.removeEventListener('reset', resetHandler);
444
556
  };
445
- }, [ref, config.name, config.defaultValue]);
557
+ }, [ref]);
446
558
  return [list, control];
447
559
  }
448
560
 
@@ -451,14 +563,19 @@ function useFieldList(ref, config) {
451
563
  * This is particular useful when integrating dropdown and datepicker whichs
452
564
  * introduces custom input mode.
453
565
  *
454
- * @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput
566
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.1/packages/conform-react/README.md#usecontrolledinput
455
567
  */
456
- function useControlledInput(field) {
457
- var _field$defaultValue;
568
+ function useControlledInput(config) {
569
+ var _config$defaultValue4;
458
570
 
459
571
  var ref = useRef(null);
460
572
  var inputRef = useRef(null);
461
- var [value, setValue] = useState("".concat((_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : ''));
573
+ var configRef = useRef(config);
574
+ var [uncontrolledState, setUncontrolledState] = useState({
575
+ defaultValue: config.defaultValue,
576
+ initialError: config.initialError
577
+ });
578
+ var [value, setValue] = useState("".concat((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : ''));
462
579
 
463
580
  var handleChange = eventOrValue => {
464
581
  if (!ref.current) {
@@ -485,6 +602,31 @@ function useControlledInput(field) {
485
602
  event.preventDefault();
486
603
  };
487
604
 
605
+ useEffect(() => {
606
+ configRef.current = config;
607
+ });
608
+ useEffect(() => {
609
+ var resetHandler = event => {
610
+ var _configRef$current$de;
611
+
612
+ var form = getFormElement(ref.current);
613
+
614
+ if (!form || event.target !== form) {
615
+ return;
616
+ }
617
+
618
+ setUncontrolledState({
619
+ defaultValue: configRef.current.defaultValue,
620
+ initialError: configRef.current.initialError
621
+ });
622
+ setValue("".concat((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : ''));
623
+ };
624
+
625
+ document.addEventListener('reset', resetHandler);
626
+ return () => {
627
+ document.removeEventListener('reset', resetHandler);
628
+ };
629
+ }, []);
488
630
  return [_objectSpread2({
489
631
  ref,
490
632
  style: {
@@ -505,7 +647,7 @@ function useControlledInput(field) {
505
647
  (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
506
648
  }
507
649
 
508
- }, input(field, {
650
+ }, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState), {
509
651
  type: 'text'
510
652
  })), {
511
653
  ref: inputRef,