el-form-react-hooks
Version:
React Hook Form alternative - TypeScript-first useForm hook with enterprise-grade state management. Schema-agnostic validation (Zod, Yup, Valibot), minimal re-renders, advanced form controls.
198 lines (188 loc) • 8.22 kB
text/typescript
import { ValidatorConfig } from 'el-form-core';
export * from 'el-form-core';
import * as react_jsx_runtime from 'react/jsx-runtime';
import React$1 from 'react';
interface FileInfo {
name: string;
size: number;
type: string;
lastModified: number;
formattedSize: string;
isImage: boolean;
extension: string;
}
interface FileValidationOptions {
maxSize?: number;
minSize?: number;
maxFiles?: number;
minFiles?: number;
acceptedTypes?: string[];
acceptedExtensions?: string[];
}
type Primitive = string | number | boolean | symbol | null | undefined | Date | bigint | Function;
type ArrayElem<T> = T extends readonly (infer E)[] ? E : T extends (infer E)[] ? E : never;
type ArrayPaths<K extends string, V> = K | `${K}.${number}` | `${K}[${number}]` | (V extends object ? `${K}.${number}.${Path<V>}` | `${K}[${number}].${Path<V>}` : never);
type Path<T, Prev extends string = never> = T extends Primitive ? never : {
[K in Extract<keyof T, string>]: T[K] extends Primitive ? K | Prev : T[K] extends readonly any[] | any[] ? ArrayPaths<K, ArrayElem<T[K]>> | Prev : K | `${K}.${Path<T[K], Prev>}` | Prev;
}[Extract<keyof T, string>];
type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends `${infer Key}[${number}]` ? Key extends keyof T ? T[Key] extends readonly any[] | any[] ? PathValue<ArrayElem<T[Key]>, Rest> : never : never : K extends `${number}` ? T extends readonly any[] | any[] ? PathValue<ArrayElem<T>, Rest> : never : K extends keyof T ? PathValue<T[K], Rest> : never : P extends `${infer K}[${number}]` ? K extends keyof T ? T[K] extends readonly any[] | any[] ? ArrayElem<T[K]> : never : never : P extends `${number}` ? T extends readonly any[] | any[] ? ArrayElem<T> : never : P extends keyof T ? T[P] : unknown;
type RegisterReturn<Value> = {
name: string;
onChange: (e: React.ChangeEvent<any>) => void;
onBlur: (e: React.FocusEvent<any>) => void;
} & (Value extends boolean ? {
checked: boolean;
value?: never;
files?: never;
} : Value extends File | FileList | (File | null)[] | File[] | null | undefined ? {
files: FileList | File | File[] | null;
value?: never;
checked?: never;
} : {
value: Value;
checked?: never;
files?: never;
});
interface FormContextValue<T extends Record<string, any>> {
form: UseFormReturn<T>;
formId?: string;
}
interface FormState<T extends Record<string, any>> {
values: Partial<T>;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
isValid: boolean;
isDirty: boolean;
}
interface FormSnapshot<T extends Record<string, any>> {
values: Partial<T>;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
timestamp: number;
isDirty: boolean;
}
interface UseFormOptions<T extends Record<string, any>> {
defaultValues?: Partial<T>;
validators?: ValidatorConfig;
onSubmit?: (values: T) => void | Promise<void>;
fieldValidators?: Partial<Record<keyof T, ValidatorConfig>>;
fileValidators?: Partial<Record<keyof T, FileValidationOptions>>;
mode?: "onChange" | "onBlur" | "onSubmit" | "all";
validateOn?: "onChange" | "onBlur" | "onSubmit" | "manual";
schema?: any;
}
interface FieldState {
isDirty: boolean;
isTouched: boolean;
error?: string;
}
interface ResetOptions<T> {
values?: Partial<T>;
keepErrors?: boolean;
keepDirty?: boolean;
keepTouched?: boolean;
}
interface SetFocusOptions {
shouldSelect?: boolean;
}
interface UseFormReturn<T extends Record<string, any>> {
register<Name extends Path<T>>(name: Name): RegisterReturn<PathValue<T, Name>>;
handleSubmit: (onValid: (data: T) => void, onError?: (errors: Record<keyof T, string>) => void) => (e: React.FormEvent) => void;
formState: FormState<T>;
reset: (options?: ResetOptions<T>) => void;
setValue: <Name extends Path<T>>(path: Name, value: PathValue<T, Name>) => void;
setValues: (values: Partial<T>) => void;
watch: {
(): Partial<T>;
<Name extends Path<T>>(name: Name): PathValue<T, Name>;
<Names extends Path<T>>(names: Names[]): {
[K in Names]: PathValue<T, K>;
};
};
resetValues: (values?: Partial<T>) => void;
getFieldState: <Name extends keyof T>(name: Name) => FieldState;
isDirty: <Name extends keyof T>(name?: Name) => boolean;
getDirtyFields: () => Partial<Record<keyof T, boolean>>;
getTouchedFields: () => Partial<Record<keyof T, boolean>>;
isFieldDirty: (name: string) => boolean;
isFieldTouched: (name: string) => boolean;
isFieldValid: (name: string) => boolean;
hasErrors: () => boolean;
getErrorCount: () => number;
markAllTouched: () => void;
markFieldTouched: (name: string) => void;
markFieldUntouched: (name: string) => void;
trigger: {
(): Promise<boolean>;
<Name extends keyof T>(name: Name): Promise<boolean>;
<Names extends keyof T>(names: Names[]): Promise<boolean>;
};
clearErrors: (name?: keyof T) => void;
setError: <Name extends keyof T>(name: Name, error: string) => void;
setFocus: <Name extends keyof T>(name: Name, options?: SetFocusOptions) => void;
addArrayItem: (path: string, item: any) => void;
removeArrayItem: (path: string, index: number) => void;
resetField: <Name extends Path<T>>(name: Name) => void;
submit: () => Promise<void>;
submitAsync: () => Promise<{
success: true;
data: T;
} | {
success: false;
errors: Partial<Record<keyof T, string>>;
}>;
canSubmit: boolean;
getSnapshot: () => FormSnapshot<T>;
restoreSnapshot: (snapshot: FormSnapshot<T>) => void;
hasChanges: () => boolean;
getChanges: () => Partial<T>;
addFile: (name: string, file: File) => void;
removeFile: (name: string, index?: number) => void;
clearFiles: (name: string) => void;
getFileInfo: (file: File) => FileInfo;
getFilePreview: (file: File) => Promise<string | null>;
filePreview: Partial<Record<keyof T, string | null>>;
}
declare function useForm<T extends Record<string, any>>(options: UseFormOptions<T>): UseFormReturn<T>;
interface DiscriminatedUnionContextValue {
schema?: any;
unionMetadata?: {
discriminatorField: string;
options: Array<{
value: string;
label: string;
}>;
unionOptions: Record<string, any[]>;
};
}
declare function FormProvider<T extends Record<string, any>>({ children, form, formId, schema, }: {
children: React$1.ReactNode;
form: UseFormReturn<T>;
formId?: string;
schema?: any;
}): react_jsx_runtime.JSX.Element;
declare function useFormContext<T extends Record<string, any> = any>(): FormContextValue<T>;
declare function useFormState<T extends Record<string, any> = any>(): UseFormReturn<T>;
declare function useDiscriminatedUnionContext(): DiscriminatedUnionContextValue | undefined;
/**
* Select a slice of the form state and subscribe to changes in that slice.
* Default equality uses Object.is. Pass a custom equality for arrays/objects
* (e.g., shallowEqual) to avoid unnecessary notifications.
*/
declare function useFormSelector<TSelected>(selector: (s: FormState<any>) => TSelected, equalityFn?: (a: TSelected, b: TSelected) => boolean): TSelected;
type FieldSlice<T, Name extends Path<T>> = {
value: PathValue<T, Name>;
error: any;
touched: any;
};
declare function useField<T extends Record<string, any>, Name extends Path<T>>(name: Name): FieldSlice<T, Name>;
/**
* Efficient equality comparison utilities for form state management
*/
/**
* Shallow equality comparison for objects
* Much faster than JSON.stringify for simple comparisons
*/
declare function shallowEqual(obj1: any, obj2: any): boolean;
export { type FieldState, type FormContextValue, FormProvider, type FormState, type Path, type PathValue, type RegisterReturn, type ResetOptions, type SetFocusOptions, type UseFormOptions, type UseFormReturn, shallowEqual, useDiscriminatedUnionContext, useField, useForm, useFormContext, useFormSelector, useFormState };