@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/module/hooks.js CHANGED
@@ -1,375 +1,492 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
2
- import { getFieldElements, setFieldState, isFieldElement, getName, createFieldConfig, shouldSkipValidate, reportValidity, createControlButton } from '@conform-to/dom';
3
- import { useRef, useState, useEffect, useReducer, useMemo, createElement } from 'react';
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
- onReset,
8
- onSubmit,
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 [formNoValidate, setFormNoValidate] = useState(noValidate || !fallbackNative);
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
- setFormNoValidate(true);
20
+ setNoValidate(true);
39
21
  }, []);
40
22
  useEffect(() => {
41
- if (noValidate) {
42
- return;
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 handleChange = event => {
46
- var _event$target;
29
+ var handleInput = event => {
30
+ var field = event.target;
31
+ var form = ref.current;
47
32
 
48
- if (!isFieldElement(event.target) || ((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.form) !== ref.current) {
33
+ if (!form || !isFieldElement(field) || field.form !== form) {
49
34
  return;
50
35
  }
51
36
 
52
- if (initialReport === 'onChange') {
53
- setFieldState(event.target, {
54
- touched: true
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
- if (ref.current) {
59
- reportValidity(ref.current);
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 _event$target2;
55
+ var field = event.target;
56
+ var form = ref.current;
65
57
 
66
- if (!isFieldElement(event.target) || ((_event$target2 = event.target) === null || _event$target2 === void 0 ? void 0 : _event$target2.form) !== ref.current) {
58
+ if (!form || !isFieldElement(field) || field.form !== form || config.noValidate || config.initialReport !== 'onBlur') {
67
59
  return;
68
60
  }
69
61
 
70
- if (initialReport === 'onBlur') {
71
- setFieldState(event.target, {
72
- touched: true
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 (ref.current) {
77
- reportValidity(ref.current);
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.body.addEventListener('input', handleChange);
82
- document.body.addEventListener('focusout', handleBlur);
97
+ document.addEventListener('input', handleInput, true);
98
+ document.addEventListener('blur', handleBlur, true);
99
+ document.addEventListener('reset', handleReset);
83
100
  return () => {
84
- document.body.removeEventListener('input', handleChange);
85
- document.body.removeEventListener('focusout', handleBlur);
101
+ document.removeEventListener('input', handleInput, true);
102
+ document.removeEventListener('blur', handleBlur, true);
103
+ document.removeEventListener('reset', handleReset);
86
104
  };
87
- }, [noValidate, initialReport]);
105
+ }, [validate, config.initialReport, config.noValidate]);
88
106
  return {
89
107
  ref,
90
- onSubmit: handleSubmit,
91
- onReset: handleReset,
92
- noValidate: formNoValidate
93
- };
94
- }
95
- function useFieldset(schema) {
96
- var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
97
- var ref = useRef(null);
98
- var [errorMessage, dispatch] = useReducer((state, action) => {
99
- switch (action.type) {
100
- case 'report':
101
- {
102
- var {
103
- key,
104
- message
105
- } = action.payload;
106
-
107
- if (state[key] === message) {
108
- return state;
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
- return _objectSpread2(_objectSpread2({}, state), {}, {
112
- [key]: message
113
- });
131
+
132
+ if (!event.currentTarget.reportValidity()) {
133
+ event.preventDefault();
114
134
  }
135
+ }
115
136
 
116
- case 'migrate':
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
- return nextState;
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
- case 'cleanup':
139
- {
140
- var {
141
- fieldset
142
- } = action.payload;
143
- var updates = [];
150
+ function useFieldset(ref, config) {
151
+ var [error, setError] = useState(() => {
152
+ var result = {};
144
153
 
145
- for (var [_key2, _message] of Object.entries(state)) {
146
- if (!_message) {
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
- var fields = getFieldElements(fieldset, _key2);
157
+ if (_error !== null && _error !== void 0 && _error.message) {
158
+ result[key] = _error.message;
159
+ }
160
+ }
151
161
 
152
- if (fields.every(field => field.validity.valid)) {
153
- updates.push([_key2, '']);
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
- if (updates.length === 0) {
158
- return state;
159
- }
199
+ return next;
200
+ });
201
+ };
160
202
 
161
- return _objectSpread2(_objectSpread2({}, state), Object.fromEntries(updates));
162
- }
203
+ var handleInput = event => {
204
+ var form = getFormElement(ref.current);
205
+ var field = event.target;
163
206
 
164
- case 'reset':
165
- {
166
- return {};
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
- var error = (_config$error = config.error) === null || _config$error === void 0 ? void 0 : _config$error[name];
211
+ resetError(form);
212
+ };
173
213
 
174
- if (typeof error === 'string') {
175
- result.push([name, error]);
176
- }
214
+ var invalidHandler = event => {
215
+ var form = getFormElement(ref.current);
216
+ var field = event.target;
177
217
 
178
- return result;
179
- }, [])));
180
- useEffect(() => {
181
- var _schema$validate;
218
+ if (!form || !isFieldElement(field) || field.form !== form) {
219
+ return;
220
+ }
182
221
 
183
- var fieldset = ref.current;
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
- if (!fieldset) {
186
- console.warn('No fieldset ref found; You must pass the fieldsetProps to the fieldset element');
187
- return;
188
- }
224
+ if (key) {
225
+ setError(prev => {
226
+ var _prev$key;
189
227
 
190
- if (!(fieldset !== null && fieldset !== void 0 && fieldset.form)) {
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
- (_schema$validate = schema.validate) === null || _schema$validate === void 0 ? void 0 : _schema$validate.call(schema, fieldset);
195
- dispatch({
196
- type: 'cleanup',
197
- payload: {
198
- fieldset
199
- }
200
- });
201
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
202
- [schema.validate]);
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
- }, [config.error, schema.fields]);
212
- return [{
213
- ref,
214
- name: config.name,
215
- form: config.form,
216
-
217
- onInput(e) {
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
- onInvalid(e) {
240
- var element = isFieldElement(e.target) ? e.target : null;
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
- if (!element || !key) {
253
+ var resetHandler = event => {
254
+ var form = getFormElement(ref.current);
255
+
256
+ if (!form || event.target !== form) {
244
257
  return;
245
- } // Disable browser report
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
- e.preventDefault();
249
- dispatch({
250
- type: 'report',
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
- }, createFieldConfig(schema, _objectSpread2(_objectSpread2({}, config), {}, {
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: "".concat(key),
341
+ key,
272
342
  config: _objectSpread2(_objectSpread2({}, config), {}, {
273
343
  name: "".concat(config.name, "[").concat(index, "]"),
274
- initialValue: (_config$initialValue2 = config.initialValue) === null || _config$initialValue2 === void 0 ? void 0 : _config$initialValue2[index],
275
- error: (_config$error2 = config.error) === null || _config$error2 === void 0 ? void 0 : _config$error2[index],
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
- }), [keys, config]);
283
- var controls = {
284
- prepend() {
285
- return _objectSpread2(_objectSpread2({}, createControlButton(config.name, 'prepend', {})), {}, {
286
- onClick(e) {
287
- setKeys(keys => [Date.now(), ...keys]);
288
- e.preventDefault();
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
- append() {
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
- remove(index) {
305
- return _objectSpread2(_objectSpread2({}, createControlButton(config.name, 'remove', {
306
- index
307
- })), {}, {
308
- onClick(e) {
309
- setKeys(keys => [...keys.slice(0, index), ...keys.slice(index + 1)]);
310
- e.preventDefault();
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
- useEffect(() => {
318
- setKeys(keys => {
319
- if (keys.length === size) {
320
- return keys;
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
- return [...Array(size).keys()];
324
- });
325
- }, [size]);
326
- return [list, controls];
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$initialValue;
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 input = useMemo(() => /*#__PURE__*/createElement('input', _objectSpread2(_objectSpread2({}, field.constraint), {}, {
335
- ref,
336
- name: field.name,
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
- ref.current.value = value;
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
- }, [value]);
353
- useEffect(() => {
354
- var _ref$current;
465
+ setValue(newValue);
466
+ };
355
467
 
356
- if (!shouldBlur) {
357
- return;
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('focusout', {
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
- setShouldBlur(false);
364
- }, [shouldBlur]);
365
- return [input, {
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: value => {
368
- setValue(value);
369
- },
370
- onBlur: () => {
371
- setShouldBlur(true);
372
- }
487
+ onChange: handleChange,
488
+ onBlur: handleBlur,
489
+ onInvalid: handleInvalid
373
490
  }];
374
491
  }
375
492