pocket-hook-form
Version:
pocket-store base hook form
268 lines (233 loc) • 7.9 kB
TypeScript
import {RefObject} from 'react';
import type {Listener} from 'pocket-state';
interface FileList {
readonly length: number;
item(index: number): File | null;
[index: number]: File;
}
export type CustomElement<T extends FormValue> = Partial<HTMLElement> & {
name: FieldName<T>;
type?: string;
value?: any;
disabled?: boolean;
checked?: boolean;
options?: HTMLOptionsCollection;
files?: FileList | null;
focus?: Noop;
text?: string;
};
export type FieldElement<T extends FormValue = FormValue> =
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement
| CustomElement<FormValue>;
/**Primitive type */
export type Primitive =
| string
| number
| bigint
| boolean
| symbol
| null
| undefined;
export type ErrorType =
| 'required'
| 'minLength'
| 'maxLength'
| 'pattern'
| 'validate'
| (string & {});
/**
* Any object used as the form's value bag.
* Keys are field names, values are field values.
*/
export type FormValue = Record<string, any>;
/** input handler */
export interface InternalFieldRefHandle {
focus?: () => void;
blur?: () => void;
clear?: () => void;
open?: () => void;
}
/**
* Minimal interface for focusable inputs.
* You can attach any extra properties your input or any component exposes.
*/
export type FieldRef<
T extends InternalFieldRefHandle = InternalFieldRefHandle,
> = T | null;
/**
* Validation rules for a single field value.
*
* - `required`: boolean or a message string (truthy enables required)
* - `minLength` / `maxLength`: number or { value, message }
* - `pattern`: RegExp or { value, message }
* - `validate`: custom validator; return:
* - `true` or `undefined` → passes
* - `string` → error message
*/
export type FieldRules<V, T extends FormValue> = {
required?: boolean | string;
minLength?: {value: number; message?: string} | number;
maxLength?: {value: number; message?: string} | number;
pattern?: {value: RegExp; message?: string} | RegExp;
validate?: (value: V, allValues: T) => string | true | undefined;
};
/**
* A single validation error for a field.
* - `type`: the rule that failed (e.g., "required", "minLength", "pattern", "validate")
* - `message`: optional human-readable message
*/
export type FieldError = {
type: ErrorType;
message?: string;
};
/**
* Error bag keyed by field name.
* Each field can hold one error or a list of errors (when `criteriaMode: 'all'`).
*/
export type FormErrors<T extends FormValue> = Partial<{
[K in keyof T]: FieldError | FieldError[];
}>;
/** How multiple errors per field are collected. */
export type CriteriaMode = 'firstError' | 'all';
/** When validation runs automatically. */
export type Mode = 'onSubmit' | 'onChange' | 'onBlur';
/**
* Options for creating a form control.
*
* - `defaultValues`: initial values for all fields
* - `mode`: when to auto-validate (default: 'onSubmit')
* - `criteriaMode`: collect only first error or all (default: 'firstError')
* - `rules`: per-field validation rules
*/
export type CreateOptions<T extends FormValue> = {
defaultValues: T;
mode?: Mode;
criteriaMode?: CriteriaMode;
rules?: Partial<{[K in keyof T]: FieldRules<T[K], T>}>;
};
/**
* The full form state kept in the store.
* - `values`: current values
* - `errors`: current validation errors
* - `touched`: fields that have been blurred/interacted with
* - `dirty`: fields changed from their defaults
* - `submitting`: true while `handleSubmit` is running
*/
export type FormState<T extends FormValue> = {
values: T;
errors?: FormErrors<T>;
touched: Partial<Record<keyof T, boolean>>;
dirty: Partial<Record<keyof T, boolean>>;
submitting: boolean;
};
/**
* Public API exposed by the form controller + its underlying store.
* Pair with hooks like `useFormState` and components like `Controller`.
*/
export type FormStoreControl<T extends FormValue, TRef = null> = {
/**
* Register/unregister a field with an optional component ref.
*/
addRef: (name: keyof T, ref: FieldRef) => void;
removeRef: (name: keyof T) => void;
/** Get a previously registered ref (or null). */
getRef: <TRef extends InternalFieldRefHandle = InternalFieldRefHandle>(
name: keyof T,
) => FieldRef<TRef>;
/**
* Update a field's value.
* - `validate`: revalidate the field after setting
* - `markDirty`: mark the field as dirty
*/
setValue: <K extends keyof T>(
name: K,
value: T[K],
opts?: {validate?: boolean; markDirty?: boolean},
) => void;
/** Read a single field value. */
getField: <K extends keyof T>(name: K) => T[K];
/** Read the entire values object. */
getValues: () => T;
/** Set a single field error (overwrites existing). */
setError: <K extends keyof T>(name: K, error: FieldError) => void;
/** Replace the entire error bag. */
setErrors: (errors: FormErrors<T>) => void;
/** Clear all errors. */
resetErrors: () => void;
/** Clear errors for specific fields. */
clearErrors: <K extends keyof T>(...names: K[]) => void;
/** Focus a registered field if it has a focusable ref. */
focus: (name: keyof T) => void;
setTouched: <K extends keyof T>(name: K, touched?: boolean) => void;
resetTouched: () => voi;
/**
* Wrap a submit handler with validation + submitting lifecycle.
* Returns an async function suitable for onPress/onSubmitEditing.
*/
handleSubmit: (
fn: (values: T) => void | Promise<void>,
) => (e?: any) => Promise<void>;
/**
* Reset values to defaults or to a provided partial.
* Also clears `dirty`, `touched`, and `errors`.
*/
reset: (next?: Partial<T>) => void;
getStoreOptions: () => CreateOptions<T>;
updateOptions: (next: Partial<CreateOptions<T>>) => void;
/**
* Subscribe to the entire state.
* The listener is invoked **after commit** whenever state changes.
* @returns Unsubscribe function.
*/
subscribe(listener: Listener<FormState<T>>): () => void;
/**
* Subscribe to a derived slice of the state.
* The listener fires only when the selector's result changes.
* @returns Unsubscribe function.
*/
subscribe<S>(
selector: (state: FormState<T>) => S,
listener: Listener<S>,
): () => void;
/**
* Underlying pocket-state store carrying the FormState<T>.
* Use with `useStore`/`useFormState` to subscribe efficiently.
*/
// #private;
// readonly _store: import('pocket-state').Store<FormState<T>>;
};
export type FieldState = {
/** Has the user interacted with (blurred) this field? */
touched: boolean;
/** Has the value diverged from the initial/default value? */
isDirty: boolean;
/** Current validation error(s) associated with this field. */
errors?: FieldError[];
};
/**
* Return shape of `useController<T, K>` for a single field.
*
* - `value`: current field value
* - `onChange(v)`: update value (usually wired to `onChangeText`)
* - `onBlur()`: mark touched & trigger validation on blur
* - `ref`: input ref (e.g., React Native `TextInput`) used for focus control
* - `errors`: same as `fieldState.error` (kept for backward compatibility)
* - `onSubmitEditing()`: move focus to next field or submit (keyboard "Next"/"Done")
* - `fieldState`: meta info (touched/dirty/error) for easy UI rendering
*/
export type UseControllerReturn<T extends FormValue, K extends keyof T> = {
/** Current field value. */
value: T[K];
/** Update the field value and (optionally) validate/mark dirty upstream. */
onChange: (...e: any) => void;
/** Trigger validation and mark the field as touched. */
onBlur: () => void;
/** Ref to the underlying input component for focus management. */
ref: React.RefObject<any>;
/** Handler for keyboard submit/next; typically focuses the next field. */
onSubmitEditing: () => void;
/** Meta state for this field (touched/dirty/error). */
fieldState: FieldState;
};