UNPKG

react-formal

Version:

Classy HTML form management for React

242 lines (241 loc) 8.12 kB
import React from 'react'; import { FieldMeta, MapFromValue, MapToValue, UseFieldProps, ValidateOnConfig } from './useField'; export type FieldEventHandlers = { onBlur?: (...args: any[]) => any; onChange?: (...args: any[]) => any; }; export declare function useMergedEventHandlers({ onBlur: onFieldBlur, onChange: onFieldChange }: FieldEventHandlers, { onBlur, onChange }: FieldEventHandlers): { onChange: (...args: any[]) => void; onBlur: (...args: any[]) => void; }; export type FieldRenderProps<TValue = any> = UseFieldProps<TValue> & { type: string; ref?: React.Ref<any>; }; /** * When Field renders an Element, it injects a few props. * In the case none DOM elements it also injects `meta` */ export type FieldInjectedProps<TValue = any> = FieldRenderProps<TValue> & { meta?: FieldMeta; }; export type FieldProps<TAs extends React.ElementType = any> = { /** * The Component Input the form should render. You can sepcify a native element such as 'input' or 'select' * or provide a Component type class directly. When no type is provided the Field will attempt determine * the correct input from the Field's schema. A Field corresponding to a `yup.number()` * will render a `type='number'` etc. * * ```jsx * import Form from 'react-formal'; * * function MyDateInput({ meta: _, ...props }) { * return <input {...props} type='datetime-local' /> * } * * <Form> * Provide a type directly * <Form.Field * name='dateOfBirth' * type='time' * placeholder='time only' * /> * * Use a custom Component * (need native 'datetime' support to see it) * <Form.Field * name='dateOfBirth' * as={MyDateInput} * /> * * </Form> * ``` * * Custom Inputs should comply with the basic input api contract: set a value via a `value` prop and * broadcast changes to that value via an `onChange` handler. */ as?: TAs; /** * The Field name, which should be path corresponding to a specific form `value` path. * * ```jsx static * // given the form value * value = { * name: { first: '' } * languages: ['english', 'spanish'] * } * * // the path "name.first" would update the "first" property of the form value * <Form.Field name='name.first' /> * * // use indexes for paths that cross arrays * <Form.Field name='languages[0]' /> * * ``` */ name: string | string; /** * Configure whether validation occur: onChange, onBlur, or both * You can also specify a function that receives the Field `meta` object and returns a configuration map * in order to change validation strategies based on validity or other metadata. */ validateOn?: ValidateOnConfig; /** * Customize how the Field value maps to the overall Form `value`. * `mapFromValue` can be a a string property name or a function that returns a * value for `name`'d path, allowing you to set commuted values from the Field. * * ```jsx static * <Form.Field * name='name' * mapFromValue={fieldValue => `${fieldValue.first} ${fieldValue.last}`} * /> * ``` * * You can also provide an object hash, mapping paths of the Form `value` * to fields in the field value using a string field name, or a function accessor. * * ```jsx * import Form from '@docs/components/FormWithResult'; * import * as yup from 'yup'; * * const getYear = () => (new Date()).getFullYear() * * const schema = yup.object({ * dateOfBirth: yup.date().required('Required'), * age: yup.number() * }); * * <Form * schema={schema} * defaultValue={schema.default()} * > * <label> * Date of Birth * <Form.Field * name='dateOfBirth' * mapFromValue={{ * 'dateOfBirth': event => event.target.value, * 'age': ({ target }) => target.valueAsDate ? * getYear() - target.valueAsDate.getFullYear() : null * }}/> * </label> * <label> * Age * <Form.Field name='age' /> * </label> * * <Form.Submit type='submit'>Submit</Form.Submit> * </Form> * ``` */ mapFromValue?: MapFromValue; /** * Map the Form value to the Field value. By default * the `name` of the Field is used to extract the relevant * property from the Form value. * * ```jsx static * <Form.Field * name='location' * type="dropdownlist" * mapToValue={model=> pick(model, 'location', 'locationId')} * /> * ``` */ mapToValue?: MapToValue; /** * The css class added to the Field Input when it fails validation */ errorClass?: string; /** * Tells the Field to trigger validation for specific paths. * Useful when used in conjuction with a `mapFromValue` hash that updates more than one value, or * if you want to trigger validation for the parent path as well. * * > NOTE! This overrides the default behavior of validating the field itself by `name`, * include the `name` if you want the field to validate itself. * * ```jsx static * <Form.Field name='name.first' validates="name.last" /> * <Form.Field name='name' validates={['name', 'surname']} /> * ``` */ validates?: string | string[]; /** * Indicates whether child paths of the current Field * affect the active state of the field. * * ``` * 'names' * - 'first' * - 'last' * ``` * * Are all considered "part" of a field named `'names'` by default. Does not * affect which paths are validated, only whether `meta.valid` considers child * paths for its state. */ exclusive?: boolean; /** * Disables validation for the Field. */ noValidate?: boolean; /** * When children is the traditional react element or nodes, they are * passed through as-is to the Field `type` component. * * ```jsx static * <Field type='select'> * <option>red</option> * <option>red</option> * </Field> * ``` * * When `children` is a function, its called with the processed field * props and field meta. * * **Tip:** you can pass `onChange` and `onBlur` handlers * to the `<Field>` component and it will handle merging them with its own injected * handlers. * * ```jsx static * <Field name='birthDate'> * {(props, meta) => * <DataProvider> * <Input {...props} /> * </DataProvider> * } * </Field> * ``` */ children?: React.ReactNode | ((fieldProps: FieldRenderProps, meta: FieldMeta) => React.ReactNode); /** * A value to pass to checkboxs/radios/boolean inputs */ value?: any; className?: string; /** * Instruct the field to not inject the `meta` prop into the input, * defaults to `true` when `as` is a non DOM component */ injectMeta?: boolean; /** An HTML input type attribute */ type?: string; /** A local onChange handler, will be merged with the injected onChange */ onChange?: (...args: any[]) => any; /** A local onBlur handler, will be merged with the injected onBlur */ onBlur?: (...args: any[]) => any; }; export type FieldPropsWithAs<TAs extends React.ElementType> = FieldProps<TAs> & React.RefAttributes<any> & Omit<React.ComponentPropsWithoutRef<TAs>, keyof FieldProps | 'meta' | 'name' | 'value' | 'checked' | 'onChange' | 'onBlur'>; export declare interface Field<TDefaultControl extends React.ElementType = 'input'> { <TAs extends React.ElementType = TDefaultControl>(props: FieldPropsWithAs<TAs>): React.ReactElement | null; displayName?: string; propTypes?: any; } /** * @alias Field * @memberof Form */ declare const _Field: Field; export default _Field;