import { 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: K, ): 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; // console.log("USE FIELD", String(name), formValue.current, fieldValue); const [currentValue, setCurrentValue] = useState(fieldValue); const fieldState = readField>(formState, String(name)) ?? {}; //compute default state const state = { disabled: fieldState.disabled ?? false, readonly: fieldState.readonly ?? false, hidden: fieldState.hidden ?? false, error: fieldState.error, elements: "elements" in fieldState ? fieldState.elements ?? [] : [], }; 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, debug?: boolean, ): T | undefined { return name.split(".").reduce((prev, current) => { if (debug) { console.log( "READ", name, prev, current, prev ? prev[current] : undefined, ); } return prev ? prev[current] : undefined; }, 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) }; }