UNPKG

extended-dynamic-forms

Version:

Extended React JSON Schema Form (RJSF) v6 with custom components, widgets, templates, layouts, and form events

543 lines (449 loc) 12.6 kB
# Extended Dynamic Forms ## BREAKING CHANGE: v0.2.0 All conditional logic now uses **JsonLogic rules only** for conditions. Function-based conditions have been removed for security and portability. Note that validation effects may still use functions. See migration examples below. ## Project Status **Important Notes:** - Requires RJSF source in sibling directory for development - Cannot be installed from npm due to local file dependencies - JSON logic conditions don't work with vanilla JavaScript builds - Array drag-and-drop is not implemented - Limited test coverage for UI components and event system - JSON Patch implementation supports add/remove/replace only (not move/copy/test) ## Overview Extended Dynamic Forms is an extension library for React JSON Schema Form (RJSF) v6 that adds: - **Three Pillars Architecture** for conditional logic with O(N) performance (where N is the number of conditional rules/array items) - **Multi-step wizard forms** with automatic step detection - **Central event orchestration** with webhook support - **25+ Ant Design components** for professional UI - **TypeScript-first** development with full type safety ## Installation **Note:** This library currently requires local development setup and cannot be installed via npm due to local file dependencies. ```bash # Clone RJSF as sibling directory (required) cd .. git clone https://github.com/rjsf-team/react-jsonschema-form.git # Setup Extended Dynamic Forms cd extended-dynamic-forms npm install npm run dev ``` **Important:** The library is currently designed for internal use or as part of a monorepo setup. Standalone npm package distribution is not yet supported. ## Core Architecture ### Three Pillars Conditional System The conditional logic system is divided into three independent pillars: #### 1. UI Logic Pillar Controls form appearance using JsonLogic conditions and JSON Patch operations: ```typescript import { useConditionalUi, UiRule } from 'extended-dynamic-forms/conditionals/v2'; const uiRules: UiRule[] = [{ name: 'Show license number when has license', condition: { "==": [{ "var": "hasDriversLicense" }, true] }, effect: [{ op: 'remove', path: '/licenseNumber/ui:widget' }] }]; const conditionalUiSchema = useConditionalUi(uiRules, baseUiSchema, formData); ``` #### 2. Schema Logic Pillar Modifies JSON Schema structure dynamically (use sparingly): ```typescript const schemaRules: SchemaRule[] = [{ name: 'Add premium fields', condition: { "==": [{ "var": "accountType" }, "premium"] }, effect: [{ op: 'add', path: '/properties/premiumFeatures', value: {...} }] }]; ``` #### 3. Validation Logic Pillar Adds conditional validation rules: ```typescript const validationRules: ValidationRule[] = [{ name: 'Require consent for minors', condition: { "<": [{ "var": "age" }, 18] }, effect: { name: 'Parent consent validation', isActive: { "<": [{ "var": "age" }, 18] }, validate: (errors, formData) => { if (!formData.parentConsent) { errors.parentConsent.__errors = ['Parent consent required']; } } } }]; ``` ### Dynamic Arrays with O(N) Performance Provides efficient handling of conditional logic within arrays, where N is the number of array items: ```typescript import { createDynamicArrayItemsUiSchema } from 'extended-dynamic-forms/conditionals/v2'; const uiSchema = { directors: { items: createDynamicArrayItemsUiSchema((director, index) => ({ shareholdingPercentage: { 'ui:widget': director.hasShareholding ? 'percentage' : 'hidden' } })) } }; ``` ## Multi-Step Wizard Forms Create wizard forms with automatic step detection: ```typescript import { WizardForm } from 'extended-dynamic-forms'; <WizardForm schema={wizardSchema} uiSchema={conditionalUiSchema} formData={formData} onStepChange={(fromStep, toStep) => { console.log(`Step ${fromStep} → ${toStep}`); }} /> ``` **Important:** Wizard forms flatten nested step data. Use flat paths in conditions: ```typescript // Schema uses STEP_ prefixes const schema = { properties: { STEP_PERSONAL: { properties: { hasDriversLicense: { type: 'boolean' } } } } }; // Conditions use flat paths (no STEP_ prefix) condition: { "==": [{ "var": "hasDriversLicense" }, true] } ``` ## Event System & Webhooks Central event orchestration for all form interactions: ```typescript <ExtendedForm schema={schema} uiSchema={uiSchema} webhooks={[{ url: 'https://api.example.com/events', events: ['change', 'blur'], debounceMs: 500, retries: 3 }]} onFieldChange={(event) => console.log(event.fieldId, event.fieldValue)} /> ``` **Event Types:** - `focus` - Field focus - `blur` - Field blur - `change` - Value changes - `submit` - Form submission - `stepChange` - Wizard navigation - `validationError` - Validation failures ## Custom Widgets All widgets must integrate with the event system: ```typescript import { WidgetProps } from '@rjsf/utils'; import { useFieldEventHandlers } from 'extended-dynamic-forms/events'; export const CustomWidget: FC<WidgetProps> = ({ id, value, onChange, schema }) => { const fieldEventHandlers = useFieldEventHandlers(id, schema); const handleChange = async (newValue) => { onChange(newValue); // RJSF update await fieldEventHandlers.onChange(newValue); // Event system }; return <Input value={value} onChange={(e) => handleChange(e.target.value)} />; }; ``` ## JsonLogic Migration Examples Common patterns for migrating from function-based conditions: ```typescript // Equality // Before: (formData) => formData.field === "value" // After: { "==": [{ "var": "field" }, "value"] } // Boolean check // Before: (formData) => formData.isEnabled // After: { "var": "isEnabled" } // AND condition // Before: (formData) => formData.a && formData.b // After: { "and": [{ "var": "a" }, { "var": "b" }] } // Array contains // Before: (formData) => formData.roles?.includes("admin") // After: { "in": ["admin", { "var": "roles" }] } ``` ## Available Widgets ### Complete Widget List #### Choice Widget (Unified Selection) - **ChoiceWidget** - Intelligent widget that automatically adapts based on schema: - Renders as dropdown/select for regular choices - Renders as radio group when `ui:widget` is "radio" - Renders as checkbox group for multi-select arrays - Supports static and dynamic data sources (API, WebSocket) #### Text Input Widgets - **TextWidget** - Standard text input with Ant Design styling - **TextareaWidget** - Multi-line text input - **PasswordWidget** - Password input with visibility toggle #### Specialized Input Widgets - **ColorWidget** - Color picker using HTML5 color input - **EmailWidget** - Email input with validation - **URLWidget** - URL input with validation - **TelephoneWidget** - Phone number input - **NumberWidget** - Numeric input with increment/decrement controls #### Visual Input Widgets - **RangeWidget** / **SliderWidget** - Slider for numeric ranges - **RatingWidget** - Star rating component #### Date & Time Widgets - **DateWidget** / **EnhancedDateWidget** - Date picker with Ant Design DatePicker - **DateTimeWidget** - Combined date and time picker #### File Upload Widgets - **FileWidget** - Basic file input - **FileUploadWidget** - Enhanced upload with progress, preview, and base64 support ### Widget Configuration Examples #### Basic Text Input ```json { "schema": { "type": "string", "title": "Full Name" }, "uiSchema": { "ui:widget": "text", "ui:placeholder": "Enter your full name" } } ``` #### Number Input with Range ```json { "schema": { "type": "number", "title": "Age", "minimum": 0, "maximum": 120 }, "uiSchema": { "ui:widget": "number" } } ``` #### Rating Widget ```json { "schema": { "type": "number", "title": "Rate your experience", "minimum": 0, "maximum": 5 }, "uiSchema": { "ui:widget": "rating", "ui:options": { "count": 5, "allowHalf": true } } } ``` #### Choice Widget (Dropdown) ```json { "schema": { "type": "string", "title": "Country", "enum": ["us", "uk", "au", "ca"], "enumNames": ["United States", "United Kingdom", "Australia", "Canada"] }, "uiSchema": { "ui:widget": "choice" } } ``` #### Choice Widget (Radio Group) ```json { "schema": { "type": "string", "title": "Gender", "enum": ["male", "female", "other"] }, "uiSchema": { "ui:widget": "choice", "ui:options": { "presenter": "radio" } } } ``` #### Choice Widget (Checkbox Group - Multi-select) ```json { "schema": { "type": "array", "title": "Interests", "items": { "type": "string", "enum": ["sports", "music", "reading", "travel"] }, "uniqueItems": true }, "uiSchema": { "ui:widget": "choice", "ui:options": { "presenter": "checkbox" } } } ``` #### Date Picker ```json { "schema": { "type": "string", "title": "Date of Birth", "format": "date" }, "uiSchema": { "ui:widget": "date", "ui:options": { "format": "DD/MM/YYYY", "picker": "date" } } } ``` #### File Upload ```json { "schema": { "type": "string", "title": "Resume", "format": "data-url" }, "uiSchema": { "ui:widget": "FileUploadWidget", "ui:options": { "accept": ".pdf,.doc,.docx", "maxSize": 5242880, "multiple": false } } } ``` #### Color Picker ```json { "schema": { "type": "string", "title": "Favorite Color", "format": "color" }, "uiSchema": { "ui:widget": "color" } } ``` #### Range/Slider Widget ```json { "schema": { "type": "number", "title": "Volume", "minimum": 0, "maximum": 100 }, "uiSchema": { "ui:widget": "range", "ui:options": { "marks": { "0": "Mute", "50": "50%", "100": "Max" } } } } ``` #### Hidden Field ```json { "schema": { "type": "string", "title": "User ID" }, "uiSchema": { "ui:widget": "hidden" } } ``` ### Widget Registration Register custom widgets with ExtendedForm: ```typescript import { ExtendedForm, customWidgets } from 'extended-dynamic-forms'; // Use all built-in widgets <ExtendedForm widgets={customWidgets} schema={schema} uiSchema={uiSchema} /> // Or register specific widgets <ExtendedForm widgets={{ text: customWidgets.text, date: customWidgets.date, choice: customWidgets.choice }} /> ``` ## Development ```bash # Development npm run dev # Start playground npm run test # Run tests npm run test:ui # Test with UI # Building npm run build # Build library npm run build:vanilla # Build standalone (currently broken - JsonLogic incompatible) npm run preview # Preview build # Code Quality npm run lint # ESLint npm run format # Prettier ``` ## Project Structure ``` src/ ├── components/ # Core form components ├── conditionals/ # Three Pillars system │ └── v2/ # Current implementation ├── events/ # Event orchestration ├── layouts/ # Layout systems │ └── wizard/ # Wizard form components ├── widgets/ # Input widgets └── utils/ # Utilities ``` ## Examples Interactive examples available at `/playground/src/demos/`: - **Wizard Forms**: Australian Company Onboarding, Employee Onboarding - **Conditionals**: Dynamic visibility, validation, array handling - **Events**: Webhook integration, field tracking - **Widgets**: All available components Run `npm run dev` to explore examples. ## Known Limitations 1. **Dependencies**: Requires RJSF source in sibling directory 2. **Installation**: Cannot install from npm 3. **Vanilla JS**: JsonLogic not supported in standalone builds 4. **Arrays**: No drag-and-drop functionality 5. **JSON Patch**: Only supports add/remove/replace operations 6. **Test Coverage**: Limited for UI components and events ## TypeScript Full TypeScript support with exported types: ```typescript import { ExtendedForm, WizardForm, UiRule, SchemaRule, ValidationRule, JsonLogicRule, FormEvent, WebhookConfig } from 'extended-dynamic-forms'; ``` ## Contributing 1. Fork the repository 2. Create your feature branch 3. Add tests for new functionality 4. Ensure all tests pass 5. Submit a pull request ## License MIT License - see LICENSE file for details.