import * as React from "react"; import { useFormex } from "./Formex"; import { getIn, isFunction, isObject } from "./utils"; import { FormexController } from "./types"; export interface FieldInputProps { /** Value of the field */ value: Value; /** Name of the field */ name: string; /** Multiple select? */ multiple?: boolean; /** Is the field checked? */ checked?: boolean; /** Change event handler */ onChange: (event: React.SyntheticEvent) => void, /** Blur event handler */ onBlur: (event: React.FocusEvent) => void, } export interface FormexFieldProps { field: FieldInputProps; form: FormexController; } export interface FieldConfig { /** * Component to render. Can either be a string e.g. 'select', 'input', or 'textarea', or a component. */ as?: | C | string | React.ForwardRefExoticComponent>; /** * Children render function {props => ...}) */ children?: ((props: FormexFieldProps) => React.ReactNode) | React.ReactNode; /** * Validate a single field value independently */ // validate?: FieldValidator; /** * Used for 'select' and related input types. */ multiple?: boolean; /** * Field name */ name: string; /** HTML input type */ type?: string; /** Field value */ value?: unknown; /** Inner ref */ innerRef?: (instance: unknown) => void; } export type FieldProps = { as?: C; } & (C extends React.ElementType ? (React.ComponentProps & FieldConfig) : FieldConfig); export function Field({ validate, name, children, as: is, // `as` is reserved in typescript lol // component, className, ...props }: FieldProps) { const formex = useFormex(); const field = getFieldProps({ name, ...props }, formex); if (isFunction(children)) { return children({ field, form: formex }); } // if (component) { // if (typeof component === "string") { // const { innerRef, ...rest } = props; // return React.createElement( // component, // { ref: innerRef, ...field, ...rest, className }, // children // ); // } // return React.createElement( // component, // { field, form: formex, ...props, className }, // children // ); // } // default to input here so we can check for both `as` and `children` above const asElement = is || "input"; if (typeof asElement === "string") { const { innerRef, ...rest } = props; return React.createElement( asElement, { ref: innerRef, ...field, ...rest, className }, children ); } return React.createElement(asElement, { ...field, ...props, className }, children); } const getFieldProps = (nameOrOptions: string | FieldConfig, formex: FormexController): FieldInputProps => { const name: string = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name; const valueState = getIn(formex.values as Record, name); const field: FieldInputProps = { name: name as string, value: valueState, onChange: formex.handleChange, onBlur: formex.handleBlur }; if (typeof nameOrOptions !== "string") { const { type, value: valueProp, // value is special for checkboxes as: is, multiple } = nameOrOptions as FieldConfig; if (type === "checkbox") { if (valueProp === undefined) { field.checked = !!valueState; } else { field.checked = !!( Array.isArray(valueState) && ~valueState.indexOf(valueProp) ); field.value = valueProp; } } else if (type === "radio") { field.checked = valueState === valueProp; field.value = valueProp; } else if (is === "select" && multiple) { field.value = field.value || []; field.multiple = true; } } return field; };