react-jsonschema-form-validation
Version:
Simple form validation using JSON Schema and AJV
253 lines (249 loc) • 11.6 kB
TypeScript
import Ajv from 'ajv';
import { JSONSchema7Definition } from 'json-schema';
import React$1 from 'react';
/**
* An AJV `ErrorObject` enriched with a `field` property — a normalized,
* dot-separated path to the offending field (e.g. `'user.email'` or
* `'items.0.label'`). Used everywhere internally to locate the input
* associated with an error.
*/
export type FormattedError = Ajv.ErrorObject & {
field: string;
};
/**
* Minimal shape required by the library on an input target. Structurally
* compatible with `HTMLInputElement`, `HTMLTextAreaElement`, `HTMLSelectElement`
* and any custom object that mimics them (e.g. when an integration like
* `react-select` synthesizes its own events). Only the properties actually
* read by the library appear here.
*/
export type FormInputTarget = {
name: string;
value: string;
type?: string;
checked?: boolean;
files?: FileList | null;
multiple?: boolean;
};
/**
* Lightweight change event accepted by the form. Any DOM `ChangeEvent`
* or `InputEvent` is structurally compatible with this shape, so users can
* forward native React events without casting.
*/
export type FormChangeEvent = {
target: FormInputTarget;
};
/**
* Internal `Omit` variant that keeps typed keys strict even when `T` has an
* index signature (like `[key: string]: any` — reactstrap and a few other
* libraries expose these). Standard `Omit<T, K>` on such a type collapses
* the typed keys back into `any` through the index, which effectively kills
* the polymorphic type-checking on `<Field component={X} .../>` etc.
*
* This variant:
* 1. Strips the index signature so `Omit` can operate on named keys only,
* keeping their precise types.
* 2. Only re-attaches the original index signature (with its original
* value type) *if T actually had one to begin with*. The `string
* extends keyof T` guard prevents accidentally re-injecting an index
* signature on plain "record-like" types whose values happen to share
* a common supertype (e.g. `{ label: string; flavor: 'a' | 'b' }`
* structurally extends `{ [k: string]: string }` but does not really
* accept arbitrary keys).
*/
export type SafePropsOmit<T, K extends PropertyKey> = (Omit<{
[P in keyof T as string extends P ? never : P]: T[P];
}, K> & (string extends keyof T ? (T extends {
[k: string]: infer V;
} ? {
[k: string]: V;
} : {}) : {}));
/**
* Built-in AJV validation keywords. Listed here so consumers writing
* `errorMessages` get IDE autocomplete on the well-known keys while still
* being free to add custom keywords (any other string is accepted via the
* `(string & {})` intersection in `ErrorMessagesMap`).
*/
export type AjvKeyword = ("type" | "required" | "enum" | "const" | "minimum" | "maximum" | "exclusiveMinimum" | "exclusiveMaximum" | "multipleOf" | "minLength" | "maxLength" | "pattern" | "format" | "minItems" | "maxItems" | "uniqueItems" | "minProperties" | "maxProperties" | "additionalProperties" | "dependencies" | "patternProperties" | "properties" | "oneOf" | "anyOf" | "allOf" | "not" | "if" | "then" | "else");
/**
* Builds a single error message string from an AJV error.
*/
export type ErrorMessageFn = (error: FormattedError) => string;
/**
* Map of error messages. `defaultMessage` is the catch-all used by
* `<FieldError>` when no entry matches the error's keyword. The known
* keywords (see {@link AjvKeyword}) are typed for autocomplete; any other
* string key (e.g. a custom AJV keyword) is still allowed.
*
* The `(string & {})` in the key union is the standard TypeScript trick to
* accept arbitrary strings *without* collapsing the literal union — plain
* `string` would erase the autocomplete on the known keywords.
*/
export type ErrorMessagesMap = Partial<Record<AjvKeyword | "defaultMessage" | (string & {}), ErrorMessageFn>>;
/**
* The complete value of the form context, supplied by `<Form>` to all its
* descendants. The actual context type is `FormContextValue | undefined`
* (it is `undefined` for any consumer rendered outside a `<Form>`), but
* `useFormContext()` and `withFormContext()` perform the runtime check
* so downstream code receives a guaranteed non-`undefined` value.
*/
export type FormContextValue = {
errors: FormattedError[];
isSubmitted: boolean;
touchedFields: string[];
valid: boolean;
errorMessages?: ErrorMessagesMap;
getFieldErrors: (names: string | string[]) => FormattedError[];
handleFieldChange: (event: FormChangeEvent | string, value?: unknown) => void;
isFieldTouched: (names: string | string[]) => boolean;
isFieldInvalid: (names: string | string[]) => boolean;
isTouched: () => boolean;
touch: (names: string | string[]) => void;
};
declare const _default: <C extends React$1.ElementType = "input">(props: FieldProps<C> & {
ref?: React$1.ComponentPropsWithRef<C>["ref"];
}) => JSX.Element | null;
/**
* Signature of the user-supplied `onChange` handler. It receives the event
* and the form's internal handler so the user can decide whether to apply,
* transform or skip the update.
*/
export type FieldChangeHandler = (event: FormChangeEvent, formHandleFieldChange: FormContextValue["handleFieldChange"]) => void;
/**
* Base props of `<Field>` — the validation-specific props handled by the
* component itself. The public, polymorphic `FieldProps<C>` extends this
* with the props of the underlying component `C`.
*
* `forwardedRef` is an implementation detail (injected by the outer
* `React.forwardRef` wrapper) and is omitted from the public polymorphic
* type below.
*/
export type FieldBaseProps = {
name: string;
children?: React$1.ReactNode;
className?: string;
component?: React$1.ElementType;
forwardedRef?: React$1.Ref<unknown> | null;
onBlur?: ((event: React$1.FocusEvent) => void) | null;
onChange?: FieldChangeHandler | null;
};
/**
* Polymorphic props of `<Field>`. When `component={X}` is supplied, every
* prop accepted by `X` is also accepted here (with autocomplete and typo
* detection). The default `C = 'input'` matches the runtime default.
*
* - `name` — path within the form data this field reads/writes.
* - `component` — host element or component (default `'input'`).
* - `onChange` — user override; receives the raw event plus the form's
* internal change handler so the user can decide whether
* to apply, transform or skip the update.
* - `onBlur` — user override; always fires *after* `form.touch(name)`.
*/
export type FieldProps<C extends React$1.ElementType = "input"> = (Omit<FieldBaseProps, "component" | "forwardedRef"> & {
component?: C;
} & SafePropsOmit<React$1.ComponentProps<C>, keyof FieldBaseProps | "ref">);
declare const _default$1: <C extends React$1.ElementType = "div">(props: FieldErrorProps<C>) => JSX.Element | null;
/**
* Base props of `<FieldError>` — the validation-specific props handled by
* the component itself. The public, polymorphic `FieldErrorProps<C>` extends
* this with the props of the underlying component `C`.
*/
export type FieldErrorBaseProps = {
children?: React$1.ReactNode;
className?: string;
component?: React$1.ElementType;
errorMessages?: ErrorMessagesMap | null;
name: string;
};
/**
* Polymorphic props of `<FieldError>`. When `component={X}` is supplied,
* every prop accepted by `X` is also accepted here (with autocomplete and
* typo detection). The default `C = 'div'` matches the runtime default.
*
* - `name` — path of the form field whose first error should be shown.
* - `errorMessages` — optional per-field overrides; they take priority over the
* map declared at the `<Form>` level (see `ErrorMessagesMap`).
* - `component` — element rendered when an error is present (default `'div'`).
* - `children` — replaces the auto-generated message when provided.
*/
export type FieldErrorProps<C extends React$1.ElementType = "div"> = (Omit<FieldErrorBaseProps, "component"> & {
component?: C;
} & SafePropsOmit<React$1.ComponentProps<C>, keyof FieldErrorBaseProps | "ref">);
declare const _default$2: <T = Record<string, unknown>, C extends React$1.ElementType = "form">(props: FormProps<T, C>) => JSX.Element | null;
/**
* Options controlling how the form scrolls to the first invalid field on
* a failed submit.
*/
export type JfvScrollOptions = {
offset?: number;
align?: "top" | "middle" | "bottom" | (string & {});
duration?: number;
ease?: string;
};
/**
* Base props of `<Form>` — the validation-specific props the component
* handles itself. The public, polymorphic `FormProps<T, C>` extends this
* with the props of the underlying component `C` and typed data `T`.
*/
export type FormBaseProps = {
ajv?: Ajv.Ajv;
children?: React$1.ReactNode;
className?: string;
component?: React$1.ElementType;
data?: Record<string, unknown>;
throttleDuration?: number;
errorMessages?: ErrorMessagesMap;
onChange?: ((data: Record<string, unknown>, event?: FormChangeEvent) => void) | null;
onSubmit: (event: React$1.FormEvent) => void;
schema: JSONSchema7Definition;
scrollToError?: boolean;
scrollOptions?: JfvScrollOptions;
};
/**
* Polymorphic props of `<Form>`. Two type parameters:
* - `T` — shape of the form data. Inferred from `data` / `onChange`, or
* you can pass it explicitly via `<Form<UserData> …>`. Default
* `Record<string, unknown>`. Note that `T` is a *promise* by the
* caller: nothing at compile time ensures the JSON Schema actually
* validates `T`.
* - `C` — element type used for the form wrapper (the `component` prop).
* Default `'form'`. When supplied, every prop accepted by `C` is
* also accepted on `<Form>` (autocomplete + typo detection).
*
* Field reference:
* - `schema` — JSON-Schema used to validate `data`.
* - `data` — current form values, fully controlled by the parent.
* - `onChange` — called with the updated data on every field change.
* - `onSubmit` — called only when validation passes at submit time.
* - `errorMessages` — map of error messages shared by every `<FieldError>`
* descendant (see `ErrorMessagesMap`).
* - `ajv` — optional pre-configured AJV instance, useful to plug
* in custom keywords/formats.
* - `component` — element rendered for the form wrapper (default `<form noValidate>`).
*/
export type FormProps<T = Record<string, unknown>, C extends React$1.ElementType = "form"> = (Omit<FormBaseProps, "component" | "data" | "onChange"> & {
component?: C;
data?: T;
onChange?: ((data: T, event?: FormChangeEvent) => void) | null;
} & SafePropsOmit<React$1.ComponentProps<C>, keyof FormBaseProps | "ref">);
export function useFormContext(): FormContextValue;
export function withFormContext<T>(cb: (ctx: FormContextValue) => T): React$1.ReactElement;
/**
* @import { ReactElement } from 'react'
* @import { FormContextValue } from './Context.types'
*/
/**
* React context that carries the `<Form>` state down to descendant
* `<Field>` and `<FieldError>` components. Not intended for direct use —
* consume it through `useFormContext()` (or the legacy `withFormContext()`
* render-prop helper), which handles the "outside a Form" case with a
* descriptive error instead of returning `undefined`.
*/
export declare const FormContext: React$1.Context<FormContextValue | undefined>;
export {
_default as Field,
_default$1 as FieldError,
_default$2 as Form,
_default$2 as default,
};
export {};