alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
316 lines • 11.7 kB
TypeScript
import { Alepha, Static, TArray, TObject, TSchema, TypeBoxError } from "alepha";
import { InputHTMLAttributes, ReactNode } from "react";
//#region ../../src/react/form/services/FormModel.d.ts
/**
* FormModel is a dynamic form handler that generates form inputs based on a provided TypeBox schema.
* It manages form state, handles input changes, and processes form submissions with validation.
*
* It means to be injected and used within React components to provide a structured way to create and manage forms.
*
* @see {@link useForm}
*/
declare class FormModel<T extends TObject> {
readonly id: string;
readonly options: FormCtrlOptions<T>;
protected readonly log: import("alepha/logger").Logger;
protected readonly alepha: Alepha;
protected readonly values: Record<string, any>;
protected readonly initialValues: Record<string, any>;
protected submitInProgress: boolean;
input: SchemaToInput<T>;
constructor(id: string, options: FormCtrlOptions<T>);
/**
* Extract default values from a zod object schema.
* Recursively handles nested objects, unwrapping optional/nullable/default.
*/
protected extractSchemaDefaults(schema: TObject, prefix?: string): Record<string, any>;
get currentValues(): Record<string, any>;
get props(): {
id: string;
noValidate: boolean;
onSubmit: (ev?: FormEventLike) => void;
onReset: (event: FormEventLike) => void;
};
readonly setInitialValues: (values: Record<string, any>) => void;
readonly reset: (event?: FormEventLike) => void;
readonly submit: () => Promise<void>;
/**
* Restructures flat keys like "address.city" into nested objects like { address: { city: ... } }
* Values are already typed from onChange, so no conversion is needed.
*/
protected restructureValues(store: Record<string, any>): Record<string, any>;
/**
* Helper to restructure a flat key like "address.city" into nested object structure.
* The value is already typed, so we just assign it to the nested path.
*/
protected restructureNestedValue(values: Record<string, any>, key: string, value: any): void;
protected createProxyFromSchema<T extends TObject>(options: FormCtrlOptions<T>, schema: TSchema, context: {
parent: string;
store: Record<string, any>;
}): SchemaToInput<T>;
protected createInputFromSchema<T extends TObject>(name: keyof Static<T> & string, options: FormCtrlOptions<T>, schema: TObject, required: boolean, context: {
parent: string;
store: Record<string, any>;
}): BaseInputField;
/**
* Convert an input value to the correct type based on the schema.
* Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)
*/
protected getValueFromInput(input: any, schema: TSchema): any;
}
type SchemaToInput<T extends TObject> = { [K in keyof T["properties"]]: InputField<T["properties"][K]>; };
interface FormEventLike {
preventDefault?: () => void;
stopPropagation?: () => void;
}
type InputField<T extends TSchema> = T extends TObject ? ObjectInputField<T> : T extends TArray<infer U> ? ArrayInputField<U> : BaseInputField;
interface BaseInputField {
path: string;
required: boolean;
initialValue: any;
props: InputHTMLAttributesLike;
schema: TSchema;
set: (value: any) => void;
form: FormModel<any>;
items?: any;
}
interface ObjectInputField<T extends TObject> extends BaseInputField {
items: SchemaToInput<T>;
}
interface ArrayInputField<T extends TSchema> extends BaseInputField {
items: Array<InputField<T>>;
}
type InputHTMLAttributesLike = Pick<InputHTMLAttributes<unknown>, "id" | "name" | "type" | "value" | "required" | "maxLength" | "minLength" | "aria-label"> & {
value?: any;
};
type FormCtrlOptions<T extends TObject> = {
/**
* The schema defining the structure and validation rules for the form.
* This should be a TypeBox schema object.
*/
schema: T;
/**
* Callback function to handle form submission.
* This function will receive the parsed and validated form values.
*/
handler: (values: Static<T>) => unknown;
/**
* Optional initial values for the form fields.
* This can be used to pre-populate the form with existing data.
*/
initialValues?: Partial<Static<T>>;
/**
* Optional function to create custom field attributes.
* This can be used to add custom validation, styles, or other attributes.
*/
onCreateField?: (name: keyof Static<T> & string, schema: TSchema) => InputHTMLAttributes<unknown>;
/**
* If defined, this will generate a unique ID for each field, prefixed with this string.
*
* > "username" with id="form-123" will become "form-123-username".
*
* If omitted, IDs will not be generated.
*/
id?: string;
onError?: (error: Error) => void;
onChange?: (key: string, value: any, store: Record<string, any>) => void;
onReset?: () => void;
};
//#endregion
//#region ../../src/react/form/components/FormState.d.ts
declare const FormState: <T extends TObject>(props: {
form: FormModel<T>;
children: (state: {
loading: boolean;
dirty: boolean;
}) => ReactNode;
}) => ReactNode;
//#endregion
//#region ../../src/react/form/errors/FormValidationError.d.ts
declare class FormValidationError extends TypeBoxError {
readonly name = "ValidationError";
constructor(options: {
message: string;
path: string;
});
}
//#endregion
//#region ../../src/react/form/hooks/useFieldValue.d.ts
/**
* Hook to subscribe to a single form field's value.
* Only re-renders when this specific field changes.
*
* @returns A tuple of [value, setValue] similar to useState.
*/
declare const useFieldValue: (input: BaseInputField) => [any, (value: any) => void];
//#endregion
//#region ../../src/react/form/hooks/useForm.d.ts
declare const useForm: <T extends TObject>(options: FormCtrlOptions<T>, deps?: any[]) => FormModel<T>;
//#endregion
//#region ../../src/react/form/hooks/useFormQuerySync.d.ts
interface UseFormQuerySyncOptions<TKey extends string> {
/**
* Form field keys to mirror to/from the URL query. Fields not listed
* here stay local to the form and never leak into the address bar.
*/
keys: readonly TKey[];
}
/**
* Two-way bind a `useForm` instance to the URL query params, keyed
* per-field (so the URL stays human-readable: `?status=new&zone=ops`).
*
* Direction 1 — URL → form:
* - On mount AND every time one of the watched query keys changes
* (typically via browser back/forward or external `router.push`),
* call `form.input[key].set(value)` for each listed key. Missing /
* empty params clear the field (set to `undefined`).
*
* Direction 2 — form → URL:
* - Subscribe to the form's `form:change` event for the listed keys.
* Each emission writes the current values for all listed keys to the
* URL via `router.setQueryParams` (replace-state, no history spam).
* Empty values (`undefined` / `""`) are stripped from the URL so the
* address bar stays clean.
*
* Replaces ad-hoc `localStorage` filter persistence: the URL becomes
* the canonical store, so the view is shareable via copy-paste and
* navigable via back/forward.
*
* @example
* const form = useForm({ schema: z.object({ status: z.string(), q: z.string() }) });
* useFormQuerySync(form, { keys: ["status", "q"] });
*/
declare const useFormQuerySync: <T extends TObject, TKey extends string>(form: FormModel<T>, options: UseFormQuerySyncOptions<TKey>) => void;
//#endregion
//#region ../../src/react/form/hooks/useFormState.d.ts
interface UseFormStateReturn {
loading: boolean;
dirty: boolean;
values?: Record<string, any>;
error?: Error;
}
declare const useFormState: <T extends TObject, Keys extends keyof UseFormStateReturn>(target: FormModel<T> | {
form: FormModel<T>;
path: string;
}, _events?: Keys[]) => Pick<UseFormStateReturn, Keys>;
//#endregion
//#region ../../src/react/form/hooks/useFormValues.d.ts
/**
* Hook to subscribe to all form values.
* Re-renders on every field change — use only when needed (debug panels, live previews).
*/
declare const useFormValues: <T extends TObject>(form: FormModel<T>) => Record<string, any>;
//#endregion
//#region ../../src/react/form/services/parseField.d.ts
/**
* Semantic icon hint derived from schema metadata. UI layers map this
* to their own icon set — this module is headless and ships no JSX.
*/
type IconHint = "email" | "password" | "phone" | "url" | "number" | "calendar" | "clock" | "list" | "text" | "user" | "file" | "switch";
interface FieldConstraints {
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
pattern?: string;
}
interface FieldMeta {
id?: string;
label: string;
description?: string;
error?: string;
required: boolean;
type?: string;
format?: string;
isEnum: boolean;
isArray: boolean;
isObject: boolean;
isArrayOfObjects: boolean;
enum?: readonly unknown[];
iconHint?: IconHint;
constraints: FieldConstraints;
testId?: string;
schema: TSchema;
/**
* Raw `$control` value from the schema, untyped here. The UI layer
* (`alepha/react/ui`) provides the strict {@link SchemaControl} type and
* a `resolveSchemaControl` helper to evaluate the function form.
*/
control?: unknown;
}
interface ParseFieldOptions {
label?: string;
description?: string;
error?: Error;
}
/**
* Derives a {@link FieldMeta} from an `InputField` (from `useForm`) plus
* optional overrides. Pure — no React, no JSX, no UI library coupling.
*
* UI components consume this metadata to render labels, descriptions,
* error messages, icons, and validation constraints.
*/
declare const parseField: (input: BaseInputField, options?: ParseFieldOptions) => FieldMeta;
//#endregion
//#region ../../src/react/form/services/prettyName.d.ts
/**
* Converts a path or identifier string into a pretty display name.
* For paths like "/contacts/0/name", extracts just the field name "Name".
* Handles camelCase and snake_case conversion to Title Case.
*
* @example
* prettyName("/userName") // "User Name"
* prettyName("/contacts/0/email") // "Email"
* prettyName("/address/streetName") // "Street Name"
* prettyName("first_name") // "First Name"
*/
declare const prettyName: (name: string) => string;
//#endregion
//#region ../../src/react/form/index.d.ts
declare module "alepha" {
interface Hooks {
"form:change": {
id: string;
path: string;
value: any;
/**
* Programmatic reset (e.g. `setInitialValues` after the parent updates
* its state). Subscribers tracking dirty state should ignore these.
*/
initial?: boolean;
};
"form:submit:begin": {
id: string;
};
"form:submit:success": {
id: string;
values: Record<string, any>;
};
"form:submit:error": {
id: string;
error: Error;
};
"form:submit:end": {
id: string;
};
"form:reset": {
id: string;
};
}
}
/**
* Type-safe forms with validation.
*
* **Features:**
* - Form state management
* - TypeBox schema validation
* - Field-level error handling
* - Submit handling with loading state
* - Form reset
*
* @module alepha.react.form
*/
declare const AlephaReactForm: import("alepha").Service<import("alepha").Module>;
//#endregion
export { AlephaReactForm, ArrayInputField, BaseInputField, FieldConstraints, FieldMeta, FormCtrlOptions, FormEventLike, FormModel, FormState, FormValidationError, IconHint, InputField, InputHTMLAttributesLike, ObjectInputField, ParseFieldOptions, SchemaToInput, UseFormQuerySyncOptions, UseFormStateReturn, parseField, prettyName, useFieldValue, useForm, useFormQuerySync, useFormState, useFormValues };
//# sourceMappingURL=index.d.ts.map