UNPKG

el-form-react-hooks

Version:

TypeScript-first React form hooks with flexible validation - supports Zod, Yup, Valibot, custom functions, or no validation. Modern form state management with useForm hook.

1,032 lines (1,016 loc) 32.2 kB
// src/index.ts export * from "el-form-core"; // src/useForm.ts import { useState, useCallback, useRef } from "react"; import { setNestedValue as setNestedValue5, getNestedValue as getNestedValue4, ValidationEngine, createFileValidator } from "el-form-core"; // src/utils/index.ts import { setNestedValue as setNestedValue4, getNestedValue as getNestedValue3, removeArrayItem, parseZodErrors } from "el-form-core"; // src/utils/arrayHelpers.ts function addArrayItemReact(obj, path, item) { const result = { ...obj }; const normalizedPath = path.replace(/\[(\d+)\]/g, ".$1"); const keys = normalizedPath.split(".").filter((key) => key !== ""); let current = result; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!isNaN(Number(key))) { if (Array.isArray(current)) { current[Number(key)] = Array.isArray(current[Number(key)]) ? [...current[Number(key)]] : { ...current[Number(key)] }; current = current[Number(key)]; } } else { if (typeof current[key] !== "object" || current[key] === null) { current[key] = {}; } else { current[key] = Array.isArray(current[key]) ? [...current[key]] : { ...current[key] }; } current = current[key]; } } const arrayKey = keys[keys.length - 1]; if (!isNaN(Number(arrayKey))) { if (Array.isArray(current)) { current = [...current]; current[Number(arrayKey)] = item; } } else { if (!Array.isArray(current[arrayKey])) { current[arrayKey] = []; } else { current[arrayKey] = [...current[arrayKey]]; } current[arrayKey].push(item); } return result; } // src/utils/equality.ts function shallowEqual(obj1, obj2) { if (obj1 === obj2) return true; if (obj1 == null || obj2 == null) return false; if (typeof obj1 !== "object" || typeof obj2 !== "object") return obj1 === obj2; const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (let key of keys1) { if (!keys2.includes(key) || obj1[key] !== obj2[key]) { return false; } } return true; } function deepEqual(obj1, obj2) { if (obj1 === obj2) return true; if (obj1 == null || obj2 == null) return false; if (typeof obj1 !== typeof obj2) return false; if (typeof obj1 !== "object") return obj1 === obj2; if (Array.isArray(obj1) !== Array.isArray(obj2)) return false; if (Array.isArray(obj1)) { if (obj1.length !== obj2.length) return false; for (let i = 0; i < obj1.length; i++) { if (!deepEqual(obj1[i], obj2[i])) return false; } return true; } const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (let key of keys1) { if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { return false; } } return true; } // src/utils/dirtyState.ts import { getNestedValue } from "el-form-core"; function createDirtyStateManager(dirtyFieldsRef) { return { dirtyFieldsRef, // Helper function to check if form is dirty (optimized) checkIsDirty: (currentValues, defaultValues) => { if (dirtyFieldsRef.current.size > 0) return true; if (shallowEqual(defaultValues || {}, currentValues || {})) { return false; } return !deepEqual(defaultValues || {}, currentValues || {}); }, // Helper function to check if specific field is dirty (optimized) checkFieldIsDirty: (fieldName, currentValue, defaultValue) => { const fieldKey = String(fieldName); if (dirtyFieldsRef.current.has(fieldKey)) return true; if (defaultValue === currentValue) return false; return !deepEqual(defaultValue, currentValue); }, // Helper to mark field as dirty/clean updateFieldDirtyState: (fieldName, value, defaultValues) => { const initialValue = getNestedValue(defaultValues || {}, fieldName); const isDirty = !deepEqual(initialValue, value); if (isDirty) { dirtyFieldsRef.current.add(fieldName); } else { dirtyFieldsRef.current.delete(fieldName); } }, // Clear all dirty state clearDirtyState: () => { dirtyFieldsRef.current.clear(); }, // Remove specific field from dirty tracking removeDirtyField: (fieldName) => { dirtyFieldsRef.current.delete(fieldName); }, // Add field to dirty tracking addDirtyField: (fieldName) => { dirtyFieldsRef.current.add(fieldName); } }; } // src/utils/validation.ts function createValidationManager(options) { const { validationEngine, validators, fieldValidators, mode, validateOn } = options; return { // Determine if validation should run based on mode and validateOn option shouldValidate: (eventType) => { if (validateOn) { if (validateOn === "manual") return false; if (validateOn === eventType) return true; if (eventType === "onSubmit") return true; return false; } const hasValidatorForEvent = validators && validators[eventType]; if (hasValidatorForEvent) { return true; } if (mode === "all") return true; if (mode === eventType) return true; if (eventType === "onSubmit") return true; return false; }, // Validate field using the new validation system validateField: async (fieldName, fieldValue, formValues, eventType) => { const fieldKey = String(fieldName); const fieldConfig = fieldValidators[fieldKey]; if (!fieldConfig && !validators) return { isValid: true, errors: {} }; const event = { type: eventType, isAsync: false, fieldName: String(fieldName) }; let result = { isValid: true, errors: {} }; if (fieldConfig) { result = await validationEngine.current.validateField( String(fieldName), fieldValue, formValues, fieldConfig, event ); } if (result.isValid && validators) { const formResult = await validationEngine.current.validateForm( formValues, validators, event ); if (!formResult.isValid && formResult.errors[String(fieldName)]) { result = { isValid: false, errors: { [String(fieldName)]: formResult.errors[String(fieldName)] } }; } } return result; }, // Validate entire form validateForm: async (values, eventType = "onSubmit") => { const event = { type: eventType, isAsync: false }; let allErrors = {}; let isValid = true; for (const [fieldName, fieldConfig] of Object.entries(fieldValidators)) { const fieldResult = await validationEngine.current.validateField( fieldName, values[fieldName], values, fieldConfig, event ); if (!fieldResult.isValid) { isValid = false; Object.assign(allErrors, fieldResult.errors); } } if (validators) { const formResult = await validationEngine.current.validateForm( values, validators, event ); if (!formResult.isValid) { isValid = false; Object.assign(allErrors, formResult.errors); } } return { isValid, errors: allErrors }; } }; } // src/utils/fieldOperations.ts import { setNestedValue } from "el-form-core"; function createFieldOperationsManager(options) { const { formState, setFormState, dirtyManager, defaultValues } = options; return { // Form State Utilities isFieldDirty: (name) => { return dirtyManager.checkFieldIsDirty( name, formState.values[name], defaultValues[name] ); }, isFieldTouched: (name) => { return !!formState.touched[name]; }, isFieldValid: (name) => { return !formState.errors[name]; }, hasErrors: () => { return Object.keys(formState.errors).length > 0; }, getErrorCount: () => { return Object.keys(formState.errors).length; }, // Bulk operations markAllTouched: () => { setFormState((prev) => { const newTouched = {}; Object.keys(prev.values).forEach((key) => { newTouched[key] = true; }); return { ...prev, touched: newTouched }; }); }, markFieldTouched: (name) => { setFormState((prev) => { const newTouched = name.includes(".") ? setNestedValue(prev.touched, name, true) : { ...prev.touched, [name]: true }; return { ...prev, touched: newTouched }; }); }, markFieldUntouched: (name) => { setFormState((prev) => { const newTouched = name.includes(".") ? setNestedValue(prev.touched, name, false) : { ...prev.touched, [name]: false }; return { ...prev, touched: newTouched }; }); }, // Get all dirty fields getDirtyFields: () => { const dirtyFields = {}; dirtyManager.dirtyFieldsRef.current.forEach((fieldName) => { dirtyFields[fieldName] = true; }); Object.keys(formState.values).forEach((key) => { const fieldName = key; if (!dirtyFields[fieldName] && dirtyManager.checkFieldIsDirty( fieldName, formState.values[fieldName], defaultValues[fieldName] )) { dirtyFields[fieldName] = true; } }); return dirtyFields; }, // Get all touched fields getTouchedFields: () => { return { ...formState.touched }; }, resetField: (name) => { dirtyManager.removeDirtyField(String(name)); setFormState((prev) => { const newValues = { ...prev.values }; newValues[name] = defaultValues[name]; const newErrors = { ...prev.errors }; delete newErrors[name]; const newTouched = { ...prev.touched }; delete newTouched[name]; return { ...prev, values: newValues, errors: newErrors, touched: newTouched, isDirty: dirtyManager.dirtyFieldsRef.current.size > 0 }; }); }, getFieldState: (name) => ({ isDirty: dirtyManager.checkFieldIsDirty( name, formState.values[name], defaultValues[name] ), isTouched: Boolean(formState.touched[name]), error: formState.errors[name] }), // Check if form/field is dirty isDirty: (name) => { if (name) { return dirtyManager.checkFieldIsDirty( name, formState.values[name], defaultValues[name] ); } return formState.isDirty; } }; } // src/utils/formState.ts import { setNestedValue as setNestedValue2 } from "el-form-core"; function createFormStateManager(options) { const { formState, setFormState, dirtyManager, defaultValues } = options; return { setValue: (path, value) => { dirtyManager.updateFieldDirtyState(path, value, defaultValues); setFormState((prev) => ({ ...prev, values: setNestedValue2(prev.values, path, value), isDirty: dirtyManager.dirtyFieldsRef.current.size > 0 })); }, // setValues - Set multiple field values at once setValues: (values) => { Object.entries(values).forEach(([path, value]) => { dirtyManager.updateFieldDirtyState(path, value, defaultValues); }); setFormState((prev) => ({ ...prev, values: { ...prev.values, ...values }, isDirty: dirtyManager.dirtyFieldsRef.current.size > 0 })); }, // resetValues - Reset form with new default values resetValues: (values) => { const newValues = values ?? defaultValues; dirtyManager.clearDirtyState(); setFormState({ values: newValues, errors: {}, touched: {}, isSubmitting: false, isValid: false, isDirty: false }); }, watch: (nameOrNames) => { if (!nameOrNames) return formState.values; if (Array.isArray(nameOrNames)) { const result = {}; nameOrNames.forEach((name) => { result[name] = formState.values[name]; }); return result; } return formState.values[nameOrNames]; } }; } // src/utils/submitOperations.ts function createSubmitOperationsManager(options) { const { formState, setFormState, validationManager, onSubmit } = options; return { // Handle submit - simplified handleSubmit: (onValid, onError) => { return async (e) => { e.preventDefault(); setFormState((prev) => ({ ...prev, isSubmitting: true })); const { isValid, errors } = await validationManager.validateForm( formState.values ); setFormState((prev) => ({ ...prev, errors, isValid, isSubmitting: false })); if (isValid) { await onValid(formState.values); } else if (onError) { onError(errors); } }; }, // Advanced form control methods submit: async () => { if (!onSubmit) { console.warn("useForm: No onSubmit handler provided for submit()"); return; } setFormState((prev) => ({ ...prev, isSubmitting: true })); try { const { isValid, errors } = await validationManager.validateForm( formState.values ); setFormState((prev) => ({ ...prev, errors, isValid })); if (isValid) { await onSubmit(formState.values); } } finally { setFormState((prev) => ({ ...prev, isSubmitting: false })); } }, submitAsync: async () => { setFormState((prev) => ({ ...prev, isSubmitting: true })); try { const { isValid, errors } = await validationManager.validateForm( formState.values ); setFormState((prev) => ({ ...prev, errors, isValid })); if (isValid) { if (onSubmit) { await onSubmit(formState.values); } return { success: true, data: formState.values }; } else { return { success: false, errors }; } } finally { setFormState((prev) => ({ ...prev, isSubmitting: false })); } } }; } // src/utils/errorManagement.ts function createErrorManagementManager(options) { const { formState, setFormState, validationManager } = options; return { // Clear errors clearErrors: (name) => { setFormState((prev) => { if (name) { const newErrors = { ...prev.errors }; delete newErrors[name]; return { ...prev, errors: newErrors }; } return { ...prev, errors: {} }; }); }, // Set error setError: (name, error) => { setFormState((prev) => ({ ...prev, errors: { ...prev.errors, [name]: error }, isValid: false })); }, // Manual validation trigger trigger: async (nameOrNames) => { if (!nameOrNames) { const { isValid } = await validationManager.validateForm( formState.values ); return isValid; } if (Array.isArray(nameOrNames)) { const results = await Promise.all( nameOrNames.map( (name) => validationManager.validateField( name, formState.values[name], formState.values, "onSubmit" ) ) ); return results.every((result2) => result2.isValid); } const result = await validationManager.validateField( nameOrNames, formState.values[nameOrNames], formState.values, "onSubmit" ); return result.isValid; } }; } // src/utils/formHistory.ts import { getNestedValue as getNestedValue2, setNestedValue as setNestedValue3 } from "el-form-core"; function createFormHistoryManager(options) { const { formState, setFormState, dirtyManager, defaultValues } = options; return { // Form History & Persistence methods getSnapshot: () => { return { values: { ...formState.values }, errors: { ...formState.errors }, touched: { ...formState.touched }, timestamp: Date.now(), isDirty: formState.isDirty }; }, restoreSnapshot: (snapshot) => { dirtyManager.clearDirtyState(); Object.entries(snapshot.values).forEach(([path, value]) => { const defaultValue = getNestedValue2(defaultValues, path); if (value !== defaultValue) { dirtyManager.updateFieldDirtyState(path, value, defaultValues); } }); setFormState({ values: { ...snapshot.values }, errors: { ...snapshot.errors }, touched: { ...snapshot.touched }, isSubmitting: false, isValid: Object.keys(snapshot.errors).length === 0, isDirty: snapshot.isDirty || dirtyManager.dirtyFieldsRef.current.size > 0 }); }, hasChanges: () => { return formState.isDirty; }, getChanges: () => { const changes = {}; dirtyManager.dirtyFieldsRef.current.forEach((fieldPath) => { const currentValue = getNestedValue2(formState.values, fieldPath); const defaultValue = getNestedValue2(defaultValues, fieldPath); if (currentValue !== defaultValue) { if (fieldPath.includes(".")) { setNestedValue3(changes, fieldPath, currentValue); } else { changes[fieldPath] = currentValue; } } }); return changes; } }; } // src/utils/focusManagement.ts function createFocusManager(options) { const { fieldRefs } = options; return { // Focus management setFocus: (name, options2) => { const element = fieldRefs.current.get(name); if (element) { element.focus(); if (options2?.shouldSelect && "select" in element) { element.select(); } } } }; } // src/utils/arrayOperations.ts import { removeArrayItem as removeArrayItemCore } from "el-form-core"; function createArrayOperationsManager(options) { const { setFormState, dirtyManager } = options; return { // Array operations addArrayItem: (path, item) => { setFormState((prev) => { const newValues = addArrayItemReact(prev.values, path, item); dirtyManager.addDirtyField(path); return { ...prev, values: newValues, isDirty: true }; }); }, removeArrayItem: (path, index) => { setFormState((prev) => { const newValues = removeArrayItemCore(prev.values, path, index); dirtyManager.addDirtyField(path); return { ...prev, values: newValues, isDirty: true }; }); } }; } // src/utils/fileUtils.ts function getFileInfo(file) { return { name: file.name, size: file.size, type: file.type, lastModified: file.lastModified, formattedSize: formatFileSize(file.size), isImage: file.type.startsWith("image/"), extension: getFileExtension(file.name) }; } function formatFileSize(bytes) { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function getFileExtension(fileName) { return fileName.slice((fileName.lastIndexOf(".") - 1 >>> 0) + 2); } async function getFilePreview(file) { if (!file.type.startsWith("image/")) return null; return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => resolve(e.target?.result); reader.onerror = () => resolve(null); reader.readAsDataURL(file); }); } // src/useForm.ts function useForm(options) { const { defaultValues = {}, validators = {}, fieldValidators = {}, fileValidators = {}, mode = "onSubmit", validateOn, onSubmit } = options; const validationEngine = useRef(new ValidationEngine()); const fieldRefs = useRef(/* @__PURE__ */ new Map()); const dirtyFieldsRef = useRef(/* @__PURE__ */ new Set()); const formStateRef = useRef(); const [formState, setFormState] = useState({ values: defaultValues, errors: {}, touched: {}, isSubmitting: false, isValid: false, isDirty: false }); const [filePreview, setFilePreview] = useState({}); const canSubmit = formState.isValid && !formState.isSubmitting; formStateRef.current = formState; const dirtyManager = createDirtyStateManager(dirtyFieldsRef); const validationManager = createValidationManager({ validationEngine, validators, fieldValidators, mode, validateOn }); const fieldOperations = createFieldOperationsManager({ formState, setFormState, dirtyManager, defaultValues }); const formStateManager = createFormStateManager({ formState, setFormState, dirtyManager, defaultValues }); const submitOperations = createSubmitOperationsManager({ formState, setFormState, validationManager, onSubmit }); const errorManagement = createErrorManagementManager({ formState, setFormState, validationManager }); const formHistory = createFormHistoryManager({ formState, setFormState, dirtyManager, defaultValues }); const focusManager = createFocusManager({ fieldRefs }); const arrayOperations = createArrayOperationsManager({ setFormState, dirtyManager }); const register = useCallback( (name) => { const fieldName = name; const fieldValue = getNestedValue4(formStateRef.current?.values || {}, name) ?? ""; const isCheckbox = typeof fieldValue === "boolean"; const handleFileChange = async (name2, value) => { const fileValidationOptions = fileValidators && fieldName in fileValidators ? fileValidators[fieldName] : void 0; if (fileValidationOptions && value) { const fileValidator = createFileValidator(fileValidationOptions); const validationError = fileValidator({ value, fieldName, values: formState.values }); if (validationError) { setFormState((prev) => ({ ...prev, errors: { ...prev.errors, [fieldName]: validationError }, isValid: false })); return; } } let preview = null; if (value instanceof File) { preview = await getFilePreview(value); } dirtyManager.updateFieldDirtyState(name2, value, defaultValues); const newValues = name2.includes(".") ? setNestedValue5(formState.values, name2, value) : { ...formState.values, [name2]: value }; setFilePreview((prevPreviews) => { const newFilePreview = { ...prevPreviews }; if (preview !== void 0) { newFilePreview[fieldName] = preview; } else if (!value) { delete newFilePreview[fieldName]; } return newFilePreview; }); let newErrors = { ...formState.errors }; if (name2.includes(".")) { const nestedError = getNestedValue4(newErrors, name2); if (nestedError) { newErrors = setNestedValue5(newErrors, name2, void 0); } } else { delete newErrors[fieldName]; } if (validationManager.shouldValidate("onChange")) { const validationResult = await validationManager.validateField( fieldName, value, newValues, "onChange" ); if (!validationResult.isValid) { newErrors = { ...newErrors, ...validationResult.errors }; } } setFormState((prev) => ({ ...prev, values: newValues, errors: newErrors, isDirty: dirtyFieldsRef.current.size > 0 })); }; const baseProps = { name, onChange: async (e) => { const value = (() => { if (e.target.type === "file") { const files = e.target.files; const fileValue = e.target.multiple ? Array.from(files || []) : files?.[0] || null; handleFileChange(name, fileValue); return; } if (isCheckbox) return e.target.checked; if (e.target.type === "number") { const num = e.target.valueAsNumber; if (isNaN(num)) { return e.target.value === "" ? void 0 : e.target.value; } return num; } return e.target.value; })(); if (e.target.type === "file") return; dirtyManager.updateFieldDirtyState(name, value, defaultValues); setFormState((prev) => { const newValues = name.includes(".") ? setNestedValue5(prev.values, name, value) : { ...prev.values, [name]: value }; let newErrors = { ...prev.errors }; if (name.includes(".")) { const nestedError = getNestedValue4(newErrors, name); if (nestedError) { newErrors = setNestedValue5(newErrors, name, void 0); } } else { delete newErrors[fieldName]; } return { ...prev, values: newValues, errors: newErrors, isDirty: dirtyFieldsRef.current.size > 0 }; }); const shouldValidateResult = validationManager.shouldValidate("onChange"); if (shouldValidateResult) { const updatedValues = name.includes(".") ? setNestedValue5(formState.values, name, value) : { ...formState.values, [name]: value }; const result = await validationManager.validateField( fieldName, value, updatedValues, "onChange" ); setFormState((prev) => { const newErrors = { ...prev.errors }; if (!result.isValid && Object.keys(result.errors).length > 0) { Object.assign(newErrors, result.errors); } else { if (newErrors[fieldName]) { delete newErrors[fieldName]; } } const isFormValid = Object.values(newErrors).every( (error) => !error ); return { ...prev, errors: newErrors, isValid: isFormValid }; }); } }, onBlur: async (_e) => { setFormState((prev) => { const newTouched = name.includes(".") ? setNestedValue5(prev.touched, name, true) : { ...prev.touched, [name]: true }; return { ...prev, touched: newTouched }; }); if (validationManager.shouldValidate("onBlur")) { const currentState = formStateRef.current; const result = await validationManager.validateField( fieldName, currentState.values[fieldName], currentState.values, "onBlur" ); if (!result.isValid) { setFormState((prev) => ({ ...prev, errors: { ...prev.errors, ...result.errors }, isValid: false })); } } } }; const currentValue = getNestedValue4(formState.values, name); if (currentValue instanceof File || currentValue instanceof FileList || Array.isArray(currentValue) && currentValue.length > 0 && currentValue[0] instanceof File) { return { ...baseProps, files: currentValue }; } return isCheckbox ? { ...baseProps, checked: Boolean(fieldValue) } : { ...baseProps, value: fieldValue || "" }; }, [ defaultValues, dirtyManager, validationManager, fileValidators, formState.values ] ); const addFile = useCallback( (name, file) => { const currentValue = getNestedValue4(formState.values, name); if (currentValue instanceof FileList || Array.isArray(currentValue)) { const newFiles = [...Array.from(currentValue), file]; formStateManager.setValue(name, newFiles); } else { formStateManager.setValue(name, file); } }, [formStateManager, formState.values] ); const removeFile = useCallback( (name, index) => { const currentValue = getNestedValue4(formState.values, name); if (typeof index === "number" && (currentValue instanceof FileList || Array.isArray(currentValue))) { const files = Array.from(currentValue); files.splice(index, 1); formStateManager.setValue(name, files); } else { formStateManager.setValue(name, null); } }, [formStateManager, formState.values] ); const clearFiles = useCallback( (name) => { formStateManager.setValue(name, null); }, [formStateManager] ); const reset = useCallback( (options2) => { const newValues = options2?.values ?? defaultValues; if (!options2?.keepDirty) { dirtyManager.clearDirtyState(); } setFormState({ values: newValues, errors: options2?.keepErrors ? formState.errors : {}, touched: options2?.keepTouched ? formState.touched : {}, isSubmitting: false, isValid: false, isDirty: options2?.keepDirty ? formState.isDirty : false }); setFilePreview({}); }, [defaultValues, formState, dirtyManager] ); return { register, handleSubmit: submitOperations.handleSubmit, formState, reset, setValue: formStateManager.setValue, setValues: formStateManager.setValues, watch: formStateManager.watch, resetValues: formStateManager.resetValues, getFieldState: fieldOperations.getFieldState, isDirty: fieldOperations.isDirty, getDirtyFields: fieldOperations.getDirtyFields, getTouchedFields: fieldOperations.getTouchedFields, isFieldDirty: fieldOperations.isFieldDirty, isFieldTouched: fieldOperations.isFieldTouched, isFieldValid: fieldOperations.isFieldValid, hasErrors: fieldOperations.hasErrors, getErrorCount: fieldOperations.getErrorCount, markAllTouched: fieldOperations.markAllTouched, markFieldTouched: fieldOperations.markFieldTouched, markFieldUntouched: fieldOperations.markFieldUntouched, trigger: errorManagement.trigger, clearErrors: errorManagement.clearErrors, setError: errorManagement.setError, setFocus: focusManager.setFocus, addArrayItem: arrayOperations.addArrayItem, removeArrayItem: arrayOperations.removeArrayItem, resetField: fieldOperations.resetField, submit: submitOperations.submit, submitAsync: submitOperations.submitAsync, canSubmit, getSnapshot: formHistory.getSnapshot, restoreSnapshot: formHistory.restoreSnapshot, hasChanges: formHistory.hasChanges, getChanges: formHistory.getChanges, // File-specific methods addFile, removeFile, clearFiles, getFileInfo, getFilePreview, filePreview }; } // src/FormContext.tsx import { createContext, useContext } from "react"; import { jsx } from "react/jsx-runtime"; var FormContext = createContext(void 0); function FormProvider({ children, form, formId }) { return /* @__PURE__ */ jsx(FormContext.Provider, { value: { form, formId }, children }); } function useFormContext() { const context = useContext(FormContext); if (!context) { throw new Error("useFormContext must be used within a FormProvider"); } return context; } function useFormState() { const { form } = useFormContext(); return form; } export { FormProvider, useForm, useFormContext, useFormState }; //# sourceMappingURL=index.mjs.map