import { Effect, Either, ParseResult, Predicate, Schema } from "effect" import type React from "react" import type { FieldPath } from "react-hook-form" import type * as FormBody from "./FormBody.js" import type { Path } from "./Path.js" type Values = | { encoded: S["Encoded"] | ((from: S["Encoded"]) => S["Encoded"]) } | { unknown: unknown } const getValues = (schema: S) => ({ defaultValues, values }: { values: Values> | undefined defaultValues: NoInfer["Encoded"] }): S["Encoded"] => { if (!values) return defaultValues if ("encoded" in values) { if (Predicate.isFunction(values.encoded)) { return values.encoded(defaultValues) } else { return values.encoded } } else { const encodedSchema = Schema.encodedSchema(schema) const either = Schema.decodeUnknownEither(encodedSchema, { errors: "all" })(values.unknown) if (Either.isRight(either)) { return either.right } else { // TODO: use effect log console.log( "[warning] Provided values are not valid. Falling back on default values", ParseResult.ArrayFormatter.formatErrorSync(either.left) ) return defaultValues } } } export interface FormComponentProps { children: React.ReactNode onSubmit: (_: { decoded: S["Type"] encoded: S["Encoded"] }) => void | Promise onError?: (values: unknown) => void /** * The validation mode. Default: 'onBlur' */ validationMode?: "onBlur" | "onSubmit" | "onChange" /** * The values to hydrate the form with, typically loading some values already saved by the user */ initialValues?: Values /** * The starting point of an empty form */ resetValues?: Values } export interface FormComponent extends React.FC> { /** * The form id to synchronize with Submit button */ id: string } export type ReactFCWithChildren = React.FC<{ children: React.ReactNode }> export interface FieldControls< Field extends FormBody.AnyField = FormBody.AnyField > { watch: () => Field["schema"]["Encoded"] reset: () => void set: (value: Field["schema"]["Encoded"]) => void } export interface ArrayControls< Field extends FormBody.AnyArray = FormBody.AnyArray > extends FieldControls { append: (value?: Field["field"]["schema"]["Encoded"]) => void useRemove: () => { remove: () => void } } export interface MakeIterable< Field extends FormBody.AnyIterable = FormBody.AnyIterable > extends ReactFCWithChildren { Fields: ReactFCWithChildren useControls: () => Field extends FormBody.AnyArray ? ArrayControls : FieldControls } export interface RawControls extends FieldControls { usePath: >( path: T ) => string } export interface MakeRaw extends ReactFCWithChildren { useControls: () => RawControls } export interface MakeMapKey { Key: React.FC useKey: () => S["Encoded"] } export type Button = React.FC< { type?: "submit" | "reset" | "button" form?: string variant?: string loading?: boolean children?: React.ReactNode onClick?: React.MouseEventHandler } > export interface IFormFramework { registerUncontrolled: (component: React.FC, path: Path) => React.FC registerControlled: (component: React.FC, path: Path) => React.FC makeFieldControls: (path: Path) => { useControls: () => FieldControls } makeIterable: ( defaultValue: unknown, path: Path ) => MakeIterable makeMapKey: ( schema: S, path: Path ) => MakeMapKey makeRaw: (path: Path) => MakeRaw makeForm: (_: { schema: S resetValues: S["Encoded"] }) => FormComponent makeSubmit: (formId: string) => Button useError: (path: Path) => T Clear: Button } export class FormFramework extends Effect.Tag("@inato/Form/FormFramework")< FormFramework, IFormFramework >() { static getValues = getValues }