@conform-to/react 0.9.1 → 1.0.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/hooks.mjs CHANGED
@@ -1,728 +1,154 @@
1
- import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
- import { parseIntent, getFormData, parse, VALIDATION_UNDEFINED, VALIDATION_SKIPPED, getFormAction, getFormEncType, getFormMethod, getPaths, getName, isFieldElement, getErrors, getFormControls, getFormElement, updateList, getValidationMessage, focusFirstInvalidControl, isFocusableFormControl, requestIntent, validate } from '@conform-to/dom';
3
- import { useState, useMemo, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
1
+ import { createForm } from '@conform-to/dom';
2
+ import { useState, useRef, useEffect, useLayoutEffect, useId } from 'react';
3
+ import { useSubjectRef, useFormStore, useFormContext, getBaseMetadata, getFieldMetadata } from './context.mjs';
4
4
 
5
5
  /**
6
- * Properties to be applied to the form element
6
+ * useLayoutEffect is client-only.
7
+ * This basically makes it a no-op on server
7
8
  */
8
-
9
- function useNoValidate(defaultNoValidate, validateBeforeHydrate) {
10
- var [noValidate, setNoValidate] = useState(defaultNoValidate || !validateBeforeHydrate);
11
- useEffect(() => {
12
- setNoValidate(true);
13
- }, []);
14
- return noValidate;
15
- }
16
- function useFormRef(userProvidedRef) {
17
- var formRef = useRef(null);
18
- return userProvidedRef !== null && userProvidedRef !== void 0 ? userProvidedRef : formRef;
9
+ var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
10
+ function useFormId(preferredId) {
11
+ var id = useId();
12
+ return preferredId !== null && preferredId !== void 0 ? preferredId : id;
19
13
  }
20
- function useConfigRef(config) {
21
- var ref = useRef(config);
22
- useSafeLayoutEffect(() => {
23
- ref.current = config;
14
+ function useForm(options) {
15
+ var formId = useFormId(options.id);
16
+ var initializeForm = () => createForm(formId, options);
17
+ var [form, setForm] = useState(initializeForm);
18
+
19
+ // If id changes, reinitialize the form immediately
20
+ if (formId !== form.id) {
21
+ setForm(initializeForm);
22
+ }
23
+ var optionsRef = useRef(options);
24
+ var metadata = useFormMetadata({
25
+ formId,
26
+ context: form,
27
+ defaultNoValidate: options.defaultNoValidate
24
28
  });
25
- return ref;
26
- }
27
- function useFormReporter(ref, lastSubmission) {
28
- var [submission, setSubmission] = useState(lastSubmission);
29
- var report = useCallback((form, submission) => {
30
- var event = new CustomEvent('conform', {
31
- detail: submission.intent
32
- });
33
- form.dispatchEvent(event);
34
- setSubmission(submission);
35
- }, []);
36
- useEffect(() => {
37
- var form = ref.current;
38
- if (!form || !lastSubmission) {
39
- return;
40
- }
41
- if (!lastSubmission.payload) {
42
- // If the default value is empty, we can safely reset the form.
43
- // This ensure the behavior is consistent with and without JS.
44
- form.reset();
45
-
46
- // There is no need to report the submission anymore.
29
+ var fields = useFieldset({
30
+ formId,
31
+ context: form
32
+ });
33
+ useSafeLayoutEffect(() => form.initialize(), [form]);
34
+ useSafeLayoutEffect(() => {
35
+ if (options.lastResult === optionsRef.current.lastResult) {
36
+ // If there is no change, do nothing
47
37
  return;
48
38
  }
49
- report(form, lastSubmission);
50
- }, [ref, lastSubmission, report]);
51
- useEffect(() => {
52
- var form = ref.current;
53
- if (!form || !submission) {
54
- return;
39
+ if (options.lastResult) {
40
+ form.report(options.lastResult);
41
+ } else {
42
+ var _document$forms$named;
43
+ (_document$forms$named = document.forms.namedItem(form.id)) === null || _document$forms$named === void 0 ? void 0 : _document$forms$named.reset();
55
44
  }
56
- reportSubmission(form, submission);
57
- }, [ref, submission]);
58
- return report;
59
- }
60
- function useFormError(ref, config) {
61
- var [error, setError] = useState(() => {
62
- if (!config.initialError) {
63
- return {};
64
- }
65
- var result = {};
66
- for (var [name, message] of Object.entries(config.initialError)) {
67
- var [path, ...restPaths] = getPaths(name);
68
- if (typeof path !== 'undefined' && restPaths.length === 0) {
69
- result[path] = message;
70
- }
71
- }
72
- return result;
45
+ }, [form, options.lastResult]);
46
+ useSafeLayoutEffect(() => {
47
+ optionsRef.current = options;
48
+ form.update(options);
73
49
  });
74
- useEffect(() => {
75
- var handleInvalid = event => {
76
- var _config$name;
77
- var form = getFormElement(ref.current);
78
- var element = event.target;
79
- var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : '';
80
- if (!isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) {
81
- return;
82
- }
83
- var name = element.name.slice(prefix.length);
84
- var [path, ...restPaths] = getPaths(name);
85
- if (typeof path === 'undefined' || restPaths.length > 0) {
86
- return;
87
- }
88
- setError(prev => {
89
- if (element.validationMessage === getValidationMessage(prev[path])) {
90
- return prev;
91
- }
92
- return _objectSpread2(_objectSpread2({}, prev), {}, {
93
- [path]: getErrors(element.validationMessage)
94
- });
95
- });
96
- event.preventDefault();
97
- };
98
- var handleReset = event => {
99
- var form = getFormElement(ref.current);
100
- if (form && event.target === form) {
101
- setError({});
102
- }
103
- };
104
- document.addEventListener('reset', handleReset);
105
- document.addEventListener('invalid', handleInvalid, true);
106
- return () => {
107
- document.removeEventListener('reset', handleReset);
108
- document.removeEventListener('invalid', handleInvalid, true);
109
- };
110
- }, [ref, config.name]);
111
- return [error, setError];
50
+ return {
51
+ context: form,
52
+ fields,
53
+ form: metadata
54
+ };
112
55
  }
113
-
114
- /**
115
- * Returns properties required to hook into form events.
116
- * Applied custom validation and define when error should be reported.
117
- *
118
- * @see https://conform.guide/api/react#useform
119
- */
120
- function useForm() {
121
- var _config$lastSubmissio3, _config$lastSubmissio4;
122
- var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
123
- var configRef = useConfigRef(config);
124
- var ref = useFormRef(config.ref);
125
- var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
126
- var report = useFormReporter(ref, config.lastSubmission);
127
- var [errors, setErrors] = useState(() => {
128
- var _config$lastSubmissio, _config$lastSubmissio2;
129
- return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : [];
56
+ function useFormMetadata(options) {
57
+ var _options$defaultNoVal;
58
+ var subjectRef = useSubjectRef();
59
+ var form = useFormStore(options.formId, options.context);
60
+ var context = useFormContext(form, subjectRef);
61
+ var metadata = getBaseMetadata(options.formId, context, {
62
+ subjectRef
130
63
  });
131
- var initialError = useMemo(() => {
132
- var _submission$error$sco;
133
- var submission = config.lastSubmission;
134
- if (!submission) {
135
- return {};
136
- }
137
- var intent = parseIntent(submission.intent);
138
- var scope = getScope(intent);
139
- if (typeof scope !== 'string') {
140
- return submission.error;
141
- }
142
- return {
143
- [scope]: (_submission$error$sco = submission.error[scope]) !== null && _submission$error$sco !== void 0 ? _submission$error$sco : []
144
- };
145
- }, [config.lastSubmission]);
146
- // This payload from lastSubmission is only useful before hydration
147
- // After hydration, any new payload on lastSubmission will be ignored
148
- var [defaultValueFromLastSubmission, setDefaultValueFromLastSubmission] = useState( // @ts-expect-error defaultValue is not in Submission type
149
- (_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null);
150
- var fieldset = useFieldset(ref, {
151
- defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue,
152
- initialError,
153
- constraint: config.constraint,
154
- form: config.id
155
- });
156
- useEffect(() => {
157
- // custom validate handler
158
- var createValidateHandler = type => event => {
159
- var field = event.target;
160
- var form = ref.current;
161
- var {
162
- shouldValidate = 'onSubmit',
163
- shouldRevalidate = shouldValidate
164
- } = configRef.current;
165
- if (!form || !isFocusableFormControl(field) || field.form !== form || !field.name) {
166
- return;
167
- }
168
- if (field.dataset.conformTouched ? shouldRevalidate === type : shouldValidate === type) {
169
- requestIntent(form, validate(field.name));
170
- }
171
- };
172
- var handleInvalid = event => {
173
- var form = ref.current;
174
- var field = event.target;
175
- if (!form || !isFieldElement(field) || field.form !== form || field.name !== FORM_ERROR_ELEMENT_NAME) {
176
- return;
177
- }
178
- event.preventDefault();
179
- if (field.dataset.conformTouched) {
180
- setErrors(getErrors(field.validationMessage));
181
- }
182
- };
183
- var handleReset = event => {
184
- var form = ref.current;
185
- if (!form || event.target !== form) {
186
- return;
187
- }
188
-
189
- // Reset all field state
190
- for (var _element of getFormControls(form)) {
191
- delete _element.dataset.conformTouched;
192
- _element.setCustomValidity('');
193
- }
194
- setErrors([]);
195
- setDefaultValueFromLastSubmission(null);
196
- };
197
- var handleInput = createValidateHandler('onInput');
198
- var handleBlur = createValidateHandler('onBlur');
199
- document.addEventListener('input', handleInput, true);
200
- document.addEventListener('blur', handleBlur, true);
201
- document.addEventListener('invalid', handleInvalid, true);
202
- document.addEventListener('reset', handleReset);
203
- return () => {
204
- document.removeEventListener('input', handleInput, true);
205
- document.removeEventListener('blur', handleBlur, true);
206
- document.removeEventListener('invalid', handleInvalid, true);
207
- document.removeEventListener('reset', handleReset);
208
- };
209
- }, [ref, configRef]);
210
- var form = {
211
- ref,
212
- error: errors[0],
213
- errors,
214
- props: {
215
- ref,
216
- noValidate,
217
- onSubmit(event) {
218
- var form = event.currentTarget;
219
- var nativeEvent = event.nativeEvent;
220
- var submitter = nativeEvent.submitter;
221
- if (event.defaultPrevented) {
222
- return;
223
- }
224
- try {
225
- var _config$onValidate, _config$onValidate2;
226
- var formData = getFormData(form, submitter);
227
- var submission = (_config$onValidate = (_config$onValidate2 = config.onValidate) === null || _config$onValidate2 === void 0 ? void 0 : _config$onValidate2.call(config, {
228
- form,
229
- formData
230
- })) !== null && _config$onValidate !== void 0 ? _config$onValidate : parse(formData);
231
- var {
232
- errors: _errors,
233
- shouldServerValidate
234
- } = Object.entries(submission.error).reduce((result, _ref) => {
235
- var [, error] = _ref;
236
- for (var message of error) {
237
- if (message === VALIDATION_UNDEFINED) {
238
- result.shouldServerValidate = true;
239
- } else if (message !== VALIDATION_SKIPPED) {
240
- result.errors.push(message);
241
- }
64
+ var [noValidate, setNoValidate] = useState((_options$defaultNoVal = options.defaultNoValidate) !== null && _options$defaultNoVal !== void 0 ? _options$defaultNoVal : true);
65
+ useSafeLayoutEffect(() => {
66
+ // This is necessary to fix an issue in strict mode with related to our proxy setup
67
+ // It avoids the component from being rerendered without re-rendering the child
68
+ // Which reset the proxy but failed to capture its usage within child component
69
+ if (!noValidate) {
70
+ setNoValidate(true);
71
+ }
72
+ }, [noValidate]);
73
+ return new Proxy(metadata, {
74
+ get(target, key, receiver) {
75
+ switch (key) {
76
+ case 'onSubmit':
77
+ return event => {
78
+ var submitEvent = event.nativeEvent;
79
+ var result = form.submit(submitEvent);
80
+ if (submitEvent.defaultPrevented) {
81
+ event.preventDefault();
242
82
  }
243
83
  return result;
244
- }, {
245
- errors: [],
246
- shouldServerValidate: false
247
- });
248
- if (
249
- // has client validation
250
- typeof config.onValidate !== 'undefined' &&
251
- // not necessary to validate on the server
252
- !shouldServerValidate && (
253
- // client validation failed or non submit intent
254
- !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && _errors.length > 0 || parseIntent(submission.intent) !== null)) {
255
- report(form, submission);
256
- event.preventDefault();
257
- } else {
258
- var _config$onSubmit;
259
- (_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
260
- formData,
261
- submission,
262
- action: getFormAction(nativeEvent),
263
- encType: getFormEncType(nativeEvent),
264
- method: getFormMethod(nativeEvent)
265
- });
266
- }
267
- } catch (error) {
268
- // eslint-disable-next-line no-console
269
- console.warn('Client validation failed', error);
270
- }
271
- }
272
- }
273
- };
274
- if (config.id) {
275
- form.id = config.id;
276
- form.errorId = "".concat(config.id, "-error");
277
- form.props.id = form.id;
278
- }
279
- if (form.errorId && form.errors.length > 0) {
280
- form.props['aria-invalid'] = 'true';
281
- form.props['aria-describedby'] = form.errorId;
282
- }
283
- return [form, fieldset];
284
- }
285
-
286
- /**
287
- * A set of field configuration
288
- */
289
-
290
- /**
291
- * Returns all the information about the fieldset.
292
- *
293
- * @see https://conform.guide/api/react#usefieldset
294
- */
295
-
296
- function useFieldset(ref, config) {
297
- var [error] = useFormError(ref, {
298
- initialError: config.initialError,
299
- name: config.name
300
- });
301
-
302
- /**
303
- * This allows us constructing the field at runtime as we have no information
304
- * about which fields would be available. The proxy will also help tracking
305
- * the usage of each field for optimization in the future.
306
- */
307
- return new Proxy({}, {
308
- get(_target, key) {
309
- var _fieldsetConfig$const, _fieldsetConfig$initi, _fieldsetConfig$defau;
310
- if (typeof key !== 'string') {
311
- return;
312
- }
313
- var fieldsetConfig = config;
314
- var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key];
315
- var errors = error === null || error === void 0 ? void 0 : error[key];
316
- var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref2) => {
317
- var [name, message] = _ref2;
318
- var [field, ...paths] = getPaths(name);
319
- if (field === key) {
320
- result[getName(paths)] = message;
321
- }
322
- return result;
323
- }, {});
324
- var field = _objectSpread2(_objectSpread2({}, constraint), {}, {
325
- name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
326
- // @ts-expect-error The FieldValue type might need a rework
327
- defaultValue: (_fieldsetConfig$defau = fieldsetConfig.defaultValue) === null || _fieldsetConfig$defau === void 0 ? void 0 : _fieldsetConfig$defau[key],
328
- initialError,
329
- error: errors === null || errors === void 0 ? void 0 : errors[0],
330
- errors
331
- });
332
- if (fieldsetConfig.form) {
333
- field.form = fieldsetConfig.form;
334
- field.id = "".concat(fieldsetConfig.form, "-").concat(field.name);
335
- field.errorId = "".concat(field.id, "-error");
336
- field.descriptionId = "".concat(field.id, "-description");
84
+ };
85
+ case 'onReset':
86
+ return event => form.reset(event.nativeEvent);
87
+ case 'noValidate':
88
+ return noValidate;
337
89
  }
338
- return field;
90
+ return Reflect.get(target, key, receiver);
339
91
  }
340
92
  });
341
93
  }
342
-
343
- /**
344
- * Returns a list of key and field config.
345
- *
346
- * @see https://conform.guide/api/react#usefieldlist
347
- */
348
- function useFieldList(ref, config) {
349
- var configRef = useConfigRef(config);
350
- var [error, setError] = useFormError(ref, {
351
- initialError: config.initialError,
352
- name: config.name
353
- });
354
- var [entries, setEntries] = useState(() => {
355
- var _config$defaultValue;
356
- return Object.entries((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : []);
357
- });
358
- useEffect(() => {
359
- var conformHandler = event => {
360
- var form = getFormElement(ref.current);
361
- if (!form || event.target !== form) {
362
- return;
363
- }
364
- var intent = parseIntent(event.detail);
365
- if ((intent === null || intent === void 0 ? void 0 : intent.type) !== 'list' || (intent === null || intent === void 0 ? void 0 : intent.payload.name) !== configRef.current.name) {
366
- return;
367
- }
368
- setEntries(entries => {
369
- var list = [...entries];
370
- switch (intent.payload.operation) {
371
- case 'append':
372
- case 'prepend':
373
- case 'insert':
374
- case 'replace':
375
- return updateList(list, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
376
- defaultValue: [
377
- // Generate a random key to avoid conflicts
378
- getUniqueKey(), intent.payload.defaultValue]
379
- }));
380
- default:
381
- return updateList(list, intent.payload);
382
- }
383
- });
384
- setError(error => {
385
- var errorList = [];
386
- for (var [key, messages] of Object.entries(error)) {
387
- if (typeof key === 'number') {
388
- errorList[key] = messages;
389
- }
390
- }
391
- switch (intent.payload.operation) {
392
- case 'append':
393
- case 'prepend':
394
- case 'insert':
395
- case 'replace':
396
- errorList = updateList(errorList, _objectSpread2(_objectSpread2({}, intent.payload), {}, {
397
- defaultValue: undefined
398
- }));
399
- break;
400
- default:
401
- errorList = updateList(errorList, intent.payload);
402
- break;
403
- }
404
- return Object.assign({}, errorList);
94
+ function useFieldset(options) {
95
+ var subjectRef = useSubjectRef();
96
+ var form = useFormStore(options.formId, options.context);
97
+ var context = useFormContext(form, subjectRef);
98
+ return new Proxy({}, {
99
+ get(target, prop, receiver) {
100
+ var getMetadata = key => getFieldMetadata(options.formId, context, {
101
+ name: options.name,
102
+ key: key,
103
+ subjectRef
405
104
  });
406
- };
407
- var resetHandler = event => {
408
- var _configRef$current$de;
409
- var form = getFormElement(ref.current);
410
- if (!form || event.target !== form) {
411
- return;
412
- }
413
- setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : []));
414
- };
415
-
416
- // @ts-expect-error Custom event: conform
417
- document.addEventListener('conform', conformHandler, true);
418
- document.addEventListener('reset', resetHandler);
419
- return () => {
420
- // @ts-expect-error Custom event: conform
421
- document.removeEventListener('conform', conformHandler, true);
422
- document.removeEventListener('reset', resetHandler);
423
- };
424
- }, [ref, configRef, setError]);
425
- return entries.map((_ref3, index) => {
426
- var _config$initialError;
427
- var [key, defaultValue] = _ref3;
428
- var errors = error[index];
429
- var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref4) => {
430
- var [name, message] = _ref4;
431
- var [field, ...paths] = getPaths(name);
432
- if (field === index) {
433
- result[getName(paths)] = message;
434
- }
435
- return result;
436
- }, {});
437
- var fieldConfig = {
438
- name: "".concat(config.name, "[").concat(index, "]"),
439
- defaultValue,
440
- initialError,
441
- error: errors === null || errors === void 0 ? void 0 : errors[0],
442
- errors
443
- };
444
- if (config.form) {
445
- fieldConfig.form = config.form;
446
- fieldConfig.id = "".concat(config.form, "-").concat(config.name, "-").concat(index);
447
- fieldConfig.errorId = "".concat(fieldConfig.id, "-error");
448
- fieldConfig.descriptionId = "".concat(fieldConfig.id, "-description");
449
- }
450
- return _objectSpread2({
451
- key
452
- }, fieldConfig);
453
- });
454
- }
455
-
456
- /**
457
- * useLayoutEffect is client-only.
458
- * This basically makes it a no-op on server
459
- */
460
- var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
461
- /**
462
- * Returns a ref object and a set of helpers that dispatch corresponding dom event.
463
- *
464
- * @see https://conform.guide/api/react#useinputevent
465
- */
466
- function useInputEvent(options) {
467
- var optionsRef = useConfigRef(options);
468
- var eventDispatched = useRef({
469
- onInput: false,
470
- onFocus: false,
471
- onBlur: false
472
- });
473
- useSafeLayoutEffect(() => {
474
- var createEventListener = listener => {
475
- return event => {
476
- var _optionsRef$current, _optionsRef$current2, _optionsRef$current3;
477
- var element = typeof ((_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 ? void 0 : _optionsRef$current.ref) === 'function' ? (_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : _optionsRef$current2.ref() : (_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : _optionsRef$current3.ref.current;
478
- if (isFieldElement(element) && (listener === 'onReset' ? event.target === element.form : event.target === element)) {
479
- var _optionsRef$current4, _optionsRef$current4$;
480
- if (listener !== 'onReset') {
481
- eventDispatched.current[listener] = true;
482
- }
483
- (_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 || (_optionsRef$current4$ = _optionsRef$current4[listener]) === null || _optionsRef$current4$ === void 0 ? void 0 : _optionsRef$current4$.call(_optionsRef$current4, event);
484
- }
485
- };
486
- };
487
- var inputHandler = createEventListener('onInput');
488
- var focusHandler = createEventListener('onFocus');
489
- var blurHandler = createEventListener('onBlur');
490
- var resetHandler = createEventListener('onReset');
491
-
492
- // focus/blur event does not bubble
493
- document.addEventListener('input', inputHandler, true);
494
- document.addEventListener('focus', focusHandler, true);
495
- document.addEventListener('blur', blurHandler, true);
496
- document.addEventListener('reset', resetHandler);
497
- return () => {
498
- document.removeEventListener('input', inputHandler, true);
499
- document.removeEventListener('focus', focusHandler, true);
500
- document.removeEventListener('blur', blurHandler, true);
501
- document.removeEventListener('reset', resetHandler);
502
- };
503
- }, []);
504
- var control = useMemo(() => {
505
- var dispatch = (listener, fn) => {
506
- if (!eventDispatched.current[listener]) {
507
- var _optionsRef$current5, _optionsRef$current6, _optionsRef$current7;
508
- var _element2 = typeof ((_optionsRef$current5 = optionsRef.current) === null || _optionsRef$current5 === void 0 ? void 0 : _optionsRef$current5.ref) === 'function' ? (_optionsRef$current6 = optionsRef.current) === null || _optionsRef$current6 === void 0 ? void 0 : _optionsRef$current6.ref() : (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.ref.current;
509
- if (!isFieldElement(_element2)) {
510
- // eslint-disable-next-line no-console
511
- console.warn('Failed to dispatch event; is the input mounted?');
512
- return;
513
- }
514
-
515
- // To avoid recursion
516
- eventDispatched.current[listener] = true;
517
- fn(_element2);
518
- }
519
- eventDispatched.current[listener] = false;
520
- };
521
- return {
522
- change(eventOrValue) {
523
- dispatch('onInput', element => {
524
- if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
525
- if (typeof eventOrValue !== 'boolean') {
526
- throw new Error('You should pass a boolean when changing a checkbox or radio input');
527
- }
528
- element.checked = eventOrValue;
529
- } else {
530
- if (typeof eventOrValue === 'boolean') {
531
- throw new Error('You can pass a boolean only when changing a checkbox or radio input');
532
- }
533
- var _value = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
534
-
535
- // No change event will triggered on React if `element.value` is updated
536
- // before dispatching the event
537
- if (element.value !== _value) {
538
- /**
539
- * Triggering react custom change event
540
- * Solution based on dom-testing-library
541
- * @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
542
- * @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
543
- */
544
- var {
545
- set: valueSetter
546
- } = Object.getOwnPropertyDescriptor(element, 'value') || {};
547
- var prototype = Object.getPrototypeOf(element);
548
- var {
549
- set: prototypeValueSetter
550
- } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
551
- if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
552
- prototypeValueSetter.call(element, _value);
553
- } else {
554
- if (valueSetter) {
555
- valueSetter.call(element, _value);
556
- } else {
557
- throw new Error('The given element does not have a value setter');
558
- }
559
- }
560
- }
561
- }
562
105
 
563
- // Dispatch input event with the updated input value
564
- element.dispatchEvent(new InputEvent('input', {
565
- bubbles: true
566
- }));
567
- // Dispatch change event (necessary for select to update the selected option)
568
- element.dispatchEvent(new Event('change', {
569
- bubbles: true
570
- }));
106
+ // To support array destructuring
107
+ if (prop === Symbol.iterator) {
108
+ var _index = 0;
109
+ return () => ({
110
+ next: () => ({
111
+ value: getMetadata(_index++),
112
+ done: false
113
+ })
571
114
  });
572
- },
573
- focus() {
574
- dispatch('onFocus', element => {
575
- element.dispatchEvent(new FocusEvent('focusin', {
576
- bubbles: true
577
- }));
578
- element.dispatchEvent(new FocusEvent('focus'));
579
- });
580
- },
581
- blur() {
582
- dispatch('onBlur', element => {
583
- element.dispatchEvent(new FocusEvent('focusout', {
584
- bubbles: true
585
- }));
586
- element.dispatchEvent(new FocusEvent('blur'));
587
- });
588
- }
589
- };
590
- }, [optionsRef]);
591
- return control;
592
- }
593
- var FORM_ERROR_ELEMENT_NAME = '__form__';
594
-
595
- /**
596
- * Validate the form with the Constraint Validation API
597
- * @see https://conform.guide/api/react#validateconstraint
598
- */
599
- function validateConstraint(options) {
600
- var _options$formData, _options$formatMessag;
601
- var formData = (_options$formData = options === null || options === void 0 ? void 0 : options.formData) !== null && _options$formData !== void 0 ? _options$formData : new FormData(options.form);
602
- var getDefaultErrors = (validity, result) => {
603
- var errors = [];
604
- if (validity.valueMissing) errors.push('required');
605
- if (validity.typeMismatch || validity.badInput) errors.push('type');
606
- if (validity.tooShort) errors.push('minLength');
607
- if (validity.rangeUnderflow) errors.push('min');
608
- if (validity.stepMismatch) errors.push('step');
609
- if (validity.tooLong) errors.push('maxLength');
610
- if (validity.rangeOverflow) errors.push('max');
611
- if (validity.patternMismatch) errors.push('pattern');
612
- for (var [constraintName, valid] of Object.entries(result)) {
613
- if (!valid) {
614
- errors.push(constraintName);
615
115
  }
616
- }
617
- return errors;
618
- };
619
- var formatMessages = (_options$formatMessag = options === null || options === void 0 ? void 0 : options.formatMessages) !== null && _options$formatMessag !== void 0 ? _options$formatMessag : _ref5 => {
620
- var {
621
- defaultErrors
622
- } = _ref5;
623
- return defaultErrors;
624
- };
625
- return parse(formData, {
626
- resolve() {
627
- var error = {};
628
- var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
629
- var _loop = function _loop(_element3) {
630
- if (isFieldElement(_element3)) {
631
- var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : '';
632
- var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => {
633
- var [name, attributeValue = ''] = _ref6;
634
- if (constraintPattern.test(name)) {
635
- var _options$constraint;
636
- var constraintName = name.slice(10).toLowerCase();
637
- var _validate = (_options$constraint = options.constraint) === null || _options$constraint === void 0 ? void 0 : _options$constraint[constraintName];
638
- if (typeof _validate === 'function') {
639
- result[constraintName] = _validate(_element3.value, {
640
- formData,
641
- attributeValue
642
- });
643
- } else {
644
- // eslint-disable-next-line no-console
645
- console.warn("Found an \"".concat(constraintName, "\" constraint with undefined definition; Please specify it on the validateConstraint API."));
646
- }
647
- }
648
- return result;
649
- }, {});
650
- var errors = formatMessages({
651
- name,
652
- validity: _element3.validity,
653
- constraint,
654
- defaultErrors: getDefaultErrors(_element3.validity, constraint)
655
- });
656
- if (errors.length > 0) {
657
- error[name] = errors;
658
- }
659
- }
660
- };
661
- for (var _element3 of options.form.elements) {
662
- _loop(_element3);
116
+ var index = Number(prop);
117
+ if (typeof prop === 'string') {
118
+ return getMetadata(Number.isNaN(index) ? prop : index);
663
119
  }
664
- return {
665
- error
666
- };
120
+ return Reflect.get(target, prop, receiver);
667
121
  }
668
122
  });
669
123
  }
670
- function getUniqueKey() {
671
- var [value] = crypto.getRandomValues(new Uint32Array(1));
672
- if (!value) {
673
- throw new Error('Fail to generate an unique key');
674
- }
675
- return value.toString(36);
676
- }
677
- function reportSubmission(form, submission) {
678
- for (var [name, message] of Object.entries(submission.error)) {
679
- // There is no need to create a placeholder button if all we want is to reset the error
680
- if (message.length === 0) {
681
- continue;
682
- }
683
-
684
- // We can't use empty string as button name
685
- // As `form.element.namedItem('')` will always returns null
686
- var elementName = name ? name : FORM_ERROR_ELEMENT_NAME;
687
- var item = form.elements.namedItem(elementName);
688
- if (item === null) {
689
- // Create placeholder button to keep the error without contributing to the form data
690
- var button = document.createElement('button');
691
- button.name = elementName;
692
- button.hidden = true;
693
- button.dataset.conformTouched = 'true';
694
- form.appendChild(button);
695
- }
696
- }
697
- var intent = parseIntent(submission.intent);
698
- var scope = getScope(intent);
699
- for (var _element4 of getFormControls(form)) {
700
- var _submission$error$_el;
701
- var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : '';
702
- var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : [];
703
- if (scope === null || scope === _elementName) {
704
- _element4.dataset.conformTouched = 'true';
705
- }
706
- if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
707
- var invalidEvent = new Event('invalid', {
708
- cancelable: true
709
- });
710
- _element4.setCustomValidity(getValidationMessage(messages));
711
- _element4.dispatchEvent(invalidEvent);
124
+ function useFieldList(options) {
125
+ var _context$initialValue;
126
+ var subjectRef = useSubjectRef({
127
+ initialValue: {
128
+ name: [options.name]
712
129
  }
130
+ });
131
+ var form = useFormStore(options.formId, options.context);
132
+ var context = useFormContext(form, subjectRef);
133
+ var initialValue = (_context$initialValue = context.initialValue[options.name]) !== null && _context$initialValue !== void 0 ? _context$initialValue : [];
134
+ if (!Array.isArray(initialValue)) {
135
+ throw new Error('The initial value at the given name is not a list');
713
136
  }
714
- if (!intent) {
715
- focusFirstInvalidControl(form);
716
- }
137
+ return Array(initialValue.length).fill(0).map((_, index) => getFieldMetadata(options.formId, context, {
138
+ name: options.name,
139
+ key: index,
140
+ subjectRef
141
+ }));
717
142
  }
718
- function getScope(intent) {
719
- switch (intent === null || intent === void 0 ? void 0 : intent.type) {
720
- case 'validate':
721
- return intent.payload;
722
- case 'list':
723
- return intent.payload.name;
724
- }
725
- return null;
143
+ function useField(options) {
144
+ var subjectRef = useSubjectRef();
145
+ var form = useFormStore(options.formId, options.context);
146
+ var context = useFormContext(form, subjectRef);
147
+ var metadata = getFieldMetadata(options.formId, context, {
148
+ name: options.name,
149
+ subjectRef
150
+ });
151
+ return metadata;
726
152
  }
727
153
 
728
- export { FORM_ERROR_ELEMENT_NAME, getScope, getUniqueKey, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };
154
+ export { useField, useFieldList, useFieldset, useForm, useFormId, useFormMetadata, useSafeLayoutEffect };