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
JavaScript
// 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