UNPKG

@conform-to/react

Version:

Conform view adapter for react

789 lines (764 loc) 31.2 kB
'use client'; import { objectSpread2 as _objectSpread2, objectWithoutProperties as _objectWithoutProperties } from '../_virtual/_rollupPluginBabelHelpers.mjs'; import { createGlobalFormsObserver, isGlobalInstance, deepEqual, isFieldElement, change, focus, blur, getFormData, defaultSerialize, isPlainObject, normalizeFormError, dispatchInternalUpdateEvent, parseSubmission, report, requestSubmit } from '@conform-to/dom/future'; import { createContext, useRef, useContext, useMemo, useState, useEffect, useSyncExternalStore, useCallback, useLayoutEffect, forwardRef } from 'react'; import { appendUniqueItem, resolveValidateResult } from './util.mjs'; import { initializeState, updateState } from './state.mjs'; import { deserializeIntent, applyIntent, intentHandlers, resolveIntent } from './intent.mjs'; import { cleanupPreservedInputs, preserveInputs, deriveDefaultPayload, resolveControlPayload, initializeField, getFormElement, resetFormValue, updateFormValue, getSubmitEvent } from './dom.mjs'; import { flushSync } from 'react-dom'; import { jsx } from 'react/jsx-runtime'; var _excluded = ["name", "form", "defaultValue", "hidden"], _excluded2 = ["defaultValue", "multiple", "hidden"], _excluded3 = ["defaultValue", "hidden"], _excluded4 = ["defaultValue", "value", "hidden"], _excluded5 = ["defaultValue", "hidden"]; var INITIAL_KEY = 'INITIAL_KEY'; var GlobalFormsObserverContext = /*#__PURE__*/createContext(createGlobalFormsObserver()); /** * Preserves form field values when its contents are unmounted. * Useful for multi-step forms and virtualized lists. * * See https://conform.guide/api/react/future/PreserveBoundary */ function PreserveBoundary(props) { // name is used as key so React properly unmounts/remounts when switching // between boundaries. Without it, both sides of a ternary share // key={undefined} and React reuses the instance (useId and key prop // can't help here). This is why name is required. return /*#__PURE__*/jsx(PreserveBoundaryImpl, _objectSpread2({}, props), props.name); } function PreserveBoundaryImpl(props) { var fieldsetRef = useRef(null); // useLayoutEffect to restore values before paint, avoiding flash of default values useSafeLayoutEffect(() => { var fieldset = fieldsetRef.current; if (!fieldset || !fieldset.form) { return; } var form = fieldset.form; // On mount: restore values from preserved inputs cleanupPreservedInputs(fieldset, form, props.name); return () => { // On unmount: preserve input values preserveInputs(fieldset.querySelectorAll('input,select,textarea'), form, props.name); }; }, [props.name]); return /*#__PURE__*/jsx("fieldset", { ref: fieldsetRef, form: props.form, style: { display: 'contents' }, children: props.children }); } /** * Core form hook that manages form state, validation, and submission. * Handles both sync and async validation, intent dispatching, and DOM updates. */ function useConform(formRef, options) { var { lastResult } = options; var [state, setState] = useState(() => { var state = initializeState({ defaultValue: options.defaultValue, resetKey: INITIAL_KEY }); if (lastResult) { var intent = lastResult.submission.intent ? deserializeIntent(lastResult.submission.intent) : null; var result = applyIntent(lastResult, intent, { handlers: intentHandlers }); state = updateState(state, _objectSpread2(_objectSpread2({}, result), {}, { type: 'initialize', intent, ctx: { handlers: intentHandlers, cancelled: result !== lastResult, reset: defaultValue => initializeState({ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue, resetKey: INITIAL_KEY }) } })); } return state; }); var keyRef = useRef(options.key); var resetKeyRef = useRef(state.resetKey); var optionsRef = useLatest(options); var lastResultRef = useRef(lastResult); var pendingValueRef = useRef(); var lastAsyncResultRef = useRef(null); var abortControllerRef = useRef(null); var handleSubmission = useCallback(function (type, result) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : optionsRef.current; var normalizedResult = !result.error ? result : _objectSpread2(_objectSpread2({}, result), {}, { error: normalizeFormError(result.error) }); var intent = result.submission.intent ? deserializeIntent(result.submission.intent) : null; var finalResult = applyIntent(normalizedResult, intent, { handlers: intentHandlers }); var formElement = getFormElement(formRef); if (formElement && (finalResult.reset || typeof finalResult.targetValue !== 'undefined')) { dispatchInternalUpdateEvent(formElement); } setState(state => updateState(state, _objectSpread2(_objectSpread2({}, finalResult), {}, { type, intent, ctx: { handlers: intentHandlers, cancelled: finalResult !== normalizedResult, reset(defaultValue) { return initializeState({ defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : options.defaultValue }); } } }))); // TODO: move on error handler to a new effect if (formElement && finalResult.error) { var _optionsRef$current$o, _optionsRef$current; (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, { formElement, error: finalResult.error, intent }); } return finalResult; }, [formRef, optionsRef]); if (options.key !== keyRef.current) { keyRef.current = options.key; var formElement = getFormElement(formRef); if (formElement) { dispatchInternalUpdateEvent(formElement); } setState(initializeState({ defaultValue: options.defaultValue })); } else if (lastResult && lastResult !== lastResultRef.current) { lastResultRef.current = lastResult; handleSubmission('server', lastResult, options); } useEffect(() => { return () => { var _abortControllerRef$c; // Cancel pending validation request (_abortControllerRef$c = abortControllerRef.current) === null || _abortControllerRef$c === void 0 || _abortControllerRef$c.abort('The component is unmounted'); }; }, []); useSafeLayoutEffect(() => { var formElement = getFormElement(formRef); // Reset the form values if the reset key changes if (formElement && state.resetKey !== resetKeyRef.current) { resetKeyRef.current = state.resetKey; resetFormValue(formElement, state.defaultValue, optionsRef.current.serialize); pendingValueRef.current = undefined; } }, [formRef, state.resetKey, state.defaultValue, optionsRef]); useSafeLayoutEffect(() => { if (state.targetValue) { var _formElement = getFormElement(formRef); if (!_formElement) { // eslint-disable-next-line no-console console.error('Failed to update form value; No form element found'); return; } updateFormValue(_formElement, state.targetValue, optionsRef.current.serialize); } pendingValueRef.current = undefined; }, [formRef, state.targetValue, optionsRef]); var handleSubmit = useCallback(event => { var _abortControllerRef$c2; var abortController = new AbortController(); // Keep track of the abort controller so we can cancel the previous request if a new one is made (_abortControllerRef$c2 = abortControllerRef.current) === null || _abortControllerRef$c2 === void 0 || _abortControllerRef$c2.abort('A new submission is made'); abortControllerRef.current = abortController; var result; var resolvedValue; var formElement = event.currentTarget; var submitEvent = getSubmitEvent(event); var formData = getFormData(formElement, submitEvent.submitter); var submission = parseSubmission(formData, { intentName: optionsRef.current.intentName }); // Patch missing fields in the submission object for (var element of formElement.elements) { if (isFieldElement(element) && element.name) { submission.fields = appendUniqueItem(submission.fields, element.name); } } // Override submission value if the pending value is not applied yet (i.e. batch updates) if (pendingValueRef.current !== undefined) { submission.payload = pendingValueRef.current; } var lastAsyncResult = lastAsyncResultRef.current; // Clear the last async result so it won't affect the next submission lastAsyncResultRef.current = null; if (lastAsyncResult && // Only default submission will be re-submitted after async validation !submission.intent && // Ensure the submission payload is the same as the one being validated deepEqual(submission.payload, lastAsyncResult.result.submission.payload)) { result = lastAsyncResult.result; resolvedValue = lastAsyncResult.resolvedValue; } else { var _optionsRef$current$o2, _optionsRef$current2; var value = resolveIntent(submission); var submissionResult = report(submission, { keepFiles: true, value }); var validateResult = // Skip validation on form reset value !== undefined ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, { payload: value, error: { formErrors: null, fieldErrors: {} }, intent: submission.intent ? deserializeIntent(submission.intent) : null, formElement, submitter: submitEvent.submitter, formData, schemaValue: undefined }) : { error: null }; var { syncResult, asyncResult } = resolveValidateResult(validateResult); if (typeof syncResult !== 'undefined') { submissionResult.error = syncResult.error; resolvedValue = syncResult.value; } if (typeof asyncResult !== 'undefined') { // Update the form when the validation result is resolved asyncResult.then(_ref => { var { error, value } = _ref; // Update the form with the validation result // There is no need to flush the update in this case if (!abortController.signal.aborted) { submissionResult.error = error; handleSubmission('server', submissionResult); // If the form is meant to be submitted and there is no error if (submissionResult.error === null && !submission.intent) { // Keep track of the validated payload and resume submission on the next task. // Calling requestSubmit() directly from the async callback, or from a // microtask, can still be ignored before the native submission lifecycle // has fully settled. setTimeout(() => { if (abortController.signal.aborted) { return; } lastAsyncResultRef.current = { resolvedValue: value, result: submissionResult }; requestSubmit(formElement, submitEvent.submitter); }, 0); } } }); } var clientResult = handleSubmission('client', submissionResult); if (clientResult.reset || clientResult.targetValue !== undefined) { var _ref2, _clientResult$targetV; pendingValueRef.current = (_ref2 = (_clientResult$targetV = clientResult.targetValue) !== null && _clientResult$targetV !== void 0 ? _clientResult$targetV : optionsRef.current.defaultValue) !== null && _ref2 !== void 0 ? _ref2 : {}; } if ( // If client validation happens (typeof syncResult !== 'undefined' || typeof asyncResult !== 'undefined') && ( // Either the form is not meant to be submitted (i.e. intent is present) or there is an error / pending validation clientResult.submission.intent || clientResult.error !== null)) { event.preventDefault(); } result = clientResult; } // We might not prevent form submission if server validation is required // But the `onSubmit` handler should be triggered only if there is no intent if (!event.isDefaultPrevented() && result.submission.intent === null) { var _optionsRef$current$o3, _optionsRef$current3; (_optionsRef$current$o3 = (_optionsRef$current3 = optionsRef.current).onSubmit) === null || _optionsRef$current$o3 === void 0 || _optionsRef$current$o3.call(_optionsRef$current3, event, { formData, get value() { if (typeof resolvedValue === 'undefined') { throw new Error('`value` is not available; Please make sure you have included the value in the `onValidate` result.'); } return resolvedValue; }, update(options) { if (!abortController.signal.aborted) { var _submissionResult = report(result.submission, _objectSpread2(_objectSpread2({}, options), {}, { keepFiles: true })); handleSubmission('server', _submissionResult); } } }); } }, [handleSubmission, optionsRef]); return [state, handleSubmit]; } /** * A React hook that lets you sync the state of an input and dispatch native form events from it. * This is useful when emulating native input behavior — typically by rendering a hidden base control * and syncing it with a custom input. * * **Example:** * ```ts * const control = useControl(options); * ``` */ function useControl() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var observer = useContext(GlobalFormsObserverContext); var inputRef = useRef(null); var formRef = useMemo(() => ({ get current() { var _input$0$form, _input$; var input = inputRef.current; if (!input) { return null; } 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; } }), []); var [defaultValue, setDefaultValue] = useState(() => deriveDefaultPayload(options)); var pendingDefaultValueSyncRef = useRef(false); /** * Keep defaultValue in sync with external option updates during render. * This is required for structural controls where hidden descendants must be * rendered in the same cycle as form state updates (e.g. update intents). */ if (pendingDefaultValueSyncRef.current && inputRef.current && isGlobalInstance(inputRef.current, 'HTMLFieldSetElement')) { pendingDefaultValueSyncRef.current = false; setDefaultValue(() => deriveDefaultPayload(options)); } var eventDispatched = useRef({}); var snapshotRef = useRef(defaultValue); var optionsRef = useRef(options); useEffect(() => { optionsRef.current = options; }); useEffect(() => observer.onInternalUpdate(event => { var input = inputRef.current; if (input && input instanceof HTMLFieldSetElement && event.target === input.form) { pendingDefaultValueSyncRef.current = true; } }), [observer]); var payloadSnapshot = useSyncExternalStore(useCallback(callback => observer.onFieldUpdate(event => { var _inputRef$current; var input = event.target; 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)) { callback(); } }), [observer]), () => { var input = inputRef.current; var prev = snapshotRef.current; var next = input ? resolveControlPayload(input) : defaultValue; if (deepEqual(prev, next)) { return prev; } snapshotRef.current = next; return next; }, () => snapshotRef.current); useEffect(() => { var createEventListener = (listener, event) => { var _inputRef$current2; 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))) { var timer = eventDispatched.current[listener]; if (timer) { clearTimeout(timer); } eventDispatched.current[listener] = window.setTimeout(() => { eventDispatched.current[listener] = undefined; }); if (listener === 'focus') { var _optionsRef$current4, _optionsRef$current4$; (_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 || (_optionsRef$current4$ = _optionsRef$current4.onFocus) === null || _optionsRef$current4$ === void 0 || _optionsRef$current4$.call(_optionsRef$current4); } } }; var inputHandler = createEventListener.bind(null, 'change'); var focusHandler = createEventListener.bind(null, 'focus'); var blurHandler = createEventListener.bind(null, 'blur'); document.addEventListener('input', inputHandler, true); document.addEventListener('focusin', focusHandler, true); document.addEventListener('focusout', blurHandler, true); return () => { document.removeEventListener('input', inputHandler, true); document.removeEventListener('focusin', focusHandler, true); document.removeEventListener('focusout', blurHandler, true); }; }, []); return { defaultValue, get payload() { if (payloadSnapshot != null && 'parse' in options) { try { return options.parse(payloadSnapshot); } catch (error) { var payloadText = ''; try { payloadText = JSON.stringify(payloadSnapshot, null, 2); } catch (_unused) { payloadText = '<unserializable payload>'; } throw new Error("Failed to parse the payload. Received ".concat(payloadText, "."), { cause: error }); } } return payloadSnapshot; }, get value() { if (payloadSnapshot === null) { return ''; } if (typeof payloadSnapshot === 'string') { return payloadSnapshot; } return undefined; }, get checked() { if (payloadSnapshot === null) { return false; } var value = 'value' in options && options.value ? options.value : 'on'; if (payloadSnapshot === value) { return true; } return undefined; }, get options() { if (payloadSnapshot === null) { return []; } if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => typeof item === 'string')) { return payloadSnapshot; } return undefined; }, get files() { if (payloadSnapshot === null) { return []; } if (Array.isArray(payloadSnapshot) && payloadSnapshot.every(item => isGlobalInstance(item, 'File'))) { return payloadSnapshot; } return undefined; }, formRef, register: useCallback(element => { if (!element) { inputRef.current = null; } else if (isFieldElement(element)) { inputRef.current = element; // Conform excludes hidden type inputs by default when updating form values // Fix that by using the hidden attribute instead if (element.type === 'hidden') { element.hidden = true; element.removeAttribute('type'); } if (element.type === 'checkbox' || element.type === 'radio') { // React set the value as empty string incorrectly when the value is undefined // This make sure the checkbox value falls back to the default value "on" properly // See https://github.com/facebook/react/issues/17590 var value = 'value' in optionsRef.current && optionsRef.current.value ? optionsRef.current.value : 'on'; element.value = value; } initializeField(element, optionsRef.current); } else if (element instanceof HTMLFieldSetElement) { inputRef.current = element; } else { var _inputs$0$name, _inputs$, _inputs$0$type, _inputs$2; var inputs = Array.from(element); var name = (_inputs$0$name = (_inputs$ = inputs[0]) === null || _inputs$ === void 0 ? void 0 : _inputs$.name) !== null && _inputs$0$name !== void 0 ? _inputs$0$name : ''; var type = (_inputs$0$type = (_inputs$2 = inputs[0]) === null || _inputs$2 === void 0 ? void 0 : _inputs$2.type) !== null && _inputs$0$type !== void 0 ? _inputs$0$type : ''; if (!name || !(type === 'checkbox' || type === 'radio') || !inputs.every(input => input.name === name && input.type === type)) { throw new Error('You can only register a checkbox or radio group with the same name'); } inputRef.current = inputs; if ('defaultValue' in optionsRef.current) { for (var input of inputs) { var _optionsRef$current5; initializeField(input, { // We will not be uitlizing defaultChecked / value on checkbox / radio group defaultValue: (_optionsRef$current5 = optionsRef.current) === null || _optionsRef$current5 === void 0 ? void 0 : _optionsRef$current5.defaultValue }); } } } }, []), change: useCallback(value => { if (!eventDispatched.current.change) { var element = inputRef.current; var isFieldset = element instanceof HTMLFieldSetElement; var serializedValue = value == null ? value : 'serialize' in optionsRef.current && optionsRef.current.serialize ? optionsRef.current.serialize(value) : value; if (isFieldset) { // Fieldset mode renders hidden descendant inputs from defaultValue. // Flush this update before dispatching events so listeners see the // latest form structure in the same input/change cycle. flushSync(() => { setDefaultValue(serializedValue); }); } if (element) { change(element, serializedValue, { // Sometimes no change is made on the inputs but done through DOM mutation. // But we still want to dispatch the event to notify listeners. forceDispatch: isFieldset }); } } if (eventDispatched.current.change) { clearTimeout(eventDispatched.current.change); } eventDispatched.current.change = undefined; }, []), focus: useCallback(() => { if (!eventDispatched.current.focus) { var element = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current; if (element) { focus(element); } } if (eventDispatched.current.focus) { clearTimeout(eventDispatched.current.focus); } eventDispatched.current.focus = undefined; }, []), blur: useCallback(() => { if (!eventDispatched.current.blur) { var element = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current; if (element) { blur(element); } } if (eventDispatched.current.blur) { clearTimeout(eventDispatched.current.blur); } eventDispatched.current.blur = undefined; }, []) }; } /** * A React hook that lets you subscribe to the current `FormData` of a form and derive a custom value from it. * The selector runs whenever the form's structure or data changes, and the hook re-renders only when the result is deeply different. * * Returns `undefined` when the form element is not available (e.g., on SSR or initial client render), * unless a `fallback` is provided. * * See https://conform.guide/api/react/future/useFormData * * **Example:** * ```ts * const value = useFormData( * formRef, * formData => formData.get('fieldName') ?? '', * ); * ``` */ function useFormData(formRef, select, options) { var observer = useContext(GlobalFormsObserverContext); var valueRef = useRef(); var formDataRef = useRef(); var value = useSyncExternalStore(useCallback(callback => { var formElement = getFormElement(formRef); if (formElement) { var formData = getFormData(formElement); formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? formData : new URLSearchParams(Array.from(formData).map(_ref3 => { var [key, value] = _ref3; return [key, value.toString()]; })); } var unsubscribe = observer.onFormUpdate(event => { if (event.target === getFormElement(formRef)) { var _formData = getFormData(event.target, event.submitter); formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? _formData : new URLSearchParams(Array.from(_formData).map(_ref4 => { var [key, value] = _ref4; return [key, value.toString()]; })); callback(); } }); return unsubscribe; }, [observer, formRef, options === null || options === void 0 ? void 0 : options.acceptFiles]), () => { // Return fallback if form is not available if (formDataRef.current === undefined) { return options === null || options === void 0 ? void 0 : options.fallback; } var result = select(formDataRef.current, valueRef.current); if (typeof valueRef.current !== 'undefined' && deepEqual(result, valueRef.current)) { return valueRef.current; } valueRef.current = result; return result; }, () => options === null || options === void 0 ? void 0 : options.fallback); return value; } /** * useLayoutEffect is client-only. * This basically makes it a no-op on server */ var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect; /** * Keep a mutable ref in sync with the latest value. * Useful to avoid stale closures in event handlers or async callbacks. */ function useLatest(value) { var ref = useRef(value); useSafeLayoutEffect(() => { ref.current = value; }, [value]); return ref; } /** * A component that renders hidden base control(s) based on the shape of defaultValue. * Used with useControl to sync complex values with form data. * * **Example:** * ```tsx * const control = useControl<{ street: string; city: string }>({ * defaultValue: { street: '123 Main St', city: 'Anytown' }, * parse(payload) { * if ( * typeof payload === 'object' && * payload !== null && * 'street' in payload && * 'city' in payload && * typeof payload.street === 'string' && * typeof payload.city === 'string' * ) { * return payload; * } * * throw new Error('Unexpected payload shape'); * }, * }); * * <BaseControl * type="fieldset" * name="address" * ref={control.register} * defaultValue={control.defaultValue} * /> * ``` */ var BaseControl = /*#__PURE__*/forwardRef(function BaseControl(props, ref) { function formatValue(value) { var serialized = defaultSerialize(value); if (typeof serialized === 'string') { return serialized; } // null, undefined, File, or array - fallback to empty string return ''; } function renderInput(name, value, form) { if (Array.isArray(value)) { return value.map((item, index) => renderInput("".concat(name, "[").concat(index, "]"), item, form)); } if (isPlainObject(value)) { return Object.entries(value).map(_ref5 => { var [key, val] = _ref5; return renderInput("".concat(name, ".").concat(key), val, form); }); } return /*#__PURE__*/jsx("input", { name: name, defaultValue: formatValue(value), form: form }, name); } if (props.type === 'fieldset') { var { name, form, defaultValue: _defaultValue, hidden: _hidden = true } = props, fieldsetProps = _objectWithoutProperties(props, _excluded); return /*#__PURE__*/jsx("fieldset", _objectSpread2(_objectSpread2({}, fieldsetProps), {}, { ref: ref, name: name, form: form, hidden: _hidden, children: _defaultValue != null ? renderInput(name, _defaultValue, form) : null })); } if (props.type === 'select') { var { defaultValue: _defaultValue2, multiple = Array.isArray(_defaultValue2), hidden: _hidden2 = true } = props, selectProps = _objectWithoutProperties(props, _excluded2); if (multiple) { var defaultOptions = Array.isArray(_defaultValue2) ? _defaultValue2.map(formatValue) : [formatValue(_defaultValue2)]; return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, { ref: ref, defaultValue: defaultOptions, hidden: _hidden2, multiple: true, children: defaultOptions.map((option, index) => /*#__PURE__*/jsx("option", { value: option, children: option }, index)) })); } var defaultOption = formatValue(_defaultValue2); return /*#__PURE__*/jsx("select", _objectSpread2(_objectSpread2({}, selectProps), {}, { ref: ref, defaultValue: defaultOption, hidden: _hidden2, children: [defaultOption].map((option, index) => /*#__PURE__*/jsx("option", { value: option, children: option }, index)) })); } if (props.type === 'textarea') { var { defaultValue: _defaultValue3, hidden: _hidden3 = true } = props, textareaProps = _objectWithoutProperties(props, _excluded3); return /*#__PURE__*/jsx("textarea", _objectSpread2(_objectSpread2({}, textareaProps), {}, { defaultValue: formatValue(_defaultValue3), ref: ref, hidden: _hidden3 })); } if (props.type === 'checkbox' || props.type === 'radio') { var { defaultValue: _defaultValue4 = 'on', value = _defaultValue4, hidden: _hidden4 = true } = props, _inputProps = _objectWithoutProperties(props, _excluded4); return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, _inputProps), {}, { ref: ref, value: value, hidden: _hidden4 })); } var { defaultValue, hidden = true } = props, inputProps = _objectWithoutProperties(props, _excluded5); return /*#__PURE__*/jsx("input", _objectSpread2(_objectSpread2({}, inputProps), {}, { ref: ref, defaultValue: defaultValue !== undefined ? formatValue(defaultValue) : undefined, hidden: hidden })); }); export { BaseControl, GlobalFormsObserverContext, INITIAL_KEY, PreserveBoundary, useConform, useControl, useFormData, useLatest, useSafeLayoutEffect };