import { TargetedEvent, useContext, useState } from "preact/compat"; import { FormContext, InputFieldState } from "./FormProvider.js"; export interface InputFieldHandler { value: Type; onChange: (s: Type) => void; state: InputFieldState; isDirty: boolean; } export function useField(name: keyof T): InputFieldHandler { const { initialValue, value: formValue, computeFormState, onUpdate: notifyUpdate, } = useContext(FormContext); type P = typeof name; type V = T[P]; const formState = computeFormState ? computeFormState(formValue.current) : {}; const fieldValue = readField(formValue.current, String(name)) as V; const [currentValue, setCurrentValue] = useState(undefined); const fieldState = readField>( formState, String(name), ); //default state const state: InputFieldState = { disabled: fieldState?.disabled ?? false, readonly: fieldState?.readonly ?? false, hidden: fieldState?.hidden ?? false, error: fieldState?.error, }; function onChange(value: V): void { setCurrentValue(value); formValue.current = setValueDeeper( formValue.current, String(name).split("."), value, ); if (notifyUpdate) { notifyUpdate(formValue.current); } } return { value: fieldValue, onChange, isDirty: currentValue !== undefined, state, }; } /** * read the field of an object an support accessing it using '.' * * @param object * @param name * @returns */ function readField(object: any, name: string): T | undefined { return name .split(".") .reduce((prev, current) => prev && prev[current], object); } function setValueDeeper(object: any, names: string[], value: any): any { if (names.length === 0) return value; const [head, ...rest] = names; if (object === undefined) { return { [head]: setValueDeeper({}, rest, value) }; } return { ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) }; }