@canard/schema-form
Version:
React-based component library that renders forms based on JSON Schema with plugin system support for validators and UI components
185 lines (184 loc) • 5.44 kB
TypeScript
import type { SchemaFormPlugin } from '../..';
/**
* Registers a schema form plugin to extend or customize form functionality globally.
*
* Provides a centralized way to register custom components, validators, and formatters
* that will be used throughout the application. Plugins are deduplicated by content hash
* to prevent duplicate registrations.
*
* @param plugin - Plugin configuration object or null to reset all plugins to defaults
* @throws {UnhandledError} When plugin registration fails
*
* @example
* Basic plugin registration with custom components:
* ```typescript
* import { registerPlugin } from '@canard/schema-form';
*
* registerPlugin({
* FormGroup: CustomFormGroup,
* FormLabel: CustomFormLabel,
* FormInput: CustomFormInput,
* FormError: CustomFormError,
* });
* ```
*
* @example
* Register custom form type definitions:
* ```typescript
* const datePickerDefinition = {
* test: { type: 'string', format: 'date' },
* Component: DatePickerInput,
* };
*
* const colorPickerDefinition = {
* test: { type: 'string', format: 'color' },
* Component: ColorPickerInput,
* };
*
* registerPlugin({
* formTypeInputDefinitions: [
* datePickerDefinition,
* colorPickerDefinition,
* ],
* });
* ```
*
* @example
* Register custom validator with Ajv:
* ```typescript
* import Ajv from 'ajv';
* import addFormats from 'ajv-formats';
*
* const ajv = new Ajv({ allErrors: true });
* addFormats(ajv);
*
* registerPlugin({
* validator: {
* bind: (instance) => {
* // Access validator instance if needed
* console.log('Validator bound:', instance);
* },
* compile: (jsonSchema) => {
* const validate = ajv.compile(jsonSchema);
* return (value) => {
* validate(value);
* return validate.errors?.map(err => ({
* dataPath: err.instancePath,
* keyword: err.keyword,
* message: err.message,
* details: err.params,
* source: err,
* })) || [];
* };
* },
* },
* });
* ```
*
* @example
* Register custom error formatter:
* ```typescript
* registerPlugin({
* formatError: (error, node, context) => {
* // Custom error formatting logic
* if (error.keyword === 'required') {
* return `${node.name} is required`;
* }
* if (error.keyword === 'minLength') {
* return `${node.name} must be at least ${error.details?.limit} characters`;
* }
* return error.message || 'Invalid value';
* },
* });
* ```
*
* @example
* Complete plugin example with all options:
* ```typescript
* const myFormPlugin: SchemaFormPlugin = {
* // Custom renderer components
* FormGroup: MyFormGroup,
* FormLabel: MyFormLabel,
* FormInput: MyFormInput,
* FormError: MyFormError,
*
* // Custom input types
* formTypeInputDefinitions: [
* {
* test: { type: 'string', format: 'phone' },
* Component: PhoneNumberInput,
* },
* {
* test: (hint) => hint.jsonSchema.customType === 'address',
* Component: AddressInput,
* },
* ],
*
* // Custom validator
* validator: {
* bind: (instance) => console.log('Validator ready'),
* compile: (jsonSchema) => {
* const validate = customValidate(jsonSchema);
* return async (value) => {
* const errors = await validate(value);
* return errors;
* };
* },
* },
*
* // Custom error formatter
* formatError: (error, node) => {
* const fieldName = node.jsonSchema.title || node.name;
* return `${fieldName}: ${error.message}`;
* },
* };
*
* registerPlugin(myFormPlugin);
* ```
*
* @example
* Multiple plugins with merge behavior:
* ```typescript
* // First plugin
* registerPlugin({
* FormLabel: CustomLabel1,
* formTypeInputDefinitions: [
* { test: { format: 'date' }, Component: DatePicker1 }
* ],
* });
*
* // Second plugin - behavior differs by property:
* registerPlugin({
* FormLabel: CustomLabel2, // REPLACES CustomLabel1
* FormInput: CustomInput, // ADDS to render kit
* formTypeInputDefinitions: [
* { test: { format: 'date' }, Component: DatePicker2 }, // PREPENDED (takes precedence)
* { test: { format: 'time' }, Component: TimePicker } // ADDED
* ],
* validator: myValidator, // REPLACES any previous validator
* formatError: myFormatter, // REPLACES any previous formatter
* });
* ```
*
* @example
* Reset all plugins to defaults:
* ```typescript
* // Remove ALL custom plugins and restore defaults
* registerPlugin(null);
* ```
*
* @remarks
* ### Plugin Merge Behavior
* When multiple plugins are registered:
* - **Render components** (FormGroup, FormLabel, FormInput, FormError): Last one wins (replacement)
* - **formTypeInputDefinitions**: Prepended to list (first match wins), allowing overrides
* - **validator**: Last one wins (complete replacement)
* - **formatError**: Last one wins (complete replacement)
*
* ### Important Notes
* - Plugins are applied globally and affect all forms in the application
* - Plugins are deduplicated by content hash to prevent duplicate registrations
* - Use `null` parameter to reset ALL plugins to system defaults
* - After reset, previously registered plugins need to be re-registered
*/
export declare const registerPlugin: (plugin: SchemaFormPlugin | null) => void;