UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

316 lines 11.7 kB
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