@conform-to/react 1.8.1 → 1.9.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.
@@ -1,7 +1,506 @@
1
- import { unstable_deepEqual, isFieldElement, unstable_change, unstable_focus, unstable_blur, getFormData } from '@conform-to/dom';
2
- import { useContext, useRef, useEffect, useSyncExternalStore, useCallback } from 'react';
3
- import { getDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, focusable, initializeField, getFormElement } from './util.mjs';
4
- import { FormContext } from './context.mjs';
1
+ 'use client';
2
+ import { objectSpread2 as _objectSpread2 } from '../_virtual/_rollupPluginBabelHelpers.mjs';
3
+ import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, deepEqual, change, focus, blur, getFormData, parseSubmission, report, createSubmitEvent } from '@conform-to/dom/future';
4
+ import { createContext, useContext, useMemo, useId, useRef, useEffect, useSyncExternalStore, useCallback, useState, useLayoutEffect } from 'react';
5
+ import { resolveStandardSchemaResult, resolveValidateResult, appendUniqueItem } from './util.mjs';
6
+ import { isTouched, getFormMetadata, getFieldset, getField, initializeState, updateState } from './state.mjs';
7
+ import { deserializeIntent, actionHandlers, applyIntent } from './intent.mjs';
8
+ import { focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, updateFormValue, getSubmitEvent } from './dom.mjs';
9
+ import { jsx } from 'react/jsx-runtime';
10
+
11
+ var FormConfig = /*#__PURE__*/createContext({
12
+ intentName: DEFAULT_INTENT_NAME,
13
+ observer: createGlobalFormsObserver(),
14
+ serialize
15
+ });
16
+ var Form = /*#__PURE__*/createContext([]);
17
+
18
+ /**
19
+ * Provides form context to child components.
20
+ * Stacks contexts to support nested forms, with latest context taking priority.
21
+ */
22
+ function FormProvider(props) {
23
+ var stack = useContext(Form);
24
+ var value = useMemo(
25
+ // Put the latest form context first to ensure that to be the first one found
26
+ () => [props.context].concat(stack), [stack, props.context]);
27
+ return /*#__PURE__*/jsx(Form.Provider, {
28
+ value: value,
29
+ children: props.children
30
+ });
31
+ }
32
+ function useFormContext(formId) {
33
+ var contexts = useContext(Form);
34
+ var context = formId ? contexts.find(context => formId === context.formId) : contexts[0];
35
+ if (!context) {
36
+ throw new Error('No form context found; Have you render a <FormProvider /> with the corresponding form context?');
37
+ }
38
+ return context;
39
+ }
40
+
41
+ /**
42
+ * Core form hook that manages form state, validation, and submission.
43
+ * Handles both sync and async validation, intent dispatching, and DOM updates.
44
+ */
45
+ function useConform(formRef, options) {
46
+ var {
47
+ lastResult
48
+ } = options;
49
+ var [state, setState] = useState(() => {
50
+ var state = initializeState();
51
+ if (lastResult) {
52
+ state = updateState(state, _objectSpread2(_objectSpread2({}, lastResult), {}, {
53
+ type: 'initialize',
54
+ intent: lastResult.submission.intent ? deserializeIntent(lastResult.submission.intent) : null,
55
+ ctx: {
56
+ handlers: actionHandlers,
57
+ reset: () => state
58
+ }
59
+ }));
60
+ }
61
+ return state;
62
+ });
63
+ var keyRef = useRef(options.key);
64
+ var resetKeyRef = useRef(state.resetKey);
65
+ var optionsRef = useLatest(options);
66
+ var lastResultRef = useRef(lastResult);
67
+ var lastIntentedValueRef = useRef();
68
+ var lastAsyncResultRef = useRef(null);
69
+ var abortControllerRef = useRef(null);
70
+ var handleSubmission = useCallback((result, options) => {
71
+ var _optionsRef$current$o, _optionsRef$current;
72
+ var intent = result.submission.intent ? deserializeIntent(result.submission.intent) : null;
73
+ setState(state => updateState(state, _objectSpread2(_objectSpread2({}, result), {}, {
74
+ type: options.type,
75
+ intent,
76
+ ctx: {
77
+ handlers: actionHandlers,
78
+ reset() {
79
+ return initializeState();
80
+ }
81
+ }
82
+ })));
83
+ var formElement = getFormElement(formRef);
84
+ if (!formElement || !result.error) {
85
+ return;
86
+ }
87
+ (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
88
+ formElement,
89
+ error: result.error,
90
+ intent
91
+ });
92
+ }, [formRef, optionsRef]);
93
+ useEffect(() => {
94
+ return () => {
95
+ var _abortControllerRef$c;
96
+ // Cancel pending validation request
97
+ (_abortControllerRef$c = abortControllerRef.current) === null || _abortControllerRef$c === void 0 || _abortControllerRef$c.abort('The component is unmounted');
98
+ };
99
+ }, []);
100
+ useEffect(() => {
101
+ // To avoid re-applying the same result twice
102
+ if (lastResult && lastResult !== lastResultRef.current) {
103
+ handleSubmission(lastResult, {
104
+ type: 'server'
105
+ });
106
+ lastResultRef.current = lastResult;
107
+ }
108
+ }, [lastResult, handleSubmission]);
109
+ useEffect(() => {
110
+ // Reset the form state if the form key changes
111
+ if (options.key !== keyRef.current) {
112
+ keyRef.current = options.key;
113
+ setState(initializeState());
114
+ }
115
+ }, [options.key]);
116
+ useEffect(() => {
117
+ var formElement = getFormElement(formRef);
118
+
119
+ // Reset the form values if the reset key changes
120
+ if (formElement && state.resetKey !== resetKeyRef.current) {
121
+ resetKeyRef.current = state.resetKey;
122
+ formElement.reset();
123
+ }
124
+ }, [formRef, state.resetKey]);
125
+ useEffect(() => {
126
+ if (!state.intendedValue) {
127
+ return;
128
+ }
129
+ var formElement = getFormElement(formRef);
130
+ if (!formElement) {
131
+ // eslint-disable-next-line no-console
132
+ console.error('Failed to update form value; No form element found');
133
+ return;
134
+ }
135
+ updateFormValue(formElement, state.intendedValue, optionsRef.current.serialize);
136
+ lastIntentedValueRef.current = undefined;
137
+ }, [formRef, state.intendedValue, optionsRef]);
138
+ var handleSubmit = useCallback(event => {
139
+ var _abortControllerRef$c2, _lastAsyncResultRef$c;
140
+ var abortController = new AbortController();
141
+
142
+ // Keep track of the abort controller so we can cancel the previous request if a new one is made
143
+ (_abortControllerRef$c2 = abortControllerRef.current) === null || _abortControllerRef$c2 === void 0 || _abortControllerRef$c2.abort('A new submission is made');
144
+ abortControllerRef.current = abortController;
145
+ var formData;
146
+ var result;
147
+ var resolvedValue;
148
+
149
+ // The form might be re-submitted manually if there was an async validation
150
+ if (event.nativeEvent === ((_lastAsyncResultRef$c = lastAsyncResultRef.current) === null || _lastAsyncResultRef$c === void 0 ? void 0 : _lastAsyncResultRef$c.event)) {
151
+ formData = lastAsyncResultRef.current.formData;
152
+ result = lastAsyncResultRef.current.result;
153
+ resolvedValue = lastAsyncResultRef.current.resolvedValue;
154
+ } else {
155
+ var _optionsRef$current$o2, _optionsRef$current2;
156
+ var formElement = event.currentTarget;
157
+ var submitEvent = getSubmitEvent(event);
158
+ formData = getFormData(formElement, submitEvent.submitter);
159
+ var submission = parseSubmission(formData, {
160
+ intentName: optionsRef.current.intentName
161
+ });
162
+
163
+ // Patch missing fields in the submission object
164
+ for (var element of formElement.elements) {
165
+ if (isFieldElement(element) && element.name) {
166
+ appendUniqueItem(submission.fields, element.name);
167
+ }
168
+ }
169
+
170
+ // Override submission value if the last intended value is not applied yet (i.e. batch updates)
171
+ if (typeof lastIntentedValueRef.current !== 'undefined') {
172
+ var _lastIntentedValueRef;
173
+ submission.payload = (_lastIntentedValueRef = lastIntentedValueRef.current) !== null && _lastIntentedValueRef !== void 0 ? _lastIntentedValueRef : {};
174
+ }
175
+ var intendedValue = applyIntent(submission);
176
+
177
+ // Update the last intended value in case there will be another intent dispatched
178
+ lastIntentedValueRef.current = intendedValue === submission.payload ? undefined : intendedValue;
179
+ var submissionResult = report(submission, {
180
+ keepFiles: true,
181
+ intendedValue
182
+ });
183
+ var validateResult =
184
+ // Skip validation on form reset
185
+ intendedValue !== null ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, {
186
+ payload: intendedValue,
187
+ error: {
188
+ formErrors: [],
189
+ fieldErrors: {}
190
+ },
191
+ intent: submission.intent ? deserializeIntent(submission.intent) : null,
192
+ formElement,
193
+ submitter: submitEvent.submitter,
194
+ formData
195
+ }) : {
196
+ error: null
197
+ };
198
+ var {
199
+ syncResult,
200
+ asyncResult
201
+ } = resolveValidateResult(validateResult);
202
+ if (typeof syncResult !== 'undefined') {
203
+ submissionResult.error = syncResult.error;
204
+ resolvedValue = syncResult.value;
205
+ }
206
+ if (typeof asyncResult !== 'undefined') {
207
+ // Update the form when the validation result is resolved
208
+ asyncResult.then(_ref => {
209
+ var {
210
+ error,
211
+ value
212
+ } = _ref;
213
+ // Update the form with the validation result
214
+ // There is no need to flush the update in this case
215
+ if (!abortController.signal.aborted) {
216
+ submissionResult.error = error;
217
+ handleSubmission(submissionResult, {
218
+ type: 'server'
219
+ });
220
+
221
+ // If the form is meant to be submitted and there is no error
222
+ if (error === null && !submission.intent) {
223
+ var _event = createSubmitEvent(submitEvent.submitter);
224
+
225
+ // Keep track of the submit event so we can skip validation on the next submit
226
+ lastAsyncResultRef.current = {
227
+ event: _event,
228
+ formData,
229
+ resolvedValue: value,
230
+ result: submissionResult
231
+ };
232
+ formElement.dispatchEvent(_event);
233
+ }
234
+ }
235
+ });
236
+ }
237
+ handleSubmission(submissionResult, {
238
+ type: 'client'
239
+ });
240
+ if (
241
+ // If client validation happens
242
+ (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && (
243
+ // Either the form is not meant to be submitted (i.e. intent is present) or there is an error / pending validation
244
+ submissionResult.submission.intent || submissionResult.error !== null)) {
245
+ event.preventDefault();
246
+ }
247
+ result = submissionResult;
248
+ }
249
+
250
+ // We might not prevent form submission if server validation is required
251
+ // But the `onSubmit` handler should be triggered only if there is no intent
252
+ if (!event.isDefaultPrevented() && result.submission.intent === null) {
253
+ var _optionsRef$current$o3, _optionsRef$current3;
254
+ (_optionsRef$current$o3 = (_optionsRef$current3 = optionsRef.current).onSubmit) === null || _optionsRef$current$o3 === void 0 || _optionsRef$current$o3.call(_optionsRef$current3, event, {
255
+ formData,
256
+ get value() {
257
+ if (typeof resolvedValue === 'undefined') {
258
+ throw new Error('`value` is not available; Please make sure you have included the value in the `onValidate` result.');
259
+ }
260
+ return resolvedValue;
261
+ },
262
+ update(options) {
263
+ if (!abortController.signal.aborted) {
264
+ var _submissionResult = report(result.submission, _objectSpread2(_objectSpread2({}, options), {}, {
265
+ keepFiles: true
266
+ }));
267
+ handleSubmission(_submissionResult, {
268
+ type: 'server'
269
+ });
270
+ }
271
+ }
272
+ });
273
+ }
274
+ }, [handleSubmission, optionsRef]);
275
+ return [state, handleSubmit];
276
+ }
277
+
278
+ /**
279
+ * The main React hook for form management. Handles form state, validation, and submission
280
+ * while providing access to form metadata, field objects, and form actions.
281
+ *
282
+ * @see https://conform.guide/api/react/future/useForm
283
+ * @example
284
+ * ```tsx
285
+ * const { form, fields } = useForm({
286
+ * onValidate({ payload, error }) {
287
+ * if (!payload.email) {
288
+ * error.fieldErrors.email = ['Required'];
289
+ * }
290
+ * return error;
291
+ * }
292
+ * });
293
+ *
294
+ * return (
295
+ * <form {...form.props}>
296
+ * <input name={fields.email.name} defaultValue={fields.email.defaultValue} />
297
+ * <div>{fields.email.errors}</div>
298
+ * </form>
299
+ * );
300
+ * ```
301
+ */
302
+ function useForm(options) {
303
+ var _optionsRef$current$o4;
304
+ var {
305
+ id,
306
+ defaultValue,
307
+ constraint
308
+ } = options;
309
+ var config = useContext(FormConfig);
310
+ var optionsRef = useLatest(options);
311
+ var fallbackId = useId();
312
+ var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId);
313
+ var [state, handleSubmit] = useConform(formId, _objectSpread2(_objectSpread2({}, options), {}, {
314
+ serialize: config.serialize,
315
+ intentName: config.intentName,
316
+ onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : focusFirstInvalidField,
317
+ onValidate(ctx) {
318
+ var _options$onValidate, _options$onValidate2;
319
+ if (options.schema) {
320
+ var standardResult = options.schema['~standard'].validate(ctx.payload);
321
+ if (standardResult instanceof Promise) {
322
+ return standardResult.then(actualStandardResult => {
323
+ if (typeof options.onValidate === 'function') {
324
+ throw new Error('The "onValidate" handler is not supported when used with asynchronous schema validation.');
325
+ }
326
+ return resolveStandardSchemaResult(actualStandardResult);
327
+ });
328
+ }
329
+ var resolvedResult = resolveStandardSchemaResult(standardResult);
330
+ if (!options.onValidate) {
331
+ return resolvedResult;
332
+ }
333
+
334
+ // Update the schema error in the context
335
+ if (resolvedResult.error) {
336
+ ctx.error = resolvedResult.error;
337
+ }
338
+ var validateResult = resolveValidateResult(options.onValidate(ctx));
339
+ if (validateResult.syncResult) {
340
+ var _validateResult$syncR, _validateResult$syncR2;
341
+ (_validateResult$syncR2 = (_validateResult$syncR = validateResult.syncResult).value) !== null && _validateResult$syncR2 !== void 0 ? _validateResult$syncR2 : _validateResult$syncR.value = resolvedResult.value;
342
+ }
343
+ if (validateResult.asyncResult) {
344
+ validateResult.asyncResult = validateResult.asyncResult.then(result => {
345
+ var _result$value;
346
+ (_result$value = result.value) !== null && _result$value !== void 0 ? _result$value : result.value = resolvedResult.value;
347
+ return result;
348
+ });
349
+ }
350
+ return [validateResult.syncResult, validateResult.asyncResult];
351
+ }
352
+ return (_options$onValidate = (_options$onValidate2 = options.onValidate) === null || _options$onValidate2 === void 0 ? void 0 : _options$onValidate2.call(options, ctx)) !== null && _options$onValidate !== void 0 ? _options$onValidate : {
353
+ // To avoid conform falling back to server validation,
354
+ // if neither schema nor validation handler is provided,
355
+ // we just treat it as a valid client submission
356
+ error: null
357
+ };
358
+ }
359
+ }));
360
+ var intent = useIntent(formId);
361
+ var context = useMemo(() => ({
362
+ formId,
363
+ state,
364
+ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : null,
365
+ constraint: constraint !== null && constraint !== void 0 ? constraint : null,
366
+ handleSubmit: handleSubmit,
367
+ handleInput(event) {
368
+ var _optionsRef$current$o5, _optionsRef$current4;
369
+ if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
370
+ return;
371
+ }
372
+ (_optionsRef$current$o5 = (_optionsRef$current4 = optionsRef.current).onInput) === null || _optionsRef$current$o5 === void 0 || _optionsRef$current$o5.call(_optionsRef$current4, _objectSpread2(_objectSpread2({}, event), {}, {
373
+ target: event.target,
374
+ currentTarget: event.target.form
375
+ }));
376
+ if (event.defaultPrevented) {
377
+ return;
378
+ }
379
+ var {
380
+ shouldValidate = 'onSubmit',
381
+ shouldRevalidate = shouldValidate
382
+ } = optionsRef.current;
383
+ if (isTouched(state, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
384
+ intent.validate(event.target.name);
385
+ }
386
+ },
387
+ handleBlur(event) {
388
+ var _optionsRef$current$o6, _optionsRef$current5;
389
+ if (!isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== getFormElement(formId)) {
390
+ return;
391
+ }
392
+ (_optionsRef$current$o6 = (_optionsRef$current5 = optionsRef.current).onBlur) === null || _optionsRef$current$o6 === void 0 || _optionsRef$current$o6.call(_optionsRef$current5, _objectSpread2(_objectSpread2({}, event), {}, {
393
+ target: event.target,
394
+ currentTarget: event.target.form
395
+ }));
396
+ if (event.defaultPrevented) {
397
+ return;
398
+ }
399
+ var {
400
+ shouldValidate = 'onSubmit',
401
+ shouldRevalidate = shouldValidate
402
+ } = optionsRef.current;
403
+ if (isTouched(state, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
404
+ intent.validate(event.target.name);
405
+ }
406
+ }
407
+ }), [formId, state, defaultValue, constraint, handleSubmit, intent, optionsRef]);
408
+ var form = useMemo(() => getFormMetadata(context, {
409
+ serialize: config.serialize
410
+ }), [context, config.serialize]);
411
+ var fields = useMemo(() => getFieldset(context, {
412
+ serialize: config.serialize
413
+ }), [context, config.serialize]);
414
+ return {
415
+ form,
416
+ fields,
417
+ intent
418
+ };
419
+ }
420
+
421
+ /**
422
+ * A React hook that provides access to form-level metadata and state.
423
+ * Requires `FormProvider` context when used in child components.
424
+ *
425
+ * @see https://conform.guide/api/react/future/useFormMetadata
426
+ * @example
427
+ * ```tsx
428
+ * function ErrorSummary() {
429
+ * const form = useFormMetadata();
430
+ *
431
+ * if (!form.invalid) return null;
432
+ *
433
+ * return (
434
+ * <div>Please fix {Object.keys(form.fieldErrors).length} errors</div>
435
+ * );
436
+ * }
437
+ * ```
438
+ */
439
+ function useFormMetadata() {
440
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
441
+ var config = useContext(FormConfig);
442
+ var context = useFormContext(options.formId);
443
+ var formMetadata = useMemo(() => getFormMetadata(context, {
444
+ serialize: config.serialize
445
+ }), [context, config.serialize]);
446
+ return formMetadata;
447
+ }
448
+
449
+ /**
450
+ * A React hook that provides access to a specific field's metadata and state.
451
+ * Requires `FormProvider` context when used in child components.
452
+ *
453
+ * @see https://conform.guide/api/react/future/useField
454
+ * @example
455
+ * ```tsx
456
+ * function FormField({ name, label }) {
457
+ * const field = useField(name);
458
+ *
459
+ * return (
460
+ * <div>
461
+ * <label htmlFor={field.id}>{label}</label>
462
+ * <input id={field.id} name={field.name} defaultValue={field.defaultValue} />
463
+ * {field.errors && <div>{field.errors.join(', ')}</div>}
464
+ * </div>
465
+ * );
466
+ * }
467
+ * ```
468
+ */
469
+ function useField(name) {
470
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
471
+ var config = useContext(FormConfig);
472
+ var context = useFormContext(options.formId);
473
+ var field = useMemo(() => getField(context, {
474
+ name,
475
+ serialize: config.serialize
476
+ }), [context, name, config.serialize]);
477
+ return field;
478
+ }
479
+
480
+ /**
481
+ * A React hook that provides an intent dispatcher for programmatic form actions.
482
+ * Intent dispatchers allow you to trigger form operations like validation, field updates,
483
+ * and array manipulations without manual form submission.
484
+ *
485
+ * @see https://conform.guide/api/react/future/useIntent
486
+ * @example
487
+ * ```tsx
488
+ * function ResetButton() {
489
+ * const buttonRef = useRef<HTMLButtonElement>(null);
490
+ * const intent = useIntent(buttonRef);
491
+ *
492
+ * return (
493
+ * <button type="button" ref={buttonRef} onClick={() => intent.reset()}>
494
+ * Reset Form
495
+ * </button>
496
+ * );
497
+ * }
498
+ * ```
499
+ */
500
+ function useIntent(formRef) {
501
+ var config = useContext(FormConfig);
502
+ return useMemo(() => createIntentDispatcher(() => getFormElement(formRef), config.intentName), [formRef, config.intentName]);
503
+ }
5
504
 
6
505
  /**
7
506
  * A React hook that lets you sync the state of an input and dispatch native form events from it.
@@ -16,10 +515,10 @@ import { FormContext } from './context.mjs';
16
515
  function useControl(options) {
17
516
  var {
18
517
  observer
19
- } = useContext(FormContext);
518
+ } = useContext(FormConfig);
20
519
  var inputRef = useRef(null);
21
520
  var eventDispatched = useRef({});
22
- var defaultSnapshot = getDefaultSnapshot(options === null || options === void 0 ? void 0 : options.defaultValue, options === null || options === void 0 ? void 0 : options.defaultChecked, options === null || options === void 0 ? void 0 : options.value);
521
+ var defaultSnapshot = createDefaultSnapshot(options === null || options === void 0 ? void 0 : options.defaultValue, options === null || options === void 0 ? void 0 : options.defaultChecked, options === null || options === void 0 ? void 0 : options.value);
23
522
  var snapshotRef = useRef(defaultSnapshot);
24
523
  var optionsRef = useRef(options);
25
524
  useEffect(() => {
@@ -41,7 +540,7 @@ function useControl(options) {
41
540
  value: getRadioGroupValue(input),
42
541
  options: getCheckboxGroupValue(input)
43
542
  } : getInputSnapshot(input);
44
- if (unstable_deepEqual(prev, next)) {
543
+ if (deepEqual(prev, next)) {
45
544
  return prev;
46
545
  }
47
546
  snapshotRef.current = next;
@@ -59,8 +558,8 @@ function useControl(options) {
59
558
  eventDispatched.current[listener] = undefined;
60
559
  });
61
560
  if (listener === 'focus') {
62
- var _optionsRef$current, _optionsRef$current$o;
63
- (_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 || (_optionsRef$current$o = _optionsRef$current.onFocus) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current);
561
+ var _optionsRef$current6, _optionsRef$current6$;
562
+ (_optionsRef$current6 = optionsRef.current) === null || _optionsRef$current6 === void 0 || (_optionsRef$current6$ = _optionsRef$current6.onFocus) === null || _optionsRef$current6$ === void 0 || _optionsRef$current6$.call(_optionsRef$current6);
64
563
  }
65
564
  }
66
565
  };
@@ -88,14 +587,14 @@ function useControl(options) {
88
587
  } else if (isFieldElement(element)) {
89
588
  inputRef.current = element;
90
589
  if (shouldHandleFocus) {
91
- focusable(element);
590
+ makeInputFocusable(element);
92
591
  }
93
592
  if (element.type === 'checkbox' || element.type === 'radio') {
94
- var _optionsRef$current$v, _optionsRef$current2;
593
+ var _optionsRef$current$v, _optionsRef$current7;
95
594
  // React set the value as empty string incorrectly when the value is undefined
96
595
  // This make sure the checkbox value falls back to the default value "on" properly
97
596
  // @see https://github.com/facebook/react/issues/17590
98
- element.value = (_optionsRef$current$v = (_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : _optionsRef$current2.value) !== null && _optionsRef$current$v !== void 0 ? _optionsRef$current$v : 'on';
597
+ element.value = (_optionsRef$current$v = (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.value) !== null && _optionsRef$current$v !== void 0 ? _optionsRef$current$v : 'on';
99
598
  }
100
599
  initializeField(element, optionsRef.current);
101
600
  } else {
@@ -108,13 +607,13 @@ function useControl(options) {
108
607
  }
109
608
  inputRef.current = inputs;
110
609
  for (var input of inputs) {
111
- var _optionsRef$current3;
610
+ var _optionsRef$current8;
112
611
  if (shouldHandleFocus) {
113
- focusable(input);
612
+ makeInputFocusable(input);
114
613
  }
115
614
  initializeField(input, {
116
615
  // We will not be uitlizing defaultChecked / value on checkbox / radio group
117
- defaultValue: (_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : _optionsRef$current3.defaultValue
616
+ defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue
118
617
  });
119
618
  }
120
619
  }
@@ -122,7 +621,7 @@ function useControl(options) {
122
621
  change: useCallback(value => {
123
622
  if (!eventDispatched.current.change) {
124
623
  var _inputRef$current;
125
- var _element = Array.isArray(inputRef.current) ? (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.find(input => {
624
+ var element = Array.isArray(inputRef.current) ? (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.find(input => {
126
625
  var wasChecked = input.checked;
127
626
  var isChecked = Array.isArray(value) ? value.some(item => item === input.value) : input.value === value;
128
627
  switch (input.type) {
@@ -138,8 +637,8 @@ function useControl(options) {
138
637
  return false;
139
638
  }
140
639
  }) : inputRef.current;
141
- if (_element) {
142
- unstable_change(_element, typeof value === 'boolean' ? value ? _element.value : null : value);
640
+ if (element) {
641
+ change(element, typeof value === 'boolean' ? value ? element.value : null : value);
143
642
  }
144
643
  }
145
644
  if (eventDispatched.current.change) {
@@ -149,9 +648,9 @@ function useControl(options) {
149
648
  }, []),
150
649
  focus: useCallback(() => {
151
650
  if (!eventDispatched.current.focus) {
152
- var _element2 = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current;
153
- if (_element2) {
154
- unstable_focus(_element2);
651
+ var element = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current;
652
+ if (element) {
653
+ focus(element);
155
654
  }
156
655
  }
157
656
  if (eventDispatched.current.focus) {
@@ -161,9 +660,9 @@ function useControl(options) {
161
660
  }, []),
162
661
  blur: useCallback(() => {
163
662
  if (!eventDispatched.current.blur) {
164
- var _element3 = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current;
165
- if (_element3) {
166
- unstable_blur(_element3);
663
+ var element = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current;
664
+ if (element) {
665
+ blur(element);
167
666
  }
168
667
  }
169
668
  if (eventDispatched.current.blur) {
@@ -188,23 +687,23 @@ function useControl(options) {
188
687
  function useFormData(formRef, select, options) {
189
688
  var {
190
689
  observer
191
- } = useContext(FormContext);
690
+ } = useContext(FormConfig);
192
691
  var valueRef = useRef();
193
692
  var formDataRef = useRef(null);
194
693
  var value = useSyncExternalStore(useCallback(callback => {
195
694
  var formElement = getFormElement(formRef);
196
695
  if (formElement) {
197
- var _formData = getFormData(formElement);
198
- formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? _formData : new URLSearchParams(Array.from(_formData).map(_ref => {
199
- var [key, value] = _ref;
696
+ var formData = getFormData(formElement);
697
+ formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? formData : new URLSearchParams(Array.from(formData).map(_ref2 => {
698
+ var [key, value] = _ref2;
200
699
  return [key, value.toString()];
201
700
  }));
202
701
  }
203
702
  var unsubscribe = observer.onFormUpdate(event => {
204
703
  if (event.target === getFormElement(formRef)) {
205
- var _formData2 = getFormData(event.target, event.submitter);
206
- formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? _formData2 : new URLSearchParams(Array.from(_formData2).map(_ref2 => {
207
- var [key, value] = _ref2;
704
+ var _formData = getFormData(event.target, event.submitter);
705
+ formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? _formData : new URLSearchParams(Array.from(_formData).map(_ref3 => {
706
+ var [key, value] = _ref3;
208
707
  return [key, value.toString()];
209
708
  }));
210
709
  callback();
@@ -214,7 +713,7 @@ function useFormData(formRef, select, options) {
214
713
  }, [observer, formRef, options === null || options === void 0 ? void 0 : options.acceptFiles]), () => {
215
714
  // @ts-expect-error FIXME
216
715
  var result = select(formDataRef.current, valueRef.current);
217
- if (typeof valueRef.current !== 'undefined' && unstable_deepEqual(result, valueRef.current)) {
716
+ if (typeof valueRef.current !== 'undefined' && deepEqual(result, valueRef.current)) {
218
717
  return valueRef.current;
219
718
  }
220
719
  valueRef.current = result;
@@ -223,4 +722,22 @@ function useFormData(formRef, select, options) {
223
722
  return value;
224
723
  }
225
724
 
226
- export { useControl, useFormData };
725
+ /**
726
+ * useLayoutEffect is client-only.
727
+ * This basically makes it a no-op on server
728
+ */
729
+ var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
730
+
731
+ /**
732
+ * Keep a mutable ref in sync with the latest value.
733
+ * Useful to avoid stale closures in event handlers or async callbacks.
734
+ */
735
+ function useLatest(value) {
736
+ var ref = useRef(value);
737
+ useSafeLayoutEffect(() => {
738
+ ref.current = value;
739
+ }, [value]);
740
+ return ref;
741
+ }
742
+
743
+ export { Form, FormConfig, FormProvider, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };