@conform-to/react 1.17.0 → 1.18.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,16 +1,20 @@
1
1
  'use client';
2
2
  import { objectSpread2 as _objectSpread2, objectWithoutProperties as _objectWithoutProperties } 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, useRef, useId, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, useState } from 'react';
3
+ import { DEFAULT_INTENT_NAME, createGlobalFormsObserver, serialize, isFieldElement, isGlobalInstance, deepEqual, change, focus, blur, getFormData, dispatchInternalUpdateEvent, parseSubmission, report, requestSubmit, isPlainObject } from '@conform-to/dom/future';
4
+ import { createContext, useContext, useMemo, useRef, useId, useState, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, forwardRef } from 'react';
5
5
  import { resolveStandardSchemaResult, resolveValidateResult, appendUniqueItem } from './util.mjs';
6
6
  import { isTouched, getFormMetadata, getFieldset, getField, initializeState, updateState } from './state.mjs';
7
7
  import { deserializeIntent, applyIntent, intentHandlers, resolveIntent } from './intent.mjs';
8
- import { cleanupPreservedInputs, preserveInputs, focusFirstInvalidField, getFormElement, createIntentDispatcher, createDefaultSnapshot, getRadioGroupValue, getCheckboxGroupValue, getInputSnapshot, makeInputFocusable, initializeField, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs';
8
+ import { cleanupPreservedInputs, preserveInputs, focusFirstInvalidField, getFormElement, createIntentDispatcher, deriveDefaultPayload, resolveControlPayload, initializeField, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs';
9
+ import { flushSync } from 'react-dom';
9
10
  import { jsx } from 'react/jsx-runtime';
10
11
 
11
- var _excluded = ["children"];
12
- // Static reset key for consistent hydration during Next.js prerendering
13
- // See: https://nextjs.org/docs/messages/next-prerender-current-time-client
12
+ var _excluded = ["children"],
13
+ _excluded2 = ["name", "form", "defaultValue", "hidden"],
14
+ _excluded3 = ["defaultValue", "multiple", "hidden"],
15
+ _excluded4 = ["defaultValue", "hidden"],
16
+ _excluded5 = ["defaultValue", "value", "hidden"],
17
+ _excluded6 = ["defaultValue", "hidden"];
14
18
  var INITIAL_KEY = 'INITIAL_KEY';
15
19
  var GlobalFormOptionsContext = /*#__PURE__*/createContext({
16
20
  intentName: DEFAULT_INTENT_NAME,
@@ -146,6 +150,10 @@ function useConform(formRef, options) {
146
150
  var finalResult = applyIntent(result, intent, {
147
151
  handlers: intentHandlers
148
152
  });
153
+ var formElement = getFormElement(formRef);
154
+ if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) {
155
+ dispatchInternalUpdateEvent(formElement);
156
+ }
149
157
  setState(state => updateState(state, _objectSpread2(_objectSpread2({}, finalResult), {}, {
150
158
  type,
151
159
  intent,
@@ -161,7 +169,6 @@ function useConform(formRef, options) {
161
169
  })));
162
170
 
163
171
  // TODO: move on error handler to a new effect
164
- var formElement = getFormElement(formRef);
165
172
  if (formElement && result.error) {
166
173
  var _optionsRef$current$o, _optionsRef$current;
167
174
  (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, {
@@ -174,6 +181,10 @@ function useConform(formRef, options) {
174
181
  }, [formRef, optionsRef]);
175
182
  if (options.key !== keyRef.current) {
176
183
  keyRef.current = options.key;
184
+ var formElement = getFormElement(formRef);
185
+ if (formElement) {
186
+ dispatchInternalUpdateEvent(formElement);
187
+ }
177
188
  setState(initializeState({
178
189
  defaultValue: options.defaultValue
179
190
  }));
@@ -200,52 +211,56 @@ function useConform(formRef, options) {
200
211
  }, [formRef, state.resetKey, state.defaultValue, optionsRef]);
201
212
  useSafeLayoutEffect(() => {
202
213
  if (state.targetValue) {
203
- var formElement = getFormElement(formRef);
204
- if (!formElement) {
214
+ var _formElement = getFormElement(formRef);
215
+ if (!_formElement) {
205
216
  // eslint-disable-next-line no-console
206
217
  console.error('Failed to update form value; No form element found');
207
218
  return;
208
219
  }
209
- updateFormValue(formElement, state.targetValue, optionsRef.current.serialize);
220
+ updateFormValue(_formElement, state.targetValue, optionsRef.current.serialize);
210
221
  }
211
222
  pendingValueRef.current = undefined;
212
223
  }, [formRef, state.targetValue, optionsRef]);
213
224
  var handleSubmit = useCallback(event => {
214
- var _abortControllerRef$c2, _lastAsyncResultRef$c;
225
+ var _abortControllerRef$c2;
215
226
  var abortController = new AbortController();
216
227
 
217
228
  // Keep track of the abort controller so we can cancel the previous request if a new one is made
218
229
  (_abortControllerRef$c2 = abortControllerRef.current) === null || _abortControllerRef$c2 === void 0 || _abortControllerRef$c2.abort('A new submission is made');
219
230
  abortControllerRef.current = abortController;
220
- var formData;
221
231
  var result;
222
232
  var resolvedValue;
233
+ var formElement = event.currentTarget;
234
+ var submitEvent = getSubmitEvent(event);
235
+ var formData = getFormData(formElement, submitEvent.submitter);
236
+ var submission = parseSubmission(formData, {
237
+ intentName: optionsRef.current.intentName
238
+ });
223
239
 
224
- // The form might be re-submitted manually if there was an async validation
225
- if (event.nativeEvent === ((_lastAsyncResultRef$c = lastAsyncResultRef.current) === null || _lastAsyncResultRef$c === void 0 ? void 0 : _lastAsyncResultRef$c.event)) {
226
- formData = lastAsyncResultRef.current.formData;
227
- result = lastAsyncResultRef.current.result;
228
- resolvedValue = lastAsyncResultRef.current.resolvedValue;
229
- } else {
230
- var _optionsRef$current$o2, _optionsRef$current2;
231
- var formElement = event.currentTarget;
232
- var submitEvent = getSubmitEvent(event);
233
- formData = getFormData(formElement, submitEvent.submitter);
234
- var submission = parseSubmission(formData, {
235
- intentName: optionsRef.current.intentName
236
- });
237
-
238
- // Patch missing fields in the submission object
239
- for (var element of formElement.elements) {
240
- if (isFieldElement(element) && element.name) {
241
- submission.fields = appendUniqueItem(submission.fields, element.name);
242
- }
240
+ // Patch missing fields in the submission object
241
+ for (var element of formElement.elements) {
242
+ if (isFieldElement(element) && element.name) {
243
+ submission.fields = appendUniqueItem(submission.fields, element.name);
243
244
  }
245
+ }
244
246
 
245
- // Override submission value if the pending value is not applied yet (i.e. batch updates)
246
- if (pendingValueRef.current !== undefined) {
247
- submission.payload = pendingValueRef.current;
248
- }
247
+ // Override submission value if the pending value is not applied yet (i.e. batch updates)
248
+ if (pendingValueRef.current !== undefined) {
249
+ submission.payload = pendingValueRef.current;
250
+ }
251
+ var lastAsyncResult = lastAsyncResultRef.current;
252
+
253
+ // Clear the last async result so it won't affect the next submission
254
+ lastAsyncResultRef.current = null;
255
+ if (lastAsyncResult &&
256
+ // Only default submission will be re-submitted after async validation
257
+ !submission.intent &&
258
+ // Ensure the submission payload is the same as the one being validated
259
+ deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) {
260
+ result = lastAsyncResult.result;
261
+ resolvedValue = lastAsyncResult.resolvedValue;
262
+ } else {
263
+ var _optionsRef$current$o2, _optionsRef$current2;
249
264
  var value = resolveIntent(submission);
250
265
  var submissionResult = report(submission, {
251
266
  keepFiles: true,
@@ -290,16 +305,20 @@ function useConform(formRef, options) {
290
305
 
291
306
  // If the form is meant to be submitted and there is no error
292
307
  if (submissionResult.error === null && !submission.intent) {
293
- var _event = createSubmitEvent(submitEvent.submitter);
294
-
295
- // Keep track of the submit event so we can skip validation on the next submit
296
- lastAsyncResultRef.current = {
297
- event: _event,
298
- formData,
299
- resolvedValue: value,
300
- result: submissionResult
301
- };
302
- formElement.dispatchEvent(_event);
308
+ // Keep track of the validated payload and resume submission on the next task.
309
+ // Calling requestSubmit() directly from the async callback, or from a
310
+ // microtask, can still be ignored before the native submission lifecycle
311
+ // has fully settled.
312
+ setTimeout(() => {
313
+ if (abortController.signal.aborted) {
314
+ return;
315
+ }
316
+ lastAsyncResultRef.current = {
317
+ resolvedValue: value,
318
+ result: submissionResult
319
+ };
320
+ requestSubmit(formElement, submitEvent.submitter);
321
+ }, 0);
303
322
  }
304
323
  }
305
324
  });
@@ -614,7 +633,7 @@ function useIntent(formRef) {
614
633
 
615
634
  /**
616
635
  * A React hook that lets you sync the state of an input and dispatch native form events from it.
617
- * This is useful when emulating native input behavior — typically by rendering a hidden base input
636
+ * This is useful when emulating native input behavior — typically by rendering a hidden base control
618
637
  * and syncing it with a custom input.
619
638
  *
620
639
  * @example
@@ -622,7 +641,9 @@ function useIntent(formRef) {
622
641
  * const control = useControl(options);
623
642
  * ```
624
643
  */
625
- function useControl(options) {
644
+
645
+ function useControl() {
646
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
626
647
  var {
627
648
  observer
628
649
  } = useContext(GlobalFormOptionsContext);
@@ -637,29 +658,40 @@ function useControl(options) {
637
658
  return Array.isArray(input) ? (_input$0$form = (_input$ = input[0]) === null || _input$ === void 0 ? void 0 : _input$.form) !== null && _input$0$form !== void 0 ? _input$0$form : null : input.form;
638
659
  }
639
660
  }), []);
661
+ var [defaultValue, setDefaultValue] = useState(() => deriveDefaultPayload(options));
662
+ var pendingDefaultValueSyncRef = useRef(false);
663
+
664
+ /**
665
+ * Keep defaultValue in sync with external option updates during render.
666
+ * This is required for structural controls where hidden descendants must be
667
+ * rendered in the same cycle as form state updates (e.g. update intents).
668
+ */
669
+ if (pendingDefaultValueSyncRef.current && inputRef.current && isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) {
670
+ pendingDefaultValueSyncRef.current = false;
671
+ setDefaultValue(() => deriveDefaultPayload(options));
672
+ }
640
673
  var eventDispatched = useRef({});
641
- 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);
642
- var snapshotRef = useRef(defaultSnapshot);
674
+ var snapshotRef = useRef(defaultValue);
643
675
  var optionsRef = useRef(options);
644
676
  useEffect(() => {
645
677
  optionsRef.current = options;
646
678
  });
647
-
648
- // This is necessary to ensure that input is re-registered
649
- // if the onFocus handler changes
650
- var shouldHandleFocus = typeof (options === null || options === void 0 ? void 0 : options.onFocus) === 'function';
651
- var snapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => {
679
+ useEffect(() => observer.onInternalUpdate(event => {
680
+ var input = inputRef.current;
681
+ if (input && input instanceof HTMLFieldSetElement && event.target === input.form) {
682
+ pendingDefaultValueSyncRef.current = true;
683
+ }
684
+ }), [observer]);
685
+ var payloadSnapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => {
686
+ var _inputRef$current;
652
687
  var input = event.target;
653
- if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) {
688
+ if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.contains(input)) {
654
689
  callback();
655
690
  }
656
691
  }), [observer]), () => {
657
692
  var input = inputRef.current;
658
693
  var prev = snapshotRef.current;
659
- var next = !input ? defaultSnapshot : Array.isArray(input) ? {
660
- value: getRadioGroupValue(input),
661
- options: getCheckboxGroupValue(input)
662
- } : getInputSnapshot(input);
694
+ var next = input ? resolveControlPayload(input) : defaultValue;
663
695
  if (deepEqual(prev, next)) {
664
696
  return prev;
665
697
  }
@@ -669,7 +701,8 @@ function useControl(options) {
669
701
  useEffect(() => {
670
702
  var createEventListener = listener => {
671
703
  return event => {
672
- if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : inputRef.current === event.target) {
704
+ var _inputRef$current2;
705
+ if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : event.target instanceof Node && ((_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.contains(event.target))) {
673
706
  var timer = eventDispatched.current[listener];
674
707
  if (timer) {
675
708
  clearTimeout(timer);
@@ -697,10 +730,62 @@ function useControl(options) {
697
730
  };
698
731
  }, []);
699
732
  return {
700
- value: snapshot.value,
701
- checked: snapshot.checked,
702
- options: snapshot.options,
703
- files: snapshot.files,
733
+ defaultValue,
734
+ get payload() {
735
+ if (payloadSnapshot != null && 'parse' in options) {
736
+ try {
737
+ return options.parse(payloadSnapshot);
738
+ } catch (error) {
739
+ var payloadText = '';
740
+ try {
741
+ payloadText = JSON.stringify(payloadSnapshot, null, 2);
742
+ } catch (_unused) {
743
+ payloadText = '<unserializable payload>';
744
+ }
745
+ throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), {
746
+ cause: error
747
+ });
748
+ }
749
+ }
750
+ return payloadSnapshot;
751
+ },
752
+ get value() {
753
+ if (payloadSnapshot === null) {
754
+ return '';
755
+ }
756
+ if (typeof payloadSnapshot === 'string') {
757
+ return payloadSnapshot;
758
+ }
759
+ return undefined;
760
+ },
761
+ get checked() {
762
+ if (payloadSnapshot === null) {
763
+ return false;
764
+ }
765
+ var value = 'value' in options && options.value ? options.value : 'on';
766
+ if (payloadSnapshot === value) {
767
+ return true;
768
+ }
769
+ return undefined;
770
+ },
771
+ get options() {
772
+ if (payloadSnapshot === null) {
773
+ return [];
774
+ }
775
+ if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) {
776
+ return payloadSnapshot;
777
+ }
778
+ return undefined;
779
+ },
780
+ get files() {
781
+ if (payloadSnapshot === null) {
782
+ return [];
783
+ }
784
+ if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => isGlobalInstance(item, 'File'))) {
785
+ return payloadSnapshot;
786
+ }
787
+ return undefined;
788
+ },
704
789
  formRef,
705
790
  register: useCallback(element => {
706
791
  if (!element) {
@@ -714,17 +799,16 @@ function useControl(options) {
714
799
  element.hidden = true;
715
800
  element.removeAttribute('type');
716
801
  }
717
- if (shouldHandleFocus) {
718
- makeInputFocusable(element);
719
- }
720
802
  if (element.type === 'checkbox' || element.type === 'radio') {
721
- var _optionsRef$current$v, _optionsRef$current7;
722
803
  // React set the value as empty string incorrectly when the value is undefined
723
804
  // This make sure the checkbox value falls back to the default value "on" properly
724
805
  // @see https://github.com/facebook/react/issues/17590
725
- 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';
806
+ var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on';
807
+ element.value = value;
726
808
  }
727
809
  initializeField(element, optionsRef.current);
810
+ } else if (element instanceof HTMLFieldSetElement) {
811
+ inputRef.current = element;
728
812
  } else {
729
813
  var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2;
730
814
  var inputs = Array.from(element);
@@ -734,39 +818,36 @@ function useControl(options) {
734
818
  throw new Error('You can only register a checkbox or radio group with the same name');
735
819
  }
736
820
  inputRef.current = inputs;
737
- for (var input of inputs) {
738
- var _optionsRef$current8;
739
- if (shouldHandleFocus) {
740
- makeInputFocusable(input);
821
+ if ('defaultValue' in optionsRef.current) {
822
+ for (var input of inputs) {
823
+ var _optionsRef$current7;
824
+ initializeField(input, {
825
+ // We will not be uitlizing defaultChecked / value on checkbox / radio group
826
+ defaultValue: (_optionsRef$current7 = optionsRef.current) === null || _optionsRef$current7 === void 0 ? void 0 : _optionsRef$current7.defaultValue
827
+ });
741
828
  }
742
- initializeField(input, {
743
- // We will not be uitlizing defaultChecked / value on checkbox / radio group
744
- defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue
745
- });
746
829
  }
747
830
  }
748
- }, [shouldHandleFocus]),
831
+ }, []),
749
832
  change: useCallback(value => {
750
833
  if (!eventDispatched.current.change) {
751
- var _inputRef$current;
752
- var element = Array.isArray(inputRef.current) ? (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.find(input => {
753
- var wasChecked = input.checked;
754
- var isChecked = Array.isArray(value) ? value.some(item => item === input.value) : input.value === value;
755
- switch (input.type) {
756
- case 'checkbox':
757
- // We assume that only one checkbox can be checked at a time
758
- // So we will pick the first element with checked state changed
759
- return wasChecked !== isChecked;
760
- case 'radio':
761
- // We cannot uncheck a radio button
762
- // So we will pick the first element that should be checked
763
- return isChecked;
764
- default:
765
- return false;
766
- }
767
- }) : inputRef.current;
834
+ var element = inputRef.current;
835
+ var isFieldset = element instanceof HTMLFieldSetElement;
836
+ var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value;
837
+ if (isFieldset) {
838
+ // Fieldset mode renders hidden descendant inputs from defaultValue.
839
+ // Flush this update before dispatching events so listeners see the
840
+ // latest form structure in the same input/change cycle.
841
+ flushSync(() => {
842
+ setDefaultValue(serializedValue);
843
+ });
844
+ }
768
845
  if (element) {
769
- change(element, typeof value === 'boolean' ? value ? element.value : null : value);
846
+ change(element, serializedValue, {
847
+ // Sometimes no change is made on the inputs but done through DOM mutation.
848
+ // But we still want to dispatch the event to notify listeners.
849
+ forceDispatch: isFieldset
850
+ });
770
851
  }
771
852
  }
772
853
  if (eventDispatched.current.change) {
@@ -877,4 +958,146 @@ function useLatest(value) {
877
958
  return ref;
878
959
  }
879
960
 
880
- export { FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
961
+ /**
962
+ * A component that renders hidden base control(s) based on the shape of defaultValue.
963
+ * Used with useControl to sync complex values with form data.
964
+ *
965
+ * @example
966
+ * ```tsx
967
+ * const control = useControl<{ street: string; city: string }>({
968
+ * defaultValue: { street: '123 Main St', city: 'Anytown' },
969
+ * parse(payload) {
970
+ * if (
971
+ * typeof payload === 'object' &&
972
+ * payload !== null &&
973
+ * 'street' in payload &&
974
+ * 'city' in payload &&
975
+ * typeof payload.street === 'string' &&
976
+ * typeof payload.city === 'string'
977
+ * ) {
978
+ * return payload;
979
+ * }
980
+ *
981
+ * throw new Error('Unexpected payload shape');
982
+ * },
983
+ * });
984
+ *
985
+ * <BaseControl
986
+ * type="fieldset"
987
+ * name="address"
988
+ * ref={control.register}
989
+ * defaultValue={control.defaultValue}
990
+ * />
991
+ * ```
992
+ */
993
+ var BaseControl = /*#__PURE__*/forwardRef(function BaseControl(props, ref) {
994
+ function formatValue(value) {
995
+ var serialized = serialize(value);
996
+ if (typeof serialized === 'string') {
997
+ return serialized;
998
+ }
999
+
1000
+ // null, undefined, File, or array - fallback to empty string
1001
+ return '';
1002
+ }
1003
+ function renderInput(name, value, form) {
1004
+ if (Array.isArray(value)) {
1005
+ return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form));
1006
+ }
1007
+ if (isPlainObject(value)) {
1008
+ return Object.entries(value).map(_ref5 => {
1009
+ var [key, val] = _ref5;
1010
+ return renderInput("".concat(name, ".").concat(key), val, form);
1011
+ });
1012
+ }
1013
+ return /*#__PURE__*/jsx("input", {
1014
+ name: name,
1015
+ defaultValue: formatValue(value),
1016
+ form: form
1017
+ }, name);
1018
+ }
1019
+ if (props.type === 'fieldset') {
1020
+ var {
1021
+ name,
1022
+ form,
1023
+ defaultValue: _defaultValue,
1024
+ hidden: _hidden = true
1025
+ } = props,
1026
+ fieldsetProps = _objectWithoutProperties(props, _excluded2);
1027
+ return /*#__PURE__*/jsx("fieldset", _objectSpread2(_objectSpread2({}, fieldsetProps), {}, {
1028
+ ref: ref,
1029
+ name: name,
1030
+ form: form,
1031
+ hidden: _hidden,
1032
+ children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null
1033
+ }));
1034
+ }
1035
+ if (props.type === 'select') {
1036
+ var {
1037
+ defaultValue: _defaultValue2,
1038
+ multiple = Array.isArray(_defaultValue2),
1039
+ hidden: _hidden2 = true
1040
+ } = props,
1041
+ selectProps = _objectWithoutProperties(props, _excluded3);
1042
+ if (multiple) {
1043
+ var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)];
1044
+ return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, {
1045
+ ref: ref,
1046
+ defaultValue: defaultOptions,
1047
+ hidden: _hidden2,
1048
+ multiple: true,
1049
+ children: defaultOptions.map((option, index) => /*#__PURE__*/jsx("option", {
1050
+ value: option,
1051
+ children: option
1052
+ }, index))
1053
+ }));
1054
+ }
1055
+ var defaultOption = formatValue(_defaultValue2);
1056
+ return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, {
1057
+ ref: ref,
1058
+ defaultValue: defaultOption,
1059
+ hidden: _hidden2,
1060
+ children: [defaultOption].map((option, index) => /*#__PURE__*/jsx("option", {
1061
+ value: option,
1062
+ children: option
1063
+ }, index))
1064
+ }));
1065
+ }
1066
+ if (props.type === 'textarea') {
1067
+ var {
1068
+ defaultValue: _defaultValue3,
1069
+ hidden: _hidden3 = true
1070
+ } = props,
1071
+ textareaProps = _objectWithoutProperties(props, _excluded4);
1072
+ return /*#__PURE__*/jsx("textarea", _objectSpread2(_objectSpread2({}, textareaProps), {}, {
1073
+ defaultValue: formatValue(_defaultValue3),
1074
+ ref: ref,
1075
+ hidden: _hidden3
1076
+ }));
1077
+ }
1078
+ if (props.type === 'checkbox' || props.type === 'radio') {
1079
+ var {
1080
+ defaultValue: _defaultValue4 = 'on',
1081
+ value = _defaultValue4,
1082
+ hidden: _hidden4 = true
1083
+ } = props,
1084
+ _inputProps = _objectWithoutProperties(props, _excluded5);
1085
+ return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, _inputProps), {}, {
1086
+ ref: ref,
1087
+ value: value,
1088
+ hidden: _hidden4
1089
+ }));
1090
+ }
1091
+ var {
1092
+ defaultValue,
1093
+ hidden = true
1094
+ } = props,
1095
+ inputProps = _objectWithoutProperties(props, _excluded6);
1096
+ return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, inputProps), {}, {
1097
+ ref: ref,
1098
+ defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined,
1099
+ hidden: hidden
1100
+ }));
1101
+ });
1102
+
1103
+ export { BaseControl, FormContextContext, FormOptionsProvider, FormProvider, GlobalFormOptionsContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useField, useForm, useFormContext, useFormData, useFormMetadata, useIntent, useLatest, useSafeLayoutEffect };
@@ -1,8 +1,8 @@
1
1
  export type { FieldName, FormError, FormValue, Submission, SubmissionResult, } from '@conform-to/dom/future';
2
2
  export { getFieldValue, parseSubmission, report, isDirty, } from '@conform-to/dom/future';
3
- export type { Control, DefaultValue, BaseMetadata, BaseFieldMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, CustomSchemaTypes, FormsConfig, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, Fieldset, IntentDispatcher, InferBaseErrorShape, InferCustomFormMetadata, InferCustomFieldMetadata, } from './types';
3
+ export type { Control, ControlOptions, BaseControlProps, DefaultValue, BaseMetadata, BaseFieldMetadata, CustomMetadata, CustomMetadataDefinition, BaseErrorShape, CustomTypes, CustomSchemaTypes, FormsConfig, FormContext, FormMetadata, FormOptions, FormRef, FieldMetadata, Fieldset, IntentDispatcher, InferBaseErrorShape, InferCustomFormMetadata, InferCustomFieldMetadata, } from './types';
4
4
  export { configureForms } from './forms';
5
- export { PreserveBoundary, FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
5
+ export { BaseControl, PreserveBoundary, FormProvider, FormOptionsProvider, useControl, useField, useForm, useFormData, useFormMetadata, useIntent, } from './hooks';
6
6
  export { shape } from './util';
7
7
  export { memoize } from './memoize';
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -27,6 +27,7 @@ Object.defineProperty(exports, 'report', {
27
27
  get: function () { return future.report; }
28
28
  });
29
29
  exports.configureForms = forms.configureForms;
30
+ exports.BaseControl = hooks.BaseControl;
30
31
  exports.FormOptionsProvider = hooks.FormOptionsProvider;
31
32
  exports.FormProvider = hooks.FormProvider;
32
33
  exports.PreserveBoundary = hooks.PreserveBoundary;
@@ -1,5 +1,5 @@
1
1
  export { getFieldValue, isDirty, parseSubmission, report } from '@conform-to/dom/future';
2
2
  export { configureForms } from './forms.mjs';
3
- export { FormOptionsProvider, FormProvider, PreserveBoundary, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
3
+ export { BaseControl, FormOptionsProvider, FormProvider, PreserveBoundary, useControl, useField, useForm, useFormData, useFormMetadata, useIntent } from './hooks.mjs';
4
4
  export { shape } from './util.mjs';
5
5
  export { memoize } from './memoize.mjs';