type Maybe = T | null | undefined; type KeyOf, K = keyof T> = K extends string ? (T[K] extends Function ? never : K) : never; type DotField>, K = KeyOf>> = K extends string ? K | `${K}.${DotField[K]>}` : never; type ValueOf, K> = K extends `${infer I}.${infer R}` ? ValueOf[I], R> : K extends string ? NonNullable[K] : never; export type FormProps = { value: T; onChange: (value: T) => void; }; const pick = , K extends keyof T>(obj: T, keys: K[]): Pick => { const ret = {} as Pick; keys.forEach((key) => { ret[key] = obj[key]; }); return ret; }; export const mapFormProps = ( formProps: FormProps, { mapValue, mapOnChange }: { mapValue: (v: T) => U; mapOnChange: (v: U) => T; }, ): FormProps => { const { value, onChange } = formProps; return { value: mapValue(value), onChange: (value: U) => onChange(mapOnChange(value)), }; }; export const useSubField = >({ value, onChange, }: { value: T; onChange?: (cb: (value: T) => T) => void; }) => { const subField = >(key: K): FormProps> => { const v = key.split('.').reduce((o, x) => (o ?? {})[x], value) as ValueOf; return { value: v, onChange: (v: ValueOf) => { const setInner = >(o: O, k: string[], v: any): O => { const [head, ...tail] = k; let out; if (tail.length === 0) { out = { ...o, [head]: v, }; } else { out = { ...o, [head]: setInner(o[head], tail, v), }; } return out; }; onChange?.((old) => setInner(old, key.split('.'), v)); }, }; }; const subKeys = (keys: K[]) => { return { value: pick(value, keys), onChange: (v: Pick) => { onChange?.((old) => ({ ...old, v, })); }, }; }; return { subField, subKeys, }; };