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