svelte-simple-form
Version:
A lightweight, **type-safe**, and **reactive** form state management hook for **Svelte 5**, featuring:
147 lines (146 loc) • 8.22 kB
TypeScript
type Primitive = string | number | boolean | symbol | null | undefined | bigint | File | Blob | FileList | Date | RegExp | ArrayBuffer | DataView | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any>;
type Paths<T, Prev extends string = ''> = T extends Primitive ? Prev : T extends (infer U)[] ? [U] extends [never] ? Prev | `${Prev}.${number}` : U extends Primitive ? Prev | `${Prev}.${number}` : Prev | `${Prev}.${number}` | Paths<U, `${Prev}.${number}`> : T extends object ? string extends keyof T ? any : number extends keyof T ? any : {
[K in keyof T & string]: Prev extends '' ? Paths<T[K], K> : Paths<T[K], `${Prev}.${K}`>;
}[keyof T & string] : Prev;
type FlatPaths<T> = Exclude<Paths<T, ''>, ''> extends infer P ? [P] extends [never] ? string : Extract<P, string> : string;
type IsArrayLike<T> = T extends readonly (infer _I)[] ? true : NonNullable<T> extends readonly (infer _I)[] ? true : false;
type Split<S extends string> = S extends `${infer A}.${infer B}` ? [A, ...Split<B>] : [S];
type _ValueFromParts<T, Parts extends readonly string[]> = Parts extends [] ? T : Parts extends [infer Head extends string, ...infer Rest extends string[]] ? Head extends `${number}` ? T extends readonly (infer U)[] | (infer U)[] ? _ValueFromParts<U, Rest> : never : Head extends keyof T ? _ValueFromParts<T[Head], Rest> : never : never;
type ValueFromPath<T, P extends FlatPaths<T>> = _ValueFromParts<T, Split<P>>;
type ArrayPaths<T> = {
[P in FlatPaths<T>]: IsArrayLike<ValueFromPath<T, P>> extends true ? P : never;
}[FlatPaths<T>];
type ArrayItem<T, P extends FlatPaths<T>> = NonNullable<ValueFromPath<T, P>> extends readonly (infer I)[] ? I : never;
export type Validator<T = any> = {
validateField(field: FlatPaths<T>, form: FormControlContext<T>, force?: boolean, config?: {
validateOn?: string[];
validateAfter?: string;
validateDebounce?: number;
}): boolean | Promise<boolean>;
validateForm(form: FormControlContext<T>): boolean | Promise<boolean>;
};
export type FormContext<T = Record<string, any>> = {
initialValues: T;
data: T;
reset: () => void;
submit: (callback?: (data: T) => any) => Promise<void>;
};
export type FormControlContext<T = Record<string, any>> = {
initialValues: T;
data: T;
errors: Record<FlatPaths<T>, string[] | undefined>;
touched: Record<FlatPaths<T>, boolean | undefined>;
dirty: Record<FlatPaths<T>, boolean | undefined>;
isValid: boolean;
isValidating: boolean;
isSubmitting: boolean;
isDirty: boolean;
reset: () => void;
resetField: (path: FlatPaths<T>) => void;
submit: (callback?: (data: T) => any) => Promise<void>;
setInitialValues: (values: T, props?: {
reset?: boolean;
}) => void;
setData: {
(values: T): void;
<P extends Exclude<Paths<T, ''>, ''>>(field: P, value: _ValueFromParts<T, Split<P>>): void;
};
setIsValid: (isValid: boolean) => void;
setIsValidating: (isValidating: boolean) => void;
setTouched: (field: FlatPaths<T>, value?: boolean) => void;
removeTouched: (field: FlatPaths<T>) => void;
setDirty: (field: FlatPaths<T>, value?: boolean) => void;
removeDirty: (field: FlatPaths<T>) => void;
arrayAdd: <P extends ArrayPaths<T>>(path: P, value: NonNullable<ValueFromPath<T, P>> extends readonly (infer I)[] ? I : never, idx?: number | undefined, opts?: FieldOptions) => void;
arrayRemove: <P extends ArrayPaths<T>>(path: P, index: number, opts?: FieldOptions) => void;
arraySwap: <P extends ArrayPaths<T>>(path: P, i: number, j: number, opts?: FieldOptions) => void;
arrayMove: <P extends ArrayPaths<T>>(path: P, from: number, to: number, opts?: FieldOptions) => void;
arrayRemoveBy: <P extends ArrayPaths<T>>(path: P, predicate: (item: ArrayItem<T, P>) => boolean, opts?: FieldOptions) => void;
arrayUpdateBy: <P extends ArrayPaths<T>>(path: P, predicate: (item: ArrayItem<T, P>) => boolean, value: ArrayItem<T, P> | ((prev: ArrayItem<T, P>) => ArrayItem<T, P>), opts: FieldOptions) => void;
setErrors: (errors: Record<string, string[] | undefined>) => void;
setError: (field: FlatPaths<T>, error: string | string[]) => void;
removeError: (field: FlatPaths<T>) => void;
validateField: (field: FlatPaths<T>) => Promise<boolean>;
validate: () => Promise<boolean>;
handler: (node: HTMLFormElement) => void;
};
type FormProps<T> = {
initialValues: T;
onSubmit?: (data: T) => Promise<void>;
onReset?: () => void;
onSubmitError?: (error: unknown) => void;
};
type FormControlProps<T> = Omit<FormProps<T>, 'onSubmitError'> & {
validator?: Validator<T>;
validateOn?: ('change' | 'blur' | 'submit')[];
validateAfter?: 'touched' | 'dirty' | 'touched-or-dirty' | 'touched-and-dirty';
validateDebounce?: number;
onSubmitErrorValidation?: (errors: Record<string, string[] | undefined>) => void;
onSubmitError?: (error: unknown, errors: Record<string, string[] | undefined>) => void;
};
type FieldOptions = {
shouldTouch?: boolean;
shouldDirty?: boolean;
shouldValidate?: boolean;
};
export declare function useForm<T>(props: FormProps<T>): {
form: {
initialValues: T;
data: T;
isSubmitting: boolean;
reset(): void;
submit(callback?: (data: T) => any): Promise<void>;
handler(node: HTMLFormElement): void;
};
};
export declare function useFormControl<T>(props: FormControlProps<T>): {
readonly form: {
initialValues: T;
data: T;
errors: Record<FlatPaths<T>, string[] | undefined>;
touched: Record<FlatPaths<T>, boolean | undefined>;
dirty: Record<FlatPaths<T>, boolean | undefined>;
isValid: boolean;
isValidating: boolean;
isSubmitting: boolean;
isDirty: boolean;
reset(): void;
resetField(path: FlatPaths<T>): void;
submit(callback?: (data: T) => any): Promise<void>;
setInitialValues: (values: T, props?: {
reset?: boolean;
}) => void;
setData: {
(values: T, props?: {
shouldValidate?: boolean;
} | undefined): void;
<P extends FlatPaths<T>>(field: P, value: _ValueFromParts<T, Split<P>>, props?: FieldOptions): void;
};
setIsValid(isValid: boolean): void;
setIsValidating(isValidating: boolean): void;
setTouched(field: FlatPaths<T>, value?: boolean): void;
removeTouched(field: FlatPaths<T>): void;
setDirty(field: FlatPaths<T>, value?: boolean): void;
removeDirty(field: FlatPaths<T>): void;
arrayAdd<P extends ArrayPaths<T>>(path: P, value: NonNullable<ValueFromPath<T, P>> extends readonly (infer I)[] ? I : never, idx?: number | undefined, opts?: FieldOptions): void;
arrayRemove<P extends ArrayPaths<T>>(path: P, index: number, opts?: FieldOptions): void;
arraySwap<P extends ArrayPaths<T>>(path: P, i: number, j: number, opts?: FieldOptions): void;
arrayMove<P extends ArrayPaths<T>>(path: P, from: number, to: number, opts?: FieldOptions): void;
arrayRemoveBy<P extends ArrayPaths<T>>(path: P, predicate: (item: ArrayItem<T, P>) => boolean, opts?: FieldOptions): void;
arrayUpdateBy<P extends ArrayPaths<T>>(path: P, predicate: (item: ArrayItem<T, P>) => boolean, value: ArrayItem<T, P> | ((prev: ArrayItem<T, P>) => ArrayItem<T, P>), opts?: FieldOptions): void;
setErrors(errors: Record<FlatPaths<T>, string[] | undefined>): void;
setError(field: FlatPaths<T>, error: string | string[]): void;
removeError(field: FlatPaths<T>): void;
validateField(field: FlatPaths<T>, force?: boolean): Promise<boolean>;
validate(): Promise<boolean>;
handler(node: HTMLFormElement): void;
};
readonly control: (node: any, data: {
field: FlatPaths<T>;
valueAsNumber?: boolean;
setValueAs?: (v: any) => Promise<void> | void;
} | FlatPaths<T>) => {
destroy(): void;
} | undefined;
};
export {};