UNPKG

@conform-to/react

Version:

Conform view adapter for react

761 lines (734 loc) 30.7 kB
'use client'; 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js'); var future = require('@conform-to/dom/future'); var react = require('react'); var util = require('./util.js'); var state = require('./state.js'); var intent = require('./intent.js'); var dom = require('./dom.js'); var jsxRuntime = require('react/jsx-runtime'); var INITIAL_KEY = 'INITIAL_KEY'; var FormConfig = /*#__PURE__*/react.createContext({ intentName: future.DEFAULT_INTENT_NAME, observer: future.createGlobalFormsObserver(), serialize: future.serialize }); var Form = /*#__PURE__*/react.createContext([]); /** * Provides form context to child components. * Stacks contexts to support nested forms, with latest context taking priority. */ function FormProvider(props) { var stack = react.useContext(Form); var value = react.useMemo( // Put the latest form context first to ensure that to be the first one found () => [props.context].concat(stack), [stack, props.context]); return /*#__PURE__*/jsxRuntime.jsx(Form.Provider, { value: value, children: props.children }); } function useFormContext(formId) { var contexts = react.useContext(Form); var context = formId ? contexts.find(context => formId === context.formId) : contexts[0]; if (!context) { throw new Error('No form context found; Have you render a <FormProvider /> with the corresponding form context?'); } return context; } /** * 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$1, setState] = react.useState(() => { var state$1 = state.initializeState(INITIAL_KEY); if (lastResult) { state$1 = state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, lastResult), {}, { type: 'initialize', intent: lastResult.submission.intent ? intent.deserializeIntent(lastResult.submission.intent) : null, ctx: { handlers: intent.actionHandlers, reset: () => state$1 } })); } return state$1; }); var keyRef = react.useRef(options.key); var resetKeyRef = react.useRef(state$1.resetKey); var optionsRef = useLatest(options); var lastResultRef = react.useRef(lastResult); var lastIntentedValueRef = react.useRef(); var lastAsyncResultRef = react.useRef(null); var abortControllerRef = react.useRef(null); var handleSubmission = react.useCallback((result, options) => { var _optionsRef$current$o, _optionsRef$current; var intent$1 = result.submission.intent ? intent.deserializeIntent(result.submission.intent) : null; setState(state$1 => state.updateState(state$1, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, { type: options.type, intent: intent$1, ctx: { handlers: intent.actionHandlers, reset() { return state.initializeState(); } } }))); var formElement = dom.getFormElement(formRef); if (!formElement || !result.error) { return; } (_optionsRef$current$o = (_optionsRef$current = optionsRef.current).onError) === null || _optionsRef$current$o === void 0 || _optionsRef$current$o.call(_optionsRef$current, { formElement, error: result.error, intent: intent$1 }); }, [formRef, optionsRef]); react.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'); }; }, []); react.useEffect(() => { // To avoid re-applying the same result twice if (lastResult && lastResult !== lastResultRef.current) { handleSubmission(lastResult, { type: 'server' }); lastResultRef.current = lastResult; } }, [lastResult, handleSubmission]); react.useEffect(() => { // Reset the form state if the form key changes if (options.key !== keyRef.current) { keyRef.current = options.key; setState(state.initializeState()); } }, [options.key]); react.useEffect(() => { var formElement = dom.getFormElement(formRef); // Reset the form values if the reset key changes if (formElement && state$1.resetKey !== resetKeyRef.current) { resetKeyRef.current = state$1.resetKey; formElement.reset(); } }, [formRef, state$1.resetKey]); react.useEffect(() => { if (!state$1.clientIntendedValue) { return; } var formElement = dom.getFormElement(formRef); if (!formElement) { // eslint-disable-next-line no-console console.error('Failed to update form value; No form element found'); return; } dom.updateFormValue(formElement, state$1.clientIntendedValue, optionsRef.current.serialize); lastIntentedValueRef.current = undefined; }, [formRef, state$1.clientIntendedValue, optionsRef]); var handleSubmit = react.useCallback(event => { var _abortControllerRef$c2, _lastAsyncResultRef$c; 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 formData; var result; var resolvedValue; // The form might be re-submitted manually if there was an async validation if (event.nativeEvent === ((_lastAsyncResultRef$c = lastAsyncResultRef.current) === null || _lastAsyncResultRef$c === void 0 ? void 0 : _lastAsyncResultRef$c.event)) { formData = lastAsyncResultRef.current.formData; result = lastAsyncResultRef.current.result; resolvedValue = lastAsyncResultRef.current.resolvedValue; } else { var _optionsRef$current$o2, _optionsRef$current2; var formElement = event.currentTarget; var submitEvent = dom.getSubmitEvent(event); formData = future.getFormData(formElement, submitEvent.submitter); var submission = future.parseSubmission(formData, { intentName: optionsRef.current.intentName }); // Patch missing fields in the submission object for (var element of formElement.elements) { if (future.isFieldElement(element) && element.name) { util.appendUniqueItem(submission.fields, element.name); } } // Override submission value if the last intended value is not applied yet (i.e. batch updates) if (lastIntentedValueRef.current != null) { submission.payload = lastIntentedValueRef.current; } var intendedValue = intent.applyIntent(submission); // Update the last intended value in case there will be another intent dispatched lastIntentedValueRef.current = intendedValue === submission.payload ? undefined : intendedValue; var submissionResult = future.report(submission, { keepFiles: true, intendedValue }); var validateResult = // Skip validation on form reset intendedValue !== null ? (_optionsRef$current$o2 = (_optionsRef$current2 = optionsRef.current).onValidate) === null || _optionsRef$current$o2 === void 0 ? void 0 : _optionsRef$current$o2.call(_optionsRef$current2, { payload: intendedValue, error: { formErrors: [], fieldErrors: {} }, intent: submission.intent ? intent.deserializeIntent(submission.intent) : null, formElement, submitter: submitEvent.submitter, formData }) : { error: null }; var { syncResult, asyncResult } = util.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(submissionResult, { type: 'server' }); // If the form is meant to be submitted and there is no error if (error === null && !submission.intent) { var _event = future.createSubmitEvent(submitEvent.submitter); // Keep track of the submit event so we can skip validation on the next submit lastAsyncResultRef.current = { event: _event, formData, resolvedValue: value, result: submissionResult }; formElement.dispatchEvent(_event); } } }); } handleSubmission(submissionResult, { type: 'client' }); 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 submissionResult.submission.intent || submissionResult.error !== null)) { event.preventDefault(); } result = submissionResult; } // 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 = future.report(result.submission, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, { keepFiles: true })); handleSubmission(_submissionResult, { type: 'server' }); } } }); } }, [handleSubmission, optionsRef]); return [state$1, handleSubmit]; } /** * The main React hook for form management. Handles form state, validation, and submission * while providing access to form metadata, field objects, and form actions. * * @see https://conform.guide/api/react/future/useForm * @example * ```tsx * const { form, fields } = useForm({ * onValidate({ payload, error }) { * if (!payload.email) { * error.fieldErrors.email = ['Required']; * } * return error; * } * }); * * return ( * <form {...form.props}> * <input name={fields.email.name} defaultValue={fields.email.defaultValue} /> * <div>{fields.email.errors}</div> * </form> * ); * ``` */ function useForm(options) { var _optionsRef$current$o4; var { id, defaultValue, constraint } = options; var config = react.useContext(FormConfig); var optionsRef = useLatest(options); var fallbackId = react.useId(); var formId = id !== null && id !== void 0 ? id : "form-".concat(fallbackId); var [state$1, handleSubmit] = useConform(formId, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, options), {}, { serialize: config.serialize, intentName: config.intentName, onError: (_optionsRef$current$o4 = optionsRef.current.onError) !== null && _optionsRef$current$o4 !== void 0 ? _optionsRef$current$o4 : dom.focusFirstInvalidField, onValidate(ctx) { var _options$onValidate, _options$onValidate2; if (options.schema) { var standardResult = options.schema['~standard'].validate(ctx.payload); if (standardResult instanceof Promise) { return standardResult.then(actualStandardResult => { if (typeof options.onValidate === 'function') { throw new Error('The "onValidate" handler is not supported when used with asynchronous schema validation.'); } return util.resolveStandardSchemaResult(actualStandardResult); }); } var resolvedResult = util.resolveStandardSchemaResult(standardResult); if (!options.onValidate) { return resolvedResult; } // Update the schema error in the context if (resolvedResult.error) { ctx.error = resolvedResult.error; } var validateResult = util.resolveValidateResult(options.onValidate(ctx)); if (validateResult.syncResult) { var _validateResult$syncR, _validateResult$syncR2; (_validateResult$syncR2 = (_validateResult$syncR = validateResult.syncResult).value) !== null && _validateResult$syncR2 !== void 0 ? _validateResult$syncR2 : _validateResult$syncR.value = resolvedResult.value; } if (validateResult.asyncResult) { validateResult.asyncResult = validateResult.asyncResult.then(result => { var _result$value; (_result$value = result.value) !== null && _result$value !== void 0 ? _result$value : result.value = resolvedResult.value; return result; }); } return [validateResult.syncResult, validateResult.asyncResult]; } 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 : { // To avoid conform falling back to server validation, // if neither schema nor validation handler is provided, // we just treat it as a valid client submission error: null }; } })); var intent = useIntent(formId); var context = react.useMemo(() => ({ formId, state: state$1, defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : null, constraint: constraint !== null && constraint !== void 0 ? constraint : null, handleSubmit: handleSubmit, handleInput(event) { var _optionsRef$current$o5, _optionsRef$current4; if (!future.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) { return; } (_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), {}, { target: event.target, currentTarget: event.target.form })); if (event.defaultPrevented) { return; } var { shouldValidate = 'onSubmit', shouldRevalidate = shouldValidate } = optionsRef.current; if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') { intent.validate(event.target.name); } }, handleBlur(event) { var _optionsRef$current$o6, _optionsRef$current5; if (!future.isFieldElement(event.target) || event.target.name === '' || event.target.form === null || event.target.form !== dom.getFormElement(formId)) { return; } (_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), {}, { target: event.target, currentTarget: event.target.form })); if (event.defaultPrevented) { return; } var { shouldValidate = 'onSubmit', shouldRevalidate = shouldValidate } = optionsRef.current; if (state.isTouched(state$1, event.target.name) ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') { intent.validate(event.target.name); } } }), [formId, state$1, defaultValue, constraint, handleSubmit, intent, optionsRef]); var form = react.useMemo(() => state.getFormMetadata(context, { serialize: config.serialize }), [context, config.serialize]); var fields = react.useMemo(() => state.getFieldset(context, { serialize: config.serialize }), [context, config.serialize]); return { form, fields, intent }; } /** * A React hook that provides access to form-level metadata and state. * Requires `FormProvider` context when used in child components. * * @see https://conform.guide/api/react/future/useFormMetadata * @example * ```tsx * function ErrorSummary() { * const form = useFormMetadata(); * * if (form.valid) return null; * * return ( * <div>Please fix {Object.keys(form.fieldErrors).length} errors</div> * ); * } * ``` */ function useFormMetadata() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var config = react.useContext(FormConfig); var context = useFormContext(options.formId); var formMetadata = react.useMemo(() => state.getFormMetadata(context, { serialize: config.serialize }), [context, config.serialize]); return formMetadata; } /** * A React hook that provides access to a specific field's metadata and state. * Requires `FormProvider` context when used in child components. * * @see https://conform.guide/api/react/future/useField * @example * ```tsx * function FormField({ name, label }) { * const field = useField(name); * * return ( * <div> * <label htmlFor={field.id}>{label}</label> * <input id={field.id} name={field.name} defaultValue={field.defaultValue} /> * {field.errors && <div>{field.errors.join(', ')}</div>} * </div> * ); * } * ``` */ function useField(name) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var config = react.useContext(FormConfig); var context = useFormContext(options.formId); var field = react.useMemo(() => state.getField(context, { name, serialize: config.serialize }), [context, name, config.serialize]); return field; } /** * A React hook that provides an intent dispatcher for programmatic form actions. * Intent dispatchers allow you to trigger form operations like validation, field updates, * and array manipulations without manual form submission. * * @see https://conform.guide/api/react/future/useIntent * @example * ```tsx * function ResetButton() { * const buttonRef = useRef<HTMLButtonElement>(null); * const intent = useIntent(buttonRef); * * return ( * <button type="button" ref={buttonRef} onClick={() => intent.reset()}> * Reset Form * </button> * ); * } * ``` */ function useIntent(formRef) { var config = react.useContext(FormConfig); return react.useMemo(() => dom.createIntentDispatcher(() => dom.getFormElement(formRef), config.intentName), [formRef, config.intentName]); } /** * 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 input * and syncing it with a custom input. * * @example * ```ts * const control = useControl(options); * ``` */ function useControl(options) { var { observer } = react.useContext(FormConfig); var inputRef = react.useRef(null); var eventDispatched = react.useRef({}); 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); var snapshotRef = react.useRef(defaultSnapshot); var optionsRef = react.useRef(options); react.useEffect(() => { optionsRef.current = options; }); // This is necessary to ensure that input is re-registered // if the onFocus handler changes var shouldHandleFocus = typeof (options === null || options === void 0 ? void 0 : options.onFocus) === 'function'; var snapshot = react.useSyncExternalStore(react.useCallback(callback => observer.onFieldUpdate(event => { var input = event.target; if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === input) : inputRef.current === input) { callback(); } }), [observer]), () => { var input = inputRef.current; var prev = snapshotRef.current; var next = !input ? defaultSnapshot : Array.isArray(input) ? { value: dom.getRadioGroupValue(input), options: dom.getCheckboxGroupValue(input) } : dom.getInputSnapshot(input); if (future.deepEqual(prev, next)) { return prev; } snapshotRef.current = next; return next; }, () => snapshotRef.current); react.useEffect(() => { var createEventListener = listener => { return event => { if (Array.isArray(inputRef.current) ? inputRef.current.some(item => item === event.target) : inputRef.current === 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$current6, _optionsRef$current6$; (_optionsRef$current6 = optionsRef.current) === null || _optionsRef$current6 === void 0 || (_optionsRef$current6$ = _optionsRef$current6.onFocus) === null || _optionsRef$current6$ === void 0 || _optionsRef$current6$.call(_optionsRef$current6); } } }; }; var inputHandler = createEventListener('change'); var focusHandler = createEventListener('focus'); var blurHandler = createEventListener('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 { value: snapshot.value, checked: snapshot.checked, options: snapshot.options, files: snapshot.files, register: react.useCallback(element => { if (!element) { inputRef.current = null; } else if (future.isFieldElement(element)) { inputRef.current = element; if (shouldHandleFocus) { dom.makeInputFocusable(element); } if (element.type === 'checkbox' || element.type === 'radio') { var _optionsRef$current$v, _optionsRef$current7; // 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 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'; } dom.initializeField(element, optionsRef.current); } 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; for (var input of inputs) { var _optionsRef$current8; if (shouldHandleFocus) { dom.makeInputFocusable(input); } dom.initializeField(input, { // We will not be uitlizing defaultChecked / value on checkbox / radio group defaultValue: (_optionsRef$current8 = optionsRef.current) === null || _optionsRef$current8 === void 0 ? void 0 : _optionsRef$current8.defaultValue }); } } }, [shouldHandleFocus]), change: react.useCallback(value => { if (!eventDispatched.current.change) { var _inputRef$current; var element = Array.isArray(inputRef.current) ? (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.find(input => { var wasChecked = input.checked; var isChecked = Array.isArray(value) ? value.some(item => item === input.value) : input.value === value; switch (input.type) { case 'checkbox': // We assume that only one checkbox can be checked at a time // So we will pick the first element with checked state changed return wasChecked !== isChecked; case 'radio': // We cannot uncheck a radio button // So we will pick the first element that should be checked return isChecked; default: return false; } }) : inputRef.current; if (element) { future.change(element, typeof value === 'boolean' ? value ? element.value : null : value); } } if (eventDispatched.current.change) { clearTimeout(eventDispatched.current.change); } eventDispatched.current.change = undefined; }, []), focus: react.useCallback(() => { if (!eventDispatched.current.focus) { var element = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current; if (element) { future.focus(element); } } if (eventDispatched.current.focus) { clearTimeout(eventDispatched.current.focus); } eventDispatched.current.focus = undefined; }, []), blur: react.useCallback(() => { if (!eventDispatched.current.blur) { var element = Array.isArray(inputRef.current) ? inputRef.current[0] : inputRef.current; if (element) { future.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. * * @see https://conform.guide/api/react/future/useFormData * @example * ```ts * const value = useFormData(formRef, formData => formData?.get('fieldName').toString() ?? ''); * ``` */ function useFormData(formRef, select, options) { var { observer } = react.useContext(FormConfig); var valueRef = react.useRef(); var formDataRef = react.useRef(null); var value = react.useSyncExternalStore(react.useCallback(callback => { var formElement = dom.getFormElement(formRef); if (formElement) { var formData = future.getFormData(formElement); formDataRef.current = options !== null && options !== void 0 && options.acceptFiles ? formData : new URLSearchParams(Array.from(formData).map(_ref2 => { var [key, value] = _ref2; return [key, value.toString()]; })); } var unsubscribe = observer.onFormUpdate(event => { if (event.target === dom.getFormElement(formRef)) { var _formData = future.getFormData(event.target, event.submitter); 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()]; })); callback(); } }); return unsubscribe; }, [observer, formRef, options === null || options === void 0 ? void 0 : options.acceptFiles]), () => { // @ts-expect-error FIXME var result = select(formDataRef.current, valueRef.current); if (typeof valueRef.current !== 'undefined' && future.deepEqual(result, valueRef.current)) { return valueRef.current; } valueRef.current = result; return result; }, () => select(null, undefined)); return value; } /** * useLayoutEffect is client-only. * This basically makes it a no-op on server */ var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.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 = react.useRef(value); useSafeLayoutEffect(() => { ref.current = value; }, [value]); return ref; } exports.Form = Form; exports.FormConfig = FormConfig; exports.FormProvider = FormProvider; exports.INITIAL_KEY = INITIAL_KEY; exports.useConform = useConform; exports.useControl = useControl; exports.useField = useField; exports.useForm = useForm; exports.useFormContext = useFormContext; exports.useFormData = useFormData; exports.useFormMetadata = useFormMetadata; exports.useIntent = useIntent; exports.useLatest = useLatest; exports.useSafeLayoutEffect = useSafeLayoutEffect;