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