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
Markdown
# 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.