extended-dynamic-forms 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +387 -1392
- package/dist/events/FormEventContext.d.ts +3 -0
- package/dist/index.es.js +22516 -14517
- package/dist/index.umd.js +63 -36
- package/dist/style.css +1 -1
- package/dist/widgets/base/index.d.ts +2 -0
- package/dist/widgets/base/types.d.ts +135 -0
- package/dist/widgets/base/utils.d.ts +63 -0
- package/dist/widgets/choice/ChoiceWidget.d.ts +30 -0
- package/dist/widgets/choice/ChoiceWidget.types.d.ts +496 -0
- package/dist/widgets/choice/examples/ChoiceWidget.example.d.ts +7 -0
- package/dist/widgets/choice/hooks/__tests__/useChoiceWidget.test.d.ts +1 -0
- package/dist/widgets/choice/hooks/__tests__/useDataSource.test.d.ts +1 -0
- package/dist/widgets/choice/hooks/examples/useChoiceWidget.example.d.ts +18 -0
- package/dist/widgets/choice/hooks/examples/useDataSourceExample.d.ts +22 -0
- package/dist/widgets/choice/hooks/index.d.ts +6 -0
- package/dist/widgets/choice/hooks/useChoiceWidget.d.ts +65 -0
- package/dist/widgets/choice/hooks/useDataSource.d.ts +69 -0
- package/dist/widgets/choice/index.d.ts +59 -0
- package/dist/widgets/choice/presenters/CheckboxPresenter.d.ts +70 -0
- package/dist/widgets/choice/presenters/DropdownPresenter.d.ts +37 -0
- package/dist/widgets/choice/presenters/PresenterStrategy.d.ts +230 -0
- package/dist/widgets/choice/presenters/RadioPresenter.d.ts +39 -0
- package/dist/widgets/choice/presenters/examples/RadioPresenterExample.d.ts +7 -0
- package/dist/widgets/choice/presenters/index.d.ts +20 -0
- package/dist/widgets/choice/providers/ApiDataSource.d.ts +241 -0
- package/dist/widgets/choice/providers/DataSourceProvider.d.ts +260 -0
- package/dist/widgets/choice/providers/StaticDataSource.d.ts +258 -0
- package/dist/widgets/choice/providers/WebSocketDataSource.d.ts +310 -0
- package/dist/widgets/choice/providers/index.d.ts +12 -0
- package/dist/widgets/choice/utils/index.d.ts +1 -0
- package/dist/widgets/choice/utils/responseMappers.d.ts +342 -0
- package/dist/widgets/date/DateTimeWidget.d.ts +26 -0
- package/dist/widgets/date/EnhancedDateWidget.d.ts +26 -0
- package/dist/widgets/date/index.d.ts +13 -0
- package/dist/widgets/file/FileUploadWidget.d.ts +40 -0
- package/dist/widgets/file/index.d.ts +2 -0
- package/dist/widgets/index.d.ts +33 -17
- package/dist/widgets/input/EmailWidget.d.ts +34 -0
- package/dist/widgets/input/NumberWidget.d.ts +39 -0
- package/dist/widgets/input/TelephoneWidget.d.ts +34 -0
- package/dist/widgets/input/URLWidget.d.ts +34 -0
- package/dist/widgets/input/index.d.ts +7 -0
- package/dist/widgets/text/PasswordWidget.d.ts +45 -0
- package/dist/widgets/text/TextWidget.d.ts +40 -0
- package/dist/widgets/text/TextareaWidget.d.ts +38 -0
- package/dist/widgets/text/index.d.ts +20 -0
- package/package.json +5 -5
- package/dist/vanilla-entry.d.ts +0 -232
- package/dist/widgets/enhanced/EnhancedWidgets.d.ts +0 -37
package/README.md
CHANGED
|
@@ -1,1548 +1,543 @@
|
|
|
1
1
|
# Extended Dynamic Forms
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## BREAKING CHANGE: v0.2.0
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
>
|
|
7
|
-
> **Migration Required**: If you are using function-based conditions like `condition: (formData) => ...`, you must migrate to JsonLogic format: `condition: { "==": [{ "var": "fieldName" }, "value"] }`
|
|
5
|
+
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.
|
|
8
6
|
|
|
9
|
-
##
|
|
7
|
+
## Project Status
|
|
10
8
|
|
|
11
|
-
**Important
|
|
9
|
+
**Important Notes:**
|
|
10
|
+
- Requires RJSF source in sibling directory for development
|
|
11
|
+
- Cannot be installed from npm due to local file dependencies
|
|
12
|
+
- JSON logic conditions don't work with vanilla JavaScript builds
|
|
13
|
+
- Array drag-and-drop is not implemented
|
|
14
|
+
- Limited test coverage for UI components and event system
|
|
15
|
+
- JSON Patch implementation supports add/remove/replace only (not move/copy/test)
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
- The project currently uses local file dependencies that require a specific directory structure to build
|
|
15
|
-
- The project requires RJSF packages from a sibling directory (../react-jsonschema-form/)
|
|
16
|
-
- Array drag-and-drop functionality mentioned in documentation is not implemented
|
|
17
|
-
- Test coverage is limited for UI components and event systems
|
|
18
|
-
- Several documented features have incomplete implementations
|
|
19
|
-
- Significant dead code: 76% of utility functions are unused (13 out of 17 functions)
|
|
20
|
-
- JSON Patch implementation in v2 doesn't fully comply with RFC 6902 (no move/copy/test operations)
|
|
17
|
+
## Overview
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
Extended Dynamic Forms is an extension library for React JSON Schema Form (RJSF) v6 that adds:
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
[](https://www.typescriptlang.org/)
|
|
30
|
-
[](#standalone-javascript-support)
|
|
31
|
-
|
|
32
|
-
## Why Extended Dynamic Forms?
|
|
33
|
-
|
|
34
|
-
While [RJSF](https://github.com/rjsf-team/react-jsonschema-form) provides an excellent foundation for generating forms from JSON Schema, enterprise applications demand more sophisticated capabilities:
|
|
35
|
-
|
|
36
|
-
### 🚀 **Performance Revolution**
|
|
37
|
-
- **O(N) Conditional Logic**: Advanced Three Pillars architecture with optimized performance
|
|
38
|
-
- **Optimized Array Processing**: Per-item evaluation with dynamic array conditionals
|
|
39
|
-
- **Memory Efficient**: Immutable schema processing with intelligent caching
|
|
40
|
-
|
|
41
|
-
### 🎯 **Enterprise-Grade Features**
|
|
42
|
-
- **Complex Conditional Logic**: Multi-field dependencies, nested conditions, and dynamic validation
|
|
43
|
-
- **Multi-Step Workflows**: Automatic wizard detection with step-aware conditional logic
|
|
44
|
-
- **Real-Time Integration**: Production-ready webhooks with retry logic and error isolation
|
|
45
|
-
- **Professional UI**: Comprehensive suite of enhanced Ant Design components with consistent styling
|
|
46
|
-
|
|
47
|
-
### 🔄 **Developer Experience**
|
|
48
|
-
- **Dual Architecture Support**: Modern React hooks + vanilla JavaScript compatibility
|
|
49
|
-
- **Type-Safe Throughout**: Comprehensive TypeScript interfaces and validation
|
|
50
|
-
- **Rich Ecosystem**: Interactive playground with comprehensive real-world demos
|
|
51
|
-
|
|
52
|
-
This library transforms RJSF from a simple form generator into a complete enterprise form solution while preserving its declarative approach.
|
|
53
|
-
|
|
54
|
-
## Core Features
|
|
55
|
-
|
|
56
|
-
### 🚀 Three Pillars Architecture with JsonLogic
|
|
57
|
-
- **UI Logic Pillar**: Control form appearance with JsonLogic conditions and JSON Patch operations
|
|
58
|
-
- **Schema Logic Pillar**: Modify data structure with JsonLogic rules when truly needed
|
|
59
|
-
- **Validation Logic Pillar**: Dynamic validation rules with JsonLogic conditions
|
|
60
|
-
- **O(N) Performance**: Superior array handling with optimized evaluation
|
|
61
|
-
- **JsonLogic-Only**: Secure, data-driven conditions without code execution
|
|
62
|
-
- **React Hooks**: Modern, declarative API with `useConditionalUi`, `useConditionalSchema`, `useConditionalValidation`
|
|
63
|
-
|
|
64
|
-
### 🎯 Declarative Conditional Logic with JsonLogic
|
|
65
|
-
- Define complex form behavior with secure JsonLogic rules
|
|
66
|
-
- Dynamic array support with per-item evaluation
|
|
67
|
-
- No code execution - all conditions are data-driven
|
|
68
|
-
- Type-safe with comprehensive TypeScript support
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { UiRule, JsonLogicRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
72
|
-
|
|
73
|
-
const uiRules: UiRule[] = [{
|
|
74
|
-
name: 'Show premium features for premium users',
|
|
75
|
-
condition: {
|
|
76
|
-
"==": [{ "var": "accountType" }, "premium"]
|
|
77
|
-
} as JsonLogicRule,
|
|
78
|
-
effect: [
|
|
79
|
-
{ op: 'remove', path: '/premiumFeatures/ui:widget' }
|
|
80
|
-
]
|
|
81
|
-
}];
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 🧙♂️ Multi-Step Wizard Forms
|
|
85
|
-
- Automatic step detection from schema structure
|
|
86
|
-
- **Full conditional logic support** with step-aware rule processing
|
|
87
|
-
- Built-in progress indicators and navigation
|
|
88
|
-
- Per-step validation
|
|
89
|
-
- Supports Three Pillars architecture with JsonLogic conditions
|
|
90
|
-
|
|
91
|
-
**Step-Aware Conditional Logic**
|
|
92
|
-
|
|
93
|
-
The `WizardForm` automatically filters and transforms conditional rules to work within the current step's context. This means you can define conditions that reference fields across different steps, and the wizard will intelligently apply only the relevant rules for the active step:
|
|
94
|
-
|
|
95
|
-
```tsx
|
|
96
|
-
// Define steps with prefixed field names to avoid collisions
|
|
97
|
-
const schema = {
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: {
|
|
100
|
-
STEP_PERSONAL: {
|
|
101
|
-
type: 'object',
|
|
102
|
-
title: 'Personal Information',
|
|
103
|
-
properties: {
|
|
104
|
-
isUSCitizen: { type: 'boolean', title: 'Are you a US Citizen?' },
|
|
105
|
-
ssn: { type: 'string', title: 'Social Security Number' }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// ... other steps
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Use UiRules with JsonLogic to handle step-aware conditionals
|
|
113
|
-
const uiRules: UiRule[] = [{
|
|
114
|
-
name: 'Show SSN only for US Citizens in Personal step',
|
|
115
|
-
condition: {
|
|
116
|
-
"!": { "var": "STEP_PERSONAL.isUSCitizen" }
|
|
117
|
-
} as JsonLogicRule,
|
|
118
|
-
effect: [
|
|
119
|
-
{ op: 'add', path: '/STEP_PERSONAL/properties/ssn/ui:widget', value: 'hidden' }
|
|
120
|
-
]
|
|
121
|
-
}];
|
|
122
|
-
|
|
123
|
-
// The WizardForm handles the step isolation automatically
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 🔗 Central Event Orchestration
|
|
127
|
-
- Unified event system for all field interactions
|
|
128
|
-
- Webhook integration with retry logic
|
|
129
|
-
- Real-time form analytics capabilities
|
|
130
|
-
- Debounced event processing
|
|
131
|
-
- **Enhanced webhook support** for form-level events (submit, stepChange, validationError)
|
|
132
|
-
|
|
133
|
-
### 🎨 Ant Design Components
|
|
134
|
-
- Complete widget suite built with Ant Design v5
|
|
135
|
-
- Enhanced standard widgets with event integration
|
|
136
|
-
- Custom widgets: Rating, Color Picker, Range Slider
|
|
137
|
-
- Professional field components: Address, Phone, Currency
|
|
138
|
-
|
|
139
|
-
### ⚡ Form Lifecycle Hooks
|
|
140
|
-
- Intercept and modify form data at key stages
|
|
141
|
-
- `beforeSubmit`, `afterSubmit`, `beforeValidation`, `afterValidation`
|
|
142
|
-
- Ideal for data transformation, logging, or triggering side effects
|
|
143
|
-
|
|
144
|
-
### 📦 Additional Features
|
|
145
|
-
- **TypeScript**: Full type safety and IntelliSense with comprehensive interface definitions
|
|
146
|
-
- **Performance**: Optimized re-renders and memoization with intelligent caching
|
|
147
|
-
- **Extensible**: Add custom widgets, fields, and actions with clean APIs
|
|
148
|
-
- **Test Coverage**: Focused on conditional logic and wizard forms (UI components have limited coverage)
|
|
149
|
-
- **Developer Experience**: Interactive playground with real-world demos (note: some deprecated)
|
|
150
|
-
|
|
151
|
-
### 🌐 Standalone JavaScript Support
|
|
152
|
-
|
|
153
|
-
**Complete vanilla JavaScript compatibility** with a production-ready UMD bundle that includes React, Ant Design, and all dependencies:
|
|
154
|
-
|
|
155
|
-
```html
|
|
156
|
-
<!DOCTYPE html>
|
|
157
|
-
<html>
|
|
158
|
-
<head>
|
|
159
|
-
<script src="https://unpkg.com/extended-dynamic-forms/dist/standalone.umd.js"></script>
|
|
160
|
-
<link rel="stylesheet" href="https://unpkg.com/extended-dynamic-forms/dist/style.css">
|
|
161
|
-
</head>
|
|
162
|
-
<body>
|
|
163
|
-
<div id="form-container"></div>
|
|
164
|
-
|
|
165
|
-
<script>
|
|
166
|
-
const { createForm, createWizard } = ExtendedDynamicForms;
|
|
167
|
-
|
|
168
|
-
// Create a complete form without React knowledge
|
|
169
|
-
const form = createForm({
|
|
170
|
-
schema: {
|
|
171
|
-
type: 'object',
|
|
172
|
-
properties: {
|
|
173
|
-
name: { type: 'string', title: 'Full Name' },
|
|
174
|
-
email: { type: 'string', format: 'email', title: 'Email' }
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
container: document.getElementById('form-container'),
|
|
178
|
-
onSubmit: (data) => console.log('Form submitted:', data)
|
|
179
|
-
});
|
|
180
|
-
</script>
|
|
181
|
-
</body>
|
|
182
|
-
</html>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**Important Notes on Conditional Logic in Vanilla JS:**
|
|
186
|
-
- **JsonLogic is NOT supported in vanilla JS** - The JsonLogic runtime requires React hooks
|
|
187
|
-
- For JsonLogic benefits, you must use the React-based API
|
|
188
|
-
|
|
189
|
-
**Perfect for:**
|
|
190
|
-
- WordPress/Drupal integrations
|
|
191
|
-
- Legacy applications
|
|
192
|
-
- CMS form builders
|
|
193
|
-
- No-build environments
|
|
194
|
-
- PHP/ASP.NET applications
|
|
195
|
-
|
|
196
|
-
## Credits & Acknowledgments
|
|
197
|
-
|
|
198
|
-
This library is built on top of [React JSON Schema Form (RJSF)](https://github.com/rjsf-team/react-jsonschema-form), an amazing open-source project that provides the core form generation engine. We extend RJSF's capabilities while maintaining full compatibility with its ecosystem.
|
|
199
|
-
|
|
200
|
-
Special thanks to the RJSF team and contributors for creating such a flexible and well-architected foundation.
|
|
21
|
+
- **Three Pillars Architecture** for conditional logic with O(N) performance (where N is the number of conditional rules/array items)
|
|
22
|
+
- **Multi-step wizard forms** with automatic step detection
|
|
23
|
+
- **Central event orchestration** with webhook support
|
|
24
|
+
- **25+ Ant Design components** for professional UI
|
|
25
|
+
- **TypeScript-first** development with full type safety
|
|
201
26
|
|
|
202
27
|
## Installation
|
|
203
28
|
|
|
204
|
-
|
|
205
|
-
> - The library cannot be installed directly from npm without modifications
|
|
206
|
-
> - You need the RJSF source code in a sibling directory for development
|
|
207
|
-
> - Consider using a monorepo structure or git submodules for proper dependency management
|
|
208
|
-
|
|
209
|
-
### React Projects
|
|
29
|
+
**Note:** This library currently requires local development setup and cannot be installed via npm due to local file dependencies.
|
|
210
30
|
|
|
211
31
|
```bash
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
# or
|
|
216
|
-
pnpm add extended-dynamic-forms
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Standalone JavaScript (No Build Required)
|
|
220
|
-
|
|
221
|
-
```html
|
|
222
|
-
<!-- Production CDN -->
|
|
223
|
-
<script src="https://unpkg.com/extended-dynamic-forms@latest/dist/standalone.umd.js"></script>
|
|
224
|
-
<link rel="stylesheet" href="https://unpkg.com/extended-dynamic-forms@latest/dist/style.css">
|
|
32
|
+
# Clone RJSF as sibling directory (required)
|
|
33
|
+
cd ..
|
|
34
|
+
git clone https://github.com/rjsf-team/react-jsonschema-form.git
|
|
225
35
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
### Peer Dependencies
|
|
231
|
-
|
|
232
|
-
**Current Version**: `0.2.0` - Built for **RJSF v6** with Three Pillars architecture (JsonLogic-only).
|
|
233
|
-
|
|
234
|
-
```json
|
|
235
|
-
{
|
|
236
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
237
|
-
"react-dom": "^18.0.0 || ^19.0.0",
|
|
238
|
-
"antd": "^5.24.0"
|
|
239
|
-
}
|
|
36
|
+
# Setup Extended Dynamic Forms
|
|
37
|
+
cd extended-dynamic-forms
|
|
38
|
+
npm install
|
|
39
|
+
npm run dev
|
|
240
40
|
```
|
|
241
41
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
**RJSF Dependencies** (automatically handled):
|
|
245
|
-
- `@rjsf/core`: ^6.0.0+
|
|
246
|
-
- `@rjsf/utils`: ^6.0.0+
|
|
247
|
-
- `@rjsf/validator-ajv8`: ^6.0.0+
|
|
248
|
-
- `@rjsf/antd`: ^6.0.0+
|
|
42
|
+
**Important:** The library is currently designed for internal use or as part of a monorepo setup. Standalone npm package distribution is not yet supported.
|
|
249
43
|
|
|
250
|
-
|
|
44
|
+
## Core Architecture
|
|
251
45
|
|
|
252
|
-
|
|
253
|
-
- `dayjs`: ^1.11.13 (Date handling)
|
|
254
|
-
- `json-rules-engine-simplified`: ^0.2.0
|
|
46
|
+
### Three Pillars Conditional System
|
|
255
47
|
|
|
256
|
-
|
|
48
|
+
The conditional logic system is divided into three independent pillars:
|
|
257
49
|
|
|
258
|
-
|
|
50
|
+
#### 1. UI Logic Pillar
|
|
51
|
+
Controls form appearance using JsonLogic conditions and JSON Patch operations:
|
|
259
52
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
This package uses Ant Design components. Import the CSS in your application:
|
|
53
|
+
```typescript
|
|
54
|
+
import { useConditionalUi, UiRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
263
55
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
56
|
+
const uiRules: UiRule[] = [{
|
|
57
|
+
name: 'Show license number when has license',
|
|
58
|
+
condition: { "==": [{ "var": "hasDriversLicense" }, true] },
|
|
59
|
+
effect: [{ op: 'remove', path: '/licenseNumber/ui:widget' }]
|
|
60
|
+
}];
|
|
268
61
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
Since Extended Dynamic Forms uses Ant Design components, you can customize the appearance using Ant Design's powerful theming system:
|
|
272
|
-
|
|
273
|
-
```tsx
|
|
274
|
-
import { ConfigProvider, theme } from 'antd';
|
|
275
|
-
import { ExtendedForm, WizardForm } from 'extended-dynamic-forms';
|
|
276
|
-
|
|
277
|
-
function App() {
|
|
278
|
-
return (
|
|
279
|
-
<ConfigProvider
|
|
280
|
-
theme={{
|
|
281
|
-
token: {
|
|
282
|
-
colorPrimary: '#00b96b',
|
|
283
|
-
borderRadius: 8,
|
|
284
|
-
fontSize: 16,
|
|
285
|
-
},
|
|
286
|
-
algorithm: theme.darkAlgorithm, // Optional: Use dark theme
|
|
287
|
-
}}
|
|
288
|
-
>
|
|
289
|
-
<WizardForm
|
|
290
|
-
schema={schema}
|
|
291
|
-
uiSchema={uiSchema}
|
|
292
|
-
conditionals={conditionals}
|
|
293
|
-
/>
|
|
294
|
-
</ConfigProvider>
|
|
295
|
-
);
|
|
296
|
-
}
|
|
62
|
+
const conditionalUiSchema = useConditionalUi(uiRules, baseUiSchema, formData);
|
|
297
63
|
```
|
|
298
64
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
> ⚠️ **BREAKING CHANGE**: As of v0.2.0, the Three Pillars system ONLY supports JsonLogic conditions. Function-based conditions are no longer supported for security and maintainability reasons.
|
|
302
|
-
|
|
303
|
-
The Three Pillars system with JsonLogic provides superior performance, cleaner separation of concerns, and better security. It consists of three independent pillars:
|
|
65
|
+
#### 2. Schema Logic Pillar
|
|
66
|
+
Modifies JSON Schema structure dynamically (use sparingly):
|
|
304
67
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
import { useConditionalUi, UiRule, JsonLogicRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
312
|
-
|
|
313
|
-
// Define UI rules with JsonLogic conditions and JSON Patch operations
|
|
314
|
-
const uiRules: UiRule[] = [
|
|
315
|
-
{
|
|
316
|
-
name: 'Show shipping address when required',
|
|
317
|
-
condition: {
|
|
318
|
-
"==": [{ "var": "requiresShipping" }, true]
|
|
319
|
-
} as JsonLogicRule,
|
|
320
|
-
effect: [{ op: 'remove', path: '/shippingAddress/ui:widget' }],
|
|
321
|
-
order: 1
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
name: 'Dynamic country fields',
|
|
325
|
-
condition: {
|
|
326
|
-
"==": [{ "var": "shippingAddress.country" }, "USA"]
|
|
327
|
-
} as JsonLogicRule,
|
|
328
|
-
effect: [
|
|
329
|
-
{ op: 'remove', path: '/shippingAddress/state/ui:widget' },
|
|
330
|
-
{ op: 'add', path: '/shippingAddress/province/ui:widget', value: 'hidden' }
|
|
331
|
-
],
|
|
332
|
-
order: 2
|
|
333
|
-
}
|
|
334
|
-
];
|
|
335
|
-
|
|
336
|
-
const baseUiSchema = {
|
|
337
|
-
shippingAddress: {
|
|
338
|
-
'ui:widget': 'hidden', // Initially hidden
|
|
339
|
-
state: { 'ui:widget': 'text' },
|
|
340
|
-
province: { 'ui:widget': 'hidden' } // Initially hidden
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// In your component
|
|
345
|
-
function MyForm() {
|
|
346
|
-
const [formData, setFormData] = useState({});
|
|
347
|
-
|
|
348
|
-
// IMPORTANT: Define rules outside render or memoize them!
|
|
349
|
-
const conditionalUiSchema = useConditionalUi(uiRules, baseUiSchema, formData);
|
|
350
|
-
|
|
351
|
-
return (
|
|
352
|
-
<ExtendedForm
|
|
353
|
-
schema={schema}
|
|
354
|
-
uiSchema={conditionalUiSchema}
|
|
355
|
-
formData={formData}
|
|
356
|
-
onChange={({ formData }) => setFormData(formData)}
|
|
357
|
-
/>
|
|
358
|
-
);
|
|
359
|
-
}
|
|
68
|
+
```typescript
|
|
69
|
+
const schemaRules: SchemaRule[] = [{
|
|
70
|
+
name: 'Add premium fields',
|
|
71
|
+
condition: { "==": [{ "var": "accountType" }, "premium"] },
|
|
72
|
+
effect: [{ op: 'add', path: '/properties/premiumFeatures', value: {...} }]
|
|
73
|
+
}];
|
|
360
74
|
```
|
|
361
75
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
Modifies JSON Schema structure (use sparingly - prefer UI or Validation pillars):
|
|
365
|
-
|
|
366
|
-
```tsx
|
|
367
|
-
import { useConditionalSchema, SchemaRule, JsonLogicRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
368
|
-
|
|
369
|
-
const schemaRules: SchemaRule[] = [
|
|
370
|
-
{
|
|
371
|
-
name: 'Add premium fields',
|
|
372
|
-
condition: {
|
|
373
|
-
"==": [{ "var": "accountType" }, "premium"]
|
|
374
|
-
} as JsonLogicRule,
|
|
375
|
-
effect: [
|
|
376
|
-
{
|
|
377
|
-
op: 'add',
|
|
378
|
-
path: '/properties/premiumFeatures',
|
|
379
|
-
value: {
|
|
380
|
-
type: 'object',
|
|
381
|
-
properties: {
|
|
382
|
-
priority: { type: 'string', enum: ['high', 'medium', 'low'] }
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
]
|
|
387
|
-
}
|
|
388
|
-
];
|
|
76
|
+
#### 3. Validation Logic Pillar
|
|
77
|
+
Adds conditional validation rules:
|
|
389
78
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const validationRules: ValidationRule[] = [
|
|
402
|
-
{
|
|
403
|
-
name: 'Require consent for minors',
|
|
404
|
-
condition: {
|
|
405
|
-
"<": [{ "var": "age" }, 18]
|
|
406
|
-
} as JsonLogicRule,
|
|
407
|
-
effect: {
|
|
408
|
-
type: 'validation',
|
|
409
|
-
validate: (errors, formData) => {
|
|
410
|
-
if (!formData.parentConsent) {
|
|
411
|
-
errors.parentConsent.addError('Parent consent is required for minors');
|
|
412
|
-
}
|
|
79
|
+
```typescript
|
|
80
|
+
const validationRules: ValidationRule[] = [{
|
|
81
|
+
name: 'Require consent for minors',
|
|
82
|
+
condition: { "<": [{ "var": "age" }, 18] },
|
|
83
|
+
effect: {
|
|
84
|
+
name: 'Parent consent validation',
|
|
85
|
+
isActive: { "<": [{ "var": "age" }, 18] },
|
|
86
|
+
validate: (errors, formData) => {
|
|
87
|
+
if (!formData.parentConsent) {
|
|
88
|
+
errors.parentConsent.__errors = ['Parent consent required'];
|
|
413
89
|
}
|
|
414
90
|
}
|
|
415
91
|
}
|
|
416
|
-
];
|
|
417
|
-
|
|
418
|
-
// In your component
|
|
419
|
-
function MyForm({ schema }) {
|
|
420
|
-
const [formData, setFormData] = useState({});
|
|
421
|
-
|
|
422
|
-
// 1. Get the conditional validation configuration
|
|
423
|
-
const conditionalValidationConfig = useConditionalValidation(validationRules, {}, formData);
|
|
424
|
-
|
|
425
|
-
// 2. Create the memoized validate function
|
|
426
|
-
const customValidate = useMemo(
|
|
427
|
-
() => createCustomValidate(conditionalValidationConfig),
|
|
428
|
-
[conditionalValidationConfig]
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
// 3. Use with ExtendedForm
|
|
432
|
-
return (
|
|
433
|
-
<ExtendedForm
|
|
434
|
-
schema={schema}
|
|
435
|
-
formData={formData}
|
|
436
|
-
onChange={({ formData }) => setFormData(formData)}
|
|
437
|
-
customValidate={customValidate}
|
|
438
|
-
/>
|
|
439
|
-
);
|
|
440
|
-
}
|
|
92
|
+
}];
|
|
441
93
|
```
|
|
442
94
|
|
|
443
95
|
### Dynamic Arrays with O(N) Performance
|
|
444
96
|
|
|
445
|
-
|
|
97
|
+
Provides efficient handling of conditional logic within arrays, where N is the number of array items:
|
|
446
98
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
**How It Works:**
|
|
450
|
-
- **Per-Item Evaluation**: Each array item's conditionals are evaluated independently during render
|
|
451
|
-
- **No Rule Explosion**: No rules are multiplied by array size
|
|
452
|
-
- **React-Optimized**: Leverages React's rendering cycle for optimal performance
|
|
453
|
-
|
|
454
|
-
> **Note**: `createDynamicArrayItemsUiSchema` still supports function-based conditions as it evaluates per-item during render. This is not a security risk as the functions are defined in your code, not from user input.
|
|
455
|
-
|
|
456
|
-
```tsx
|
|
99
|
+
```typescript
|
|
457
100
|
import { createDynamicArrayItemsUiSchema } from 'extended-dynamic-forms/conditionals/v2';
|
|
458
101
|
|
|
459
|
-
// Define per-item conditional logic - this function runs for each array item
|
|
460
|
-
const directorItemConditions = (director, index) => ({
|
|
461
|
-
shareholdingPercentage: {
|
|
462
|
-
'ui:widget': director.hasShareholding ? 'percentage' : 'hidden'
|
|
463
|
-
},
|
|
464
|
-
tfn: {
|
|
465
|
-
'ui:widget': director.isAustralianResident ? 'text' : 'hidden'
|
|
466
|
-
},
|
|
467
|
-
passport: {
|
|
468
|
-
'ui:widget': !director.isAustralianResident ? 'text' : 'hidden'
|
|
469
|
-
},
|
|
470
|
-
// Add visual indicator for the chairman
|
|
471
|
-
'ui:classNames': index === 0 ? 'chairman-director' : 'regular-director'
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Apply to your array field
|
|
475
102
|
const uiSchema = {
|
|
476
103
|
directors: {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
**Performance Comparison:**
|
|
485
|
-
```typescript
|
|
486
|
-
// ❌ Traditional approach: O(M×N) - Rules × Array Items
|
|
487
|
-
// With 10 rules and 50 directors = 500 evaluations
|
|
488
|
-
|
|
489
|
-
// ✅ Our approach: O(N) - Per-item evaluation
|
|
490
|
-
// With 50 directors = 50 evaluations (regardless of rule complexity)
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
**Advanced Patterns:**
|
|
494
|
-
```tsx
|
|
495
|
-
// Complex business logic with multiple conditions
|
|
496
|
-
const productItemConditions = (product, index) => {
|
|
497
|
-
const baseConfig = {
|
|
498
|
-
price: { 'ui:widget': 'currency' },
|
|
499
|
-
quantity: { 'ui:widget': 'number' }
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
// Category-specific fields
|
|
503
|
-
if (product.category === 'software') {
|
|
504
|
-
baseConfig.licenseType = { 'ui:widget': 'select' };
|
|
505
|
-
baseConfig.maintenancePeriod = { 'ui:widget': 'hidden' };
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Subscription-specific logic
|
|
509
|
-
if (product.billingType === 'subscription') {
|
|
510
|
-
baseConfig.recurringPrice = { 'ui:widget': 'currency' };
|
|
511
|
-
baseConfig.billingCycle = { 'ui:widget': 'select' };
|
|
104
|
+
items: createDynamicArrayItemsUiSchema((director, index) => ({
|
|
105
|
+
shareholdingPercentage: {
|
|
106
|
+
'ui:widget': director.hasShareholding ? 'percentage' : 'hidden'
|
|
107
|
+
}
|
|
108
|
+
}))
|
|
512
109
|
}
|
|
513
|
-
|
|
514
|
-
return baseConfig;
|
|
515
|
-
};
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
### Performance Best Practices
|
|
519
|
-
|
|
520
|
-
> ⚠️ **Critical**: Always define rules and base schemas outside render or memoize them!
|
|
521
|
-
|
|
522
|
-
```tsx
|
|
523
|
-
// ❌ BAD - Creates new objects every render
|
|
524
|
-
function MyForm() {
|
|
525
|
-
const conditionalUiSchema = useConditionalUi(
|
|
526
|
-
[{ condition: () => true, effect: [...] }], // New array every render!
|
|
527
|
-
{ field: { 'ui:widget': 'text' } }, // New object every render!
|
|
528
|
-
formData
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// ✅ GOOD - Stable references
|
|
533
|
-
const rules = [{ condition: () => true, effect: [...] }];
|
|
534
|
-
const baseUiSchema = { field: { 'ui:widget': 'text' } };
|
|
535
|
-
|
|
536
|
-
function MyForm() {
|
|
537
|
-
const conditionalUiSchema = useConditionalUi(rules, baseUiSchema, formData);
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### JsonLogic Conditions (Required)
|
|
542
|
-
|
|
543
|
-
As of v0.2.0, ONLY JsonLogic conditions are supported for security and maintainability:
|
|
544
|
-
|
|
545
|
-
```tsx
|
|
546
|
-
import { JsonLogicRule, UiRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
547
|
-
|
|
548
|
-
// JsonLogic rule examples
|
|
549
|
-
const simpleRule: UiRule = {
|
|
550
|
-
name: 'Show field B when A is true',
|
|
551
|
-
condition: { "==": [{ "var": "fieldA" }, true] } as JsonLogicRule,
|
|
552
|
-
effect: [{ op: 'remove', path: '/fieldB/ui:widget' }]
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
const complexRule: UiRule = {
|
|
556
|
-
name: 'Complex business logic',
|
|
557
|
-
condition: {
|
|
558
|
-
"and": [
|
|
559
|
-
{ "==": [{ "var": "accountType" }, "premium"] },
|
|
560
|
-
{ ">": [{ "var": "revenue" }, 100000] },
|
|
561
|
-
{ "in": [{ "var": "country" }, ["USA", "Canada", "UK"]] }
|
|
562
|
-
]
|
|
563
|
-
} as JsonLogicRule,
|
|
564
|
-
effect: [{ op: 'remove', path: '/advancedFeatures/ui:widget' }]
|
|
565
110
|
};
|
|
566
111
|
```
|
|
567
112
|
|
|
568
|
-
##
|
|
569
|
-
|
|
570
|
-
### Simple Wizard Form (Recommended)
|
|
113
|
+
## Multi-Step Wizard Forms
|
|
571
114
|
|
|
572
|
-
|
|
115
|
+
Create wizard forms with automatic step detection:
|
|
573
116
|
|
|
574
|
-
```
|
|
575
|
-
import { useState } from 'react';
|
|
117
|
+
```typescript
|
|
576
118
|
import { WizardForm } from 'extended-dynamic-forms';
|
|
577
|
-
import { useConditionalUi, UiRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
578
|
-
import schema from './schema.json';
|
|
579
|
-
import baseUiSchema from './ui-schema.json';
|
|
580
|
-
import webhooks from './webhooks.json';
|
|
581
|
-
|
|
582
|
-
// Example: Three Pillars approach with JsonLogic (required)
|
|
583
|
-
const uiRules: UiRule[] = [
|
|
584
|
-
{
|
|
585
|
-
name: 'Show tax ID in company step when listed',
|
|
586
|
-
condition: {
|
|
587
|
-
"==": [{ "var": "STEP_COMPANY.isListedCompany" }, true]
|
|
588
|
-
} as JsonLogicRule,
|
|
589
|
-
effect: [{ op: 'remove', path: '/STEP_COMPANY/taxId/ui:widget' }]
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
name: 'Show shareholding percentage for directors with shares',
|
|
593
|
-
condition: {
|
|
594
|
-
"some": [
|
|
595
|
-
{ "var": "STEP_DIRECTORS.directors" },
|
|
596
|
-
{ "var": "hasShareholding" }
|
|
597
|
-
]
|
|
598
|
-
} as JsonLogicRule,
|
|
599
|
-
effect: [{ op: 'remove', path: '/STEP_DIRECTORS/directors/items/shareholdingPercentage/ui:widget' }]
|
|
600
|
-
}
|
|
601
|
-
];
|
|
602
|
-
|
|
603
|
-
function App() {
|
|
604
|
-
const [formData, setFormData] = useState({});
|
|
605
|
-
const conditionalUiSchema = useConditionalUi(uiRules, baseUiSchema, formData);
|
|
606
|
-
|
|
607
|
-
return (
|
|
608
|
-
<WizardForm
|
|
609
|
-
schema={schema}
|
|
610
|
-
uiSchema={conditionalUiSchema}
|
|
611
|
-
formData={formData}
|
|
612
|
-
onChange={({ formData }) => setFormData(formData)}
|
|
613
|
-
webhooks={webhooks}
|
|
614
|
-
onSubmit={(data) => console.log('Submitted:', data.formData)}
|
|
615
|
-
/>
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
119
|
|
|
120
|
+
<WizardForm
|
|
121
|
+
schema={wizardSchema}
|
|
122
|
+
uiSchema={conditionalUiSchema}
|
|
123
|
+
formData={formData}
|
|
124
|
+
onStepChange={(fromStep, toStep) => {
|
|
125
|
+
console.log(`Step ${fromStep} → ${toStep}`);
|
|
126
|
+
}}
|
|
127
|
+
/>
|
|
619
128
|
```
|
|
620
129
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
```tsx
|
|
624
|
-
import { ExtendedForm } from 'extended-dynamic-forms';
|
|
625
|
-
import { useConditionalUi, UiRule, JsonLogicRule } from 'extended-dynamic-forms/conditionals/v2';
|
|
130
|
+
**Important:** Wizard forms flatten nested step data. Use flat paths in conditions:
|
|
626
131
|
|
|
132
|
+
```typescript
|
|
133
|
+
// Schema uses STEP_ prefixes
|
|
627
134
|
const schema = {
|
|
628
|
-
type: 'object',
|
|
629
135
|
properties: {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
136
|
+
STEP_PERSONAL: {
|
|
137
|
+
properties: {
|
|
138
|
+
hasDriversLicense: { type: 'boolean' }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
633
141
|
}
|
|
634
142
|
};
|
|
635
143
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
school: { 'ui:widget': 'text' }
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
// Define UI rules outside component with JsonLogic
|
|
642
|
-
const uiRules: UiRule[] = [
|
|
643
|
-
{
|
|
644
|
-
name: 'Show student fields when student',
|
|
645
|
-
condition: {
|
|
646
|
-
"!": { "var": "isStudent" }
|
|
647
|
-
} as JsonLogicRule,
|
|
648
|
-
effect: [
|
|
649
|
-
{ op: 'add', path: '/studentId/ui:widget', value: 'hidden' },
|
|
650
|
-
{ op: 'add', path: '/school/ui:widget', value: 'hidden' }
|
|
651
|
-
]
|
|
652
|
-
}
|
|
653
|
-
];
|
|
654
|
-
|
|
655
|
-
function App() {
|
|
656
|
-
const [formData, setFormData] = useState({});
|
|
657
|
-
const conditionalUiSchema = useConditionalUi(uiRules, baseUiSchema, formData);
|
|
658
|
-
|
|
659
|
-
return (
|
|
660
|
-
<ExtendedForm
|
|
661
|
-
schema={schema}
|
|
662
|
-
uiSchema={conditionalUiSchema}
|
|
663
|
-
formData={formData}
|
|
664
|
-
onChange={({ formData }) => setFormData(formData)}
|
|
665
|
-
/>
|
|
666
|
-
);
|
|
667
|
-
}
|
|
144
|
+
// Conditions use flat paths (no STEP_ prefix)
|
|
145
|
+
condition: { "==": [{ "var": "hasDriversLicense" }, true] }
|
|
668
146
|
```
|
|
669
147
|
|
|
670
|
-
##
|
|
148
|
+
## Event System & Webhooks
|
|
671
149
|
|
|
672
|
-
|
|
150
|
+
Central event orchestration for all form interactions:
|
|
673
151
|
|
|
674
|
-
|
|
152
|
+
```typescript
|
|
153
|
+
<ExtendedForm
|
|
154
|
+
schema={schema}
|
|
155
|
+
uiSchema={uiSchema}
|
|
156
|
+
webhooks={[{
|
|
157
|
+
url: 'https://api.example.com/events',
|
|
158
|
+
events: ['change', 'blur'],
|
|
159
|
+
debounceMs: 500,
|
|
160
|
+
retries: 3
|
|
161
|
+
}]}
|
|
162
|
+
onFieldChange={(event) => console.log(event.fieldId, event.fieldValue)}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
675
165
|
|
|
676
|
-
|
|
166
|
+
**Event Types:**
|
|
167
|
+
- `focus` - Field focus
|
|
168
|
+
- `blur` - Field blur
|
|
169
|
+
- `change` - Value changes
|
|
170
|
+
- `submit` - Form submission
|
|
171
|
+
- `stepChange` - Wizard navigation
|
|
172
|
+
- `validationError` - Validation failures
|
|
677
173
|
|
|
678
|
-
|
|
679
|
-
// Color Picker
|
|
680
|
-
const uiSchema = {
|
|
681
|
-
favoriteColor: {
|
|
682
|
-
'ui:widget': 'color'
|
|
683
|
-
}
|
|
684
|
-
};
|
|
174
|
+
## Custom Widgets
|
|
685
175
|
|
|
686
|
-
|
|
687
|
-
const uiSchema = {
|
|
688
|
-
satisfaction: {
|
|
689
|
-
'ui:widget': 'rating',
|
|
690
|
-
'ui:options': {
|
|
691
|
-
max: 10 // default is 5
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
};
|
|
176
|
+
All widgets must integrate with the event system:
|
|
695
177
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
type: 'number',
|
|
700
|
-
minimum: 0,
|
|
701
|
-
maximum: 100
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
const uiSchema = {
|
|
705
|
-
volume: {
|
|
706
|
-
'ui:widget': 'range'
|
|
707
|
-
}
|
|
708
|
-
};
|
|
178
|
+
```typescript
|
|
179
|
+
import { WidgetProps } from '@rjsf/utils';
|
|
180
|
+
import { useFieldEventHandlers } from 'extended-dynamic-forms/events';
|
|
709
181
|
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
182
|
+
export const CustomWidget: FC<WidgetProps> = ({ id, value, onChange, schema }) => {
|
|
183
|
+
const fieldEventHandlers = useFieldEventHandlers(id, schema);
|
|
184
|
+
|
|
185
|
+
const handleChange = async (newValue) => {
|
|
186
|
+
onChange(newValue); // RJSF update
|
|
187
|
+
await fieldEventHandlers.onChange(newValue); // Event system
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return <Input value={value} onChange={(e) => handleChange(e.target.value)} />;
|
|
719
191
|
};
|
|
720
192
|
```
|
|
721
193
|
|
|
722
|
-
|
|
194
|
+
## JsonLogic Migration Examples
|
|
723
195
|
|
|
724
|
-
|
|
725
|
-
- **Event Integration**: Automatic integration with the central event system
|
|
726
|
-
- **Ant Design Styling**: Modern, consistent appearance
|
|
727
|
-
- **Error States**: Proper error indication and messaging
|
|
728
|
-
- **Accessibility**: Enhanced ARIA support
|
|
196
|
+
Common patterns for migrating from function-based conditions:
|
|
729
197
|
|
|
730
|
-
|
|
198
|
+
```typescript
|
|
199
|
+
// Equality
|
|
200
|
+
// Before: (formData) => formData.field === "value"
|
|
201
|
+
// After: { "==": [{ "var": "field" }, "value"] }
|
|
731
202
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
- **Visual Design**: Clean card-based layout for array management
|
|
736
|
-
- **Note**: Drag-and-drop reordering is not currently implemented
|
|
203
|
+
// Boolean check
|
|
204
|
+
// Before: (formData) => formData.isEnabled
|
|
205
|
+
// After: { "var": "isEnabled" }
|
|
737
206
|
|
|
738
|
-
|
|
207
|
+
// AND condition
|
|
208
|
+
// Before: (formData) => formData.a && formData.b
|
|
209
|
+
// After: { "and": [{ "var": "a" }, { "var": "b" }] }
|
|
739
210
|
|
|
740
|
-
|
|
211
|
+
// Array contains
|
|
212
|
+
// Before: (formData) => formData.roles?.includes("admin")
|
|
213
|
+
// After: { "in": ["admin", { "var": "roles" }] }
|
|
214
|
+
```
|
|
741
215
|
|
|
742
|
-
|
|
743
|
-
import { ExtendedForm, type FormFieldEvent } from 'extended-dynamic-forms';
|
|
216
|
+
## Available Widgets
|
|
744
217
|
|
|
745
|
-
|
|
746
|
-
schema={schema}
|
|
747
|
-
uiSchema={uiSchema}
|
|
748
|
-
formData={formData}
|
|
749
|
-
|
|
750
|
-
// Field-level event handlers
|
|
751
|
-
onFieldFocus={(event: FormFieldEvent) => {
|
|
752
|
-
console.log(`Field ${event.fieldId} focused:`, event.fieldValue);
|
|
753
|
-
}}
|
|
754
|
-
|
|
755
|
-
onFieldChange={(event: FormFieldEvent) => {
|
|
756
|
-
console.log(`Field ${event.fieldId} changed:`, event.fieldValue);
|
|
757
|
-
// Real-time validation or processing
|
|
758
|
-
}}
|
|
759
|
-
|
|
760
|
-
onFieldBlur={(event: FormFieldEvent) => {
|
|
761
|
-
console.log(`Field ${event.fieldId} blurred:`, event.fieldValue);
|
|
762
|
-
}}
|
|
763
|
-
/>
|
|
764
|
-
```
|
|
218
|
+
### Complete Widget List
|
|
765
219
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
// Comprehensive webhook configuration for all event types
|
|
774
|
-
const webhookConfigs: WebhookConfig[] = [
|
|
775
|
-
{
|
|
776
|
-
url: 'https://api.yourapp.com/form-events',
|
|
777
|
-
method: 'POST',
|
|
778
|
-
events: ['change', 'blur', 'submit', 'validationError'], // Multiple event types
|
|
779
|
-
debounceMs: 500, // Wait 500ms before sending (field events)
|
|
780
|
-
retries: 3, // Retry failed requests
|
|
781
|
-
timeout: 5000, // 5 second timeout
|
|
782
|
-
headers: {
|
|
783
|
-
'Authorization': 'Bearer your-api-token',
|
|
784
|
-
'X-Form-ID': 'contact-form'
|
|
785
|
-
}
|
|
786
|
-
},
|
|
787
|
-
{
|
|
788
|
-
url: 'https://analytics.yourapp.com/wizard-tracking',
|
|
789
|
-
method: 'POST',
|
|
790
|
-
events: ['stepChange', 'submit'], // Wizard-specific events
|
|
791
|
-
debounceMs: 0, // No debouncing for navigation
|
|
792
|
-
headers: {
|
|
793
|
-
'X-Session-ID': sessionId
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
];
|
|
220
|
+
#### Choice Widget (Unified Selection)
|
|
221
|
+
- **ChoiceWidget** - Intelligent widget that automatically adapts based on schema:
|
|
222
|
+
- Renders as dropdown/select for regular choices
|
|
223
|
+
- Renders as radio group when `ui:widget` is "radio"
|
|
224
|
+
- Renders as checkbox group for multi-select arrays
|
|
225
|
+
- Supports static and dynamic data sources (API, WebSocket)
|
|
797
226
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
webhooks={webhookConfigs}
|
|
803
|
-
onWebhookStart={(webhook, event) => {
|
|
804
|
-
console.log('Webhook started:', webhook.url, event.eventType);
|
|
805
|
-
}}
|
|
806
|
-
onWebhookComplete={(webhook, event, response) => {
|
|
807
|
-
console.log('Webhook completed:', response.status);
|
|
808
|
-
}}
|
|
809
|
-
onWebhookError={(webhook, event, error) => {
|
|
810
|
-
console.error('Webhook failed:', error.message);
|
|
811
|
-
}}
|
|
812
|
-
/>
|
|
227
|
+
#### Text Input Widgets
|
|
228
|
+
- **TextWidget** - Standard text input with Ant Design styling
|
|
229
|
+
- **TextareaWidget** - Multi-line text input
|
|
230
|
+
- **PasswordWidget** - Password input with visibility toggle
|
|
813
231
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
// Handle webhook failures gracefully
|
|
821
|
-
notifyUser('Form data saved locally, will sync when online');
|
|
822
|
-
}}
|
|
823
|
-
/>
|
|
824
|
-
```
|
|
232
|
+
#### Specialized Input Widgets
|
|
233
|
+
- **ColorWidget** - Color picker using HTML5 color input
|
|
234
|
+
- **EmailWidget** - Email input with validation
|
|
235
|
+
- **URLWidget** - URL input with validation
|
|
236
|
+
- **TelephoneWidget** - Phone number input
|
|
237
|
+
- **NumberWidget** - Numeric input with increment/decrement controls
|
|
825
238
|
|
|
826
|
-
####
|
|
239
|
+
#### Visual Input Widgets
|
|
240
|
+
- **RangeWidget** / **SliderWidget** - Slider for numeric ranges
|
|
241
|
+
- **RatingWidget** - Star rating component
|
|
827
242
|
|
|
828
|
-
|
|
243
|
+
#### Date & Time Widgets
|
|
244
|
+
- **DateWidget** / **EnhancedDateWidget** - Date picker with Ant Design DatePicker
|
|
245
|
+
- **DateTimeWidget** - Combined date and time picker
|
|
829
246
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
{
|
|
834
|
-
eventType: 'focus' | 'blur' | 'change',
|
|
835
|
-
fieldId: 'email',
|
|
836
|
-
fieldValue: 'user@example.com',
|
|
837
|
-
schema: { type: 'string', format: 'email' },
|
|
838
|
-
fieldPath: ['contact', 'email'],
|
|
839
|
-
timestamp: 1234567890123,
|
|
840
|
-
formData: { /* complete form data */ },
|
|
841
|
-
source: 'extended-dynamic-forms'
|
|
842
|
-
}
|
|
843
|
-
```
|
|
247
|
+
#### File Upload Widgets
|
|
248
|
+
- **FileWidget** - Basic file input
|
|
249
|
+
- **FileUploadWidget** - Enhanced upload with progress, preview, and base64 support
|
|
844
250
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
251
|
+
### Widget Configuration Examples
|
|
252
|
+
|
|
253
|
+
#### Basic Text Input
|
|
254
|
+
```json
|
|
848
255
|
{
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
email: 'john@example.com',
|
|
853
|
-
// ... all submitted data
|
|
256
|
+
"schema": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"title": "Full Name"
|
|
854
259
|
},
|
|
855
|
-
|
|
856
|
-
|
|
260
|
+
"uiSchema": {
|
|
261
|
+
"ui:widget": "text",
|
|
262
|
+
"ui:placeholder": "Enter your full name"
|
|
263
|
+
}
|
|
857
264
|
}
|
|
858
265
|
```
|
|
859
266
|
|
|
860
|
-
|
|
861
|
-
```
|
|
862
|
-
// Triggered when user navigates between wizard steps
|
|
267
|
+
#### Number Input with Range
|
|
268
|
+
```json
|
|
863
269
|
{
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
270
|
+
"schema": {
|
|
271
|
+
"type": "number",
|
|
272
|
+
"title": "Age",
|
|
273
|
+
"minimum": 0,
|
|
274
|
+
"maximum": 120
|
|
275
|
+
},
|
|
276
|
+
"uiSchema": {
|
|
277
|
+
"ui:widget": "number"
|
|
278
|
+
}
|
|
870
279
|
}
|
|
871
280
|
```
|
|
872
281
|
|
|
873
|
-
|
|
874
|
-
```
|
|
875
|
-
// Triggered when form validation fails
|
|
282
|
+
#### Rating Widget
|
|
283
|
+
```json
|
|
876
284
|
{
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
message: 'Age must be at least 18',
|
|
889
|
-
name: 'minimum',
|
|
890
|
-
argument: 18,
|
|
891
|
-
stack: '.age Age must be at least 18',
|
|
892
|
-
schemaPath: '#/properties/age/minimum'
|
|
285
|
+
"schema": {
|
|
286
|
+
"type": "number",
|
|
287
|
+
"title": "Rate your experience",
|
|
288
|
+
"minimum": 0,
|
|
289
|
+
"maximum": 5
|
|
290
|
+
},
|
|
291
|
+
"uiSchema": {
|
|
292
|
+
"ui:widget": "rating",
|
|
293
|
+
"ui:options": {
|
|
294
|
+
"count": 5,
|
|
295
|
+
"allowHalf": true
|
|
893
296
|
}
|
|
894
|
-
|
|
895
|
-
formData: { /* form data that failed validation */ },
|
|
896
|
-
timestamp: 1234567890123,
|
|
897
|
-
source: 'extended-dynamic-forms'
|
|
297
|
+
}
|
|
898
298
|
}
|
|
899
299
|
```
|
|
900
300
|
|
|
901
|
-
####
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
301
|
+
#### Choice Widget (Dropdown)
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"schema": {
|
|
305
|
+
"type": "string",
|
|
306
|
+
"title": "Country",
|
|
307
|
+
"enum": ["us", "uk", "au", "ca"],
|
|
308
|
+
"enumNames": ["United States", "United Kingdom", "Australia", "Canada"]
|
|
309
|
+
},
|
|
310
|
+
"uiSchema": {
|
|
311
|
+
"ui:widget": "choice"
|
|
911
312
|
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// Monitor form health and errors
|
|
915
|
-
const monitoringWebhook: WebhookConfig = {
|
|
916
|
-
url: 'https://monitoring.yourapp.com/form-errors',
|
|
917
|
-
events: ['validationError'], // Only track errors
|
|
918
|
-
method: 'POST',
|
|
919
|
-
retries: 5 // Ensure error tracking is reliable
|
|
920
|
-
};
|
|
313
|
+
}
|
|
921
314
|
```
|
|
922
315
|
|
|
923
|
-
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
// Final submission
|
|
937
|
-
const submitWebhook: WebhookConfig = {
|
|
938
|
-
url: 'https://api.yourapp.com/form-submissions',
|
|
939
|
-
events: ['submit'],
|
|
940
|
-
headers: {
|
|
941
|
-
'X-User-ID': userId,
|
|
942
|
-
'X-Final': 'true'
|
|
316
|
+
#### Choice Widget (Radio Group)
|
|
317
|
+
```json
|
|
318
|
+
{
|
|
319
|
+
"schema": {
|
|
320
|
+
"type": "string",
|
|
321
|
+
"title": "Gender",
|
|
322
|
+
"enum": ["male", "female", "other"]
|
|
323
|
+
},
|
|
324
|
+
"uiSchema": {
|
|
325
|
+
"ui:widget": "choice",
|
|
326
|
+
"ui:options": {
|
|
327
|
+
"presenter": "radio"
|
|
328
|
+
}
|
|
943
329
|
}
|
|
944
|
-
}
|
|
330
|
+
}
|
|
945
331
|
```
|
|
946
332
|
|
|
947
|
-
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
});
|
|
964
|
-
} else if (formData.country === 'EU') {
|
|
965
|
-
configs.push({
|
|
966
|
-
url: 'https://eu-api.yourapp.com/forms',
|
|
967
|
-
events: ['submit'],
|
|
968
|
-
headers: { 'X-Region': 'EU', 'X-GDPR': 'true' }
|
|
969
|
-
});
|
|
333
|
+
#### Choice Widget (Checkbox Group - Multi-select)
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"schema": {
|
|
337
|
+
"type": "array",
|
|
338
|
+
"title": "Interests",
|
|
339
|
+
"items": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"enum": ["sports", "music", "reading", "travel"]
|
|
342
|
+
},
|
|
343
|
+
"uniqueItems": true
|
|
344
|
+
},
|
|
345
|
+
"uiSchema": {
|
|
346
|
+
"ui:widget": "choice",
|
|
347
|
+
"ui:options": {
|
|
348
|
+
"presenter": "checkbox"
|
|
970
349
|
}
|
|
971
|
-
|
|
972
|
-
return configs;
|
|
973
|
-
}, [formData.country]);
|
|
974
|
-
|
|
975
|
-
return (
|
|
976
|
-
<ExtendedForm
|
|
977
|
-
schema={schema}
|
|
978
|
-
formData={formData}
|
|
979
|
-
onChange={({ formData }) => setFormData(formData)}
|
|
980
|
-
webhooks={webhooks}
|
|
981
|
-
/>
|
|
982
|
-
);
|
|
350
|
+
}
|
|
983
351
|
}
|
|
984
352
|
```
|
|
985
353
|
|
|
986
|
-
|
|
987
|
-
```
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
events: ['submit'],
|
|
1000
|
-
timeout: 10000,
|
|
1001
|
-
retries: 5
|
|
1002
|
-
};
|
|
1003
|
-
|
|
1004
|
-
<ExtendedForm
|
|
1005
|
-
webhooks={[primaryWebhook, fallbackWebhook]}
|
|
1006
|
-
onWebhookError={(webhook, event, error) => {
|
|
1007
|
-
if (webhook.url === primaryWebhook.url) {
|
|
1008
|
-
console.log('Primary webhook failed, fallback will handle');
|
|
354
|
+
#### Date Picker
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"schema": {
|
|
358
|
+
"type": "string",
|
|
359
|
+
"title": "Date of Birth",
|
|
360
|
+
"format": "date"
|
|
361
|
+
},
|
|
362
|
+
"uiSchema": {
|
|
363
|
+
"ui:widget": "date",
|
|
364
|
+
"ui:options": {
|
|
365
|
+
"format": "DD/MM/YYYY",
|
|
366
|
+
"picker": "date"
|
|
1009
367
|
}
|
|
1010
|
-
|
|
1011
|
-
// Store locally for later sync
|
|
1012
|
-
localStorage.setItem(`pending-${Date.now()}`, JSON.stringify(event));
|
|
1013
|
-
}}
|
|
1014
|
-
/>
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
## API Reference
|
|
1019
|
-
|
|
1020
|
-
### ExtendedFormProps
|
|
1021
|
-
|
|
1022
|
-
```typescript
|
|
1023
|
-
interface ExtendedFormProps<T = any, S extends RJSFSchema = RJSFSchema, F = any> extends FormProps<T, S, F> {
|
|
1024
|
-
// Standard RJSF props
|
|
1025
|
-
schema: S;
|
|
1026
|
-
uiSchema?: UiSchema<T, S, F>;
|
|
1027
|
-
formData?: T;
|
|
1028
|
-
onSubmit?: (data: IChangeEvent<T, S, F>, event?: React.FormEvent<HTMLFormElement>) => void;
|
|
1029
|
-
onChange?: (data: IChangeEvent<T, S, F>) => void;
|
|
1030
|
-
|
|
1031
|
-
// Field-level event handlers
|
|
1032
|
-
onFieldFocus?: (event: FormFieldEvent) => Promise<void> | void;
|
|
1033
|
-
onFieldBlur?: (event: FormFieldEvent) => Promise<void> | void;
|
|
1034
|
-
onFieldChange?: (event: FormFieldEvent) => Promise<void> | void;
|
|
1035
|
-
|
|
1036
|
-
// Webhook configuration
|
|
1037
|
-
webhooks?: WebhookConfig[];
|
|
1038
|
-
|
|
1039
|
-
// Webhook status callbacks
|
|
1040
|
-
onWebhookStart?: (webhook: WebhookConfig, event: any) => void;
|
|
1041
|
-
onWebhookComplete?: (webhook: WebhookConfig, event: any, response: any) => void;
|
|
1042
|
-
onWebhookError?: (webhook: WebhookConfig, event: any, error: Error) => void;
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
// Form lifecycle events
|
|
1046
|
-
beforeSubmit?: (data: T) => Promise<T> | T;
|
|
1047
|
-
afterSubmit?: (data: T) => Promise<void> | void;
|
|
1048
|
-
beforeValidation?: (data: T) => Promise<T> | T;
|
|
1049
|
-
afterValidation?: (data: T, errors: RJSFValidationError[]) => Promise<void> | void;
|
|
368
|
+
}
|
|
1050
369
|
}
|
|
1051
370
|
```
|
|
1052
371
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
372
|
+
#### File Upload
|
|
373
|
+
```json
|
|
374
|
+
{
|
|
375
|
+
"schema": {
|
|
376
|
+
"type": "string",
|
|
377
|
+
"title": "Resume",
|
|
378
|
+
"format": "data-url"
|
|
379
|
+
},
|
|
380
|
+
"uiSchema": {
|
|
381
|
+
"ui:widget": "FileUploadWidget",
|
|
382
|
+
"ui:options": {
|
|
383
|
+
"accept": ".pdf,.doc,.docx",
|
|
384
|
+
"maxSize": 5242880,
|
|
385
|
+
"multiple": false
|
|
386
|
+
}
|
|
387
|
+
}
|
|
1064
388
|
}
|
|
1065
389
|
```
|
|
1066
390
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// Field events
|
|
1081
|
-
interface FieldWebhookEvent extends BaseWebhookEvent {
|
|
1082
|
-
eventType: 'focus' | 'blur' | 'change';
|
|
1083
|
-
fieldId: string;
|
|
1084
|
-
fieldValue: any;
|
|
1085
|
-
schema?: any;
|
|
1086
|
-
fieldPath?: string[];
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// Submit event
|
|
1090
|
-
interface SubmitWebhookEvent extends BaseWebhookEvent {
|
|
1091
|
-
eventType: 'submit';
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// Step change event
|
|
1095
|
-
interface StepChangeWebhookEvent extends BaseWebhookEvent {
|
|
1096
|
-
eventType: 'stepChange';
|
|
1097
|
-
fromStep: number;
|
|
1098
|
-
toStep: number;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
// Validation error event
|
|
1102
|
-
interface ValidationErrorWebhookEvent extends BaseWebhookEvent {
|
|
1103
|
-
eventType: 'validationError';
|
|
1104
|
-
errors: Array<{
|
|
1105
|
-
property: string;
|
|
1106
|
-
message: string;
|
|
1107
|
-
name: string;
|
|
1108
|
-
argument?: any;
|
|
1109
|
-
stack: string;
|
|
1110
|
-
schemaPath: string;
|
|
1111
|
-
}>;
|
|
391
|
+
#### Color Picker
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"schema": {
|
|
395
|
+
"type": "string",
|
|
396
|
+
"title": "Favorite Color",
|
|
397
|
+
"format": "color"
|
|
398
|
+
},
|
|
399
|
+
"uiSchema": {
|
|
400
|
+
"ui:widget": "color"
|
|
401
|
+
}
|
|
1112
402
|
}
|
|
1113
403
|
```
|
|
1114
404
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
// Validation Logic Pillar
|
|
1133
|
-
function useConditionalValidation(
|
|
1134
|
-
rules: ValidationRule[],
|
|
1135
|
-
baseConfig: ValidationConfig,
|
|
1136
|
-
formData: any
|
|
1137
|
-
): ValidationConfig
|
|
1138
|
-
```
|
|
1139
|
-
|
|
1140
|
-
## Real-World Use Cases
|
|
1141
|
-
|
|
1142
|
-
### 🏢 Enterprise Business Applications
|
|
1143
|
-
|
|
1144
|
-
**Australian Company Registration** (Featured Demo)
|
|
1145
|
-
```tsx
|
|
1146
|
-
// Complete 5-step wizard with dynamic conditionals
|
|
1147
|
-
const companyOnboardingSchema = {
|
|
1148
|
-
STEP_COMPANY: { /* Company details */ },
|
|
1149
|
-
STEP_DIRECTORS: { /* Dynamic director array */ },
|
|
1150
|
-
STEP_ADDRESS: { /* Address with postal logic */ },
|
|
1151
|
-
STEP_FINANCIAL: { /* Banking with international support */ },
|
|
1152
|
-
STEP_COMPLIANCE: { /* Regulatory requirements */ }
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
// Per-director conditionals with O(N) performance
|
|
1156
|
-
const uiSchema = {
|
|
1157
|
-
STEP_DIRECTORS: {
|
|
1158
|
-
directors: {
|
|
1159
|
-
items: createDynamicArrayItemsUiSchema((director) => ({
|
|
1160
|
-
tfn: { 'ui:widget': director.isAustralianResident ? 'text' : 'hidden' },
|
|
1161
|
-
passport: { 'ui:widget': !director.isAustralianResident ? 'text' : 'hidden' }
|
|
1162
|
-
}))
|
|
405
|
+
#### Range/Slider Widget
|
|
406
|
+
```json
|
|
407
|
+
{
|
|
408
|
+
"schema": {
|
|
409
|
+
"type": "number",
|
|
410
|
+
"title": "Volume",
|
|
411
|
+
"minimum": 0,
|
|
412
|
+
"maximum": 100
|
|
413
|
+
},
|
|
414
|
+
"uiSchema": {
|
|
415
|
+
"ui:widget": "range",
|
|
416
|
+
"ui:options": {
|
|
417
|
+
"marks": {
|
|
418
|
+
"0": "Mute",
|
|
419
|
+
"50": "50%",
|
|
420
|
+
"100": "Max"
|
|
421
|
+
}
|
|
1163
422
|
}
|
|
1164
423
|
}
|
|
1165
|
-
}
|
|
424
|
+
}
|
|
1166
425
|
```
|
|
1167
426
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
- Security clearance workflows
|
|
1178
|
-
- Benefits enrollment with eligibility rules
|
|
1179
|
-
|
|
1180
|
-
**Grant Applications**
|
|
1181
|
-
- Multi-stage submissions with validation gates
|
|
1182
|
-
- Budget calculations with category-based rules
|
|
1183
|
-
- Compliance checks based on funding type
|
|
1184
|
-
|
|
1185
|
-
### 🔄 Dynamic Array Management
|
|
1186
|
-
|
|
1187
|
-
**Board of Directors Management**
|
|
1188
|
-
```tsx
|
|
1189
|
-
// Professional-grade array handling with O(N) performance
|
|
1190
|
-
import { createDynamicArrayItemsUiSchema } from 'extended-dynamic-forms';
|
|
1191
|
-
|
|
1192
|
-
const uiSchema = {
|
|
1193
|
-
STEP_DIRECTORS: {
|
|
1194
|
-
directors: {
|
|
1195
|
-
items: createDynamicArrayItemsUiSchema((director, index) => ({
|
|
1196
|
-
shareholdingPercentage: {
|
|
1197
|
-
'ui:widget': director.hasShareholding ? 'percentage' : 'hidden'
|
|
1198
|
-
},
|
|
1199
|
-
tfn: {
|
|
1200
|
-
'ui:widget': director.isAustralianResident ? 'text' : 'hidden'
|
|
1201
|
-
},
|
|
1202
|
-
passport: {
|
|
1203
|
-
'ui:widget': !director.isAustralianResident ? 'text' : 'hidden'
|
|
1204
|
-
}
|
|
1205
|
-
}))
|
|
1206
|
-
}
|
|
427
|
+
#### Hidden Field
|
|
428
|
+
```json
|
|
429
|
+
{
|
|
430
|
+
"schema": {
|
|
431
|
+
"type": "string",
|
|
432
|
+
"title": "User ID"
|
|
433
|
+
},
|
|
434
|
+
"uiSchema": {
|
|
435
|
+
"ui:widget": "hidden"
|
|
1207
436
|
}
|
|
1208
|
-
}
|
|
437
|
+
}
|
|
1209
438
|
```
|
|
1210
439
|
|
|
1211
|
-
|
|
1212
|
-
- Dynamic product options based on category
|
|
1213
|
-
- Pricing calculations with quantity breaks
|
|
1214
|
-
- Feature availability by subscription tier
|
|
1215
|
-
|
|
1216
|
-
**Address Management**
|
|
1217
|
-
- International vs. domestic address formats
|
|
1218
|
-
- State/province selection based on country
|
|
1219
|
-
- Postal code validation by region
|
|
440
|
+
### Widget Registration
|
|
1220
441
|
|
|
1221
|
-
|
|
442
|
+
Register custom widgets with ExtendedForm:
|
|
1222
443
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
<!-- Standalone JavaScript integration -->
|
|
1226
|
-
<script>
|
|
1227
|
-
const { createWizard } = ExtendedDynamicForms;
|
|
1228
|
-
|
|
1229
|
-
createWizard({
|
|
1230
|
-
schema: wpFormSchema,
|
|
1231
|
-
container: '#wp-form-container',
|
|
1232
|
-
onSubmit: (data) => {
|
|
1233
|
-
// Submit to WordPress REST API
|
|
1234
|
-
wp.apiRequest({ path: '/wp/v2/forms', method: 'POST', data });
|
|
1235
|
-
}
|
|
1236
|
-
});
|
|
1237
|
-
</script>
|
|
1238
|
-
```
|
|
1239
|
-
|
|
1240
|
-
**Legacy System Modernization**
|
|
1241
|
-
- Drop-in replacement for static HTML forms
|
|
1242
|
-
- Progressive enhancement of existing forms
|
|
1243
|
-
- No-build deployment to production environments
|
|
1244
|
-
|
|
1245
|
-
## Architecture
|
|
444
|
+
```typescript
|
|
445
|
+
import { ExtendedForm, customWidgets } from 'extended-dynamic-forms';
|
|
1246
446
|
|
|
1247
|
-
|
|
447
|
+
// Use all built-in widgets
|
|
448
|
+
<ExtendedForm
|
|
449
|
+
widgets={customWidgets}
|
|
450
|
+
schema={schema}
|
|
451
|
+
uiSchema={uiSchema}
|
|
452
|
+
/>
|
|
1248
453
|
|
|
454
|
+
// Or register specific widgets
|
|
455
|
+
<ExtendedForm
|
|
456
|
+
widgets={{
|
|
457
|
+
text: customWidgets.text,
|
|
458
|
+
date: customWidgets.date,
|
|
459
|
+
choice: customWidgets.choice
|
|
460
|
+
}}
|
|
461
|
+
/>
|
|
1249
462
|
```
|
|
1250
|
-
┌─────────────────────────────────────────────────┐
|
|
1251
|
-
│ Three Pillars v2 (Conditional System) │
|
|
1252
|
-
├─────────────────────────────────────────────────┤
|
|
1253
|
-
│ Application Layer (WizardForm) │
|
|
1254
|
-
├─────────────────────────────────────────────────┤
|
|
1255
|
-
│ Event Layer (FormEventHub) │
|
|
1256
|
-
├─────────────────────────────────────────────────┤
|
|
1257
|
-
│ Component Layer (Widgets/Fields/Templates) │
|
|
1258
|
-
├─────────────────────────────────────────────────┤
|
|
1259
|
-
│ Base Layer (@rjsf/antd) │
|
|
1260
|
-
└─────────────────────────────────────────────────┘
|
|
1261
|
-
```
|
|
1262
|
-
|
|
1263
|
-
## Feature Comparison
|
|
1264
|
-
|
|
1265
|
-
| Feature | Basic RJSF | Extended Dynamic Forms |
|
|
1266
|
-
|---------|------------|----------------------|
|
|
1267
|
-
| **Form Generation** | ✅ JSON Schema | ✅ Enhanced JSON Schema |
|
|
1268
|
-
| **UI Components** | Basic HTML | ✅ Comprehensive Ant Design Suite |
|
|
1269
|
-
| **Conditional Logic** | ❌ Manual | ✅ Three Pillars (O(N)) |
|
|
1270
|
-
| **Multi-Step Forms** | ❌ Manual | ✅ Automatic Wizard Detection |
|
|
1271
|
-
| **Event System** | Basic onChange | ✅ Central Event Orchestration |
|
|
1272
|
-
| **Webhook Integration** | ❌ None | ✅ Production-Ready with Retry |
|
|
1273
|
-
| **Array Conditionals** | ❌ None | ✅ Per-Item O(N) Performance |
|
|
1274
|
-
| **TypeScript** | Partial | ✅ Comprehensive Type Definitions |
|
|
1275
|
-
| **Standalone JS** | ❌ React Only | ✅ Complete UMD Bundle |
|
|
1276
|
-
| **Testing** | Basic | ⚠️ Focused on Conditionals/Wizards |
|
|
1277
|
-
| **Performance** | Standard | ✅ Optimized with Caching |
|
|
1278
|
-
| **Validation** | Basic | ✅ Dynamic + Custom Rules |
|
|
1279
|
-
| **Developer Tools** | Limited | ✅ Interactive Playground |
|
|
1280
463
|
|
|
1281
464
|
## Development
|
|
1282
465
|
|
|
1283
|
-
### 🚀 Quick Start
|
|
1284
|
-
|
|
1285
|
-
> **Development Setup Note**: Due to local file dependencies, you need:
|
|
1286
|
-
> 1. Clone RJSF repository in a sibling directory: `../react-jsonschema-form/`
|
|
1287
|
-
> 2. Ensure the RJSF packages are built and available
|
|
1288
|
-
|
|
1289
466
|
```bash
|
|
1290
|
-
#
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
npm
|
|
467
|
+
# Development
|
|
468
|
+
npm run dev # Start playground
|
|
469
|
+
npm run test # Run tests
|
|
470
|
+
npm run test:ui # Test with UI
|
|
1294
471
|
|
|
1295
|
-
#
|
|
1296
|
-
npm run
|
|
1297
|
-
npm run
|
|
472
|
+
# Building
|
|
473
|
+
npm run build # Build library
|
|
474
|
+
npm run build:vanilla # Build standalone (currently broken - JsonLogic incompatible)
|
|
475
|
+
npm run preview # Preview build
|
|
1298
476
|
|
|
1299
|
-
#
|
|
1300
|
-
npm run
|
|
1301
|
-
npm run
|
|
1302
|
-
npm run lint # ESLint validation
|
|
1303
|
-
npm run format # Prettier formatting
|
|
477
|
+
# Code Quality
|
|
478
|
+
npm run lint # ESLint
|
|
479
|
+
npm run format # Prettier
|
|
1304
480
|
```
|
|
1305
481
|
|
|
1306
|
-
|
|
482
|
+
## Project Structure
|
|
1307
483
|
|
|
1308
|
-
```bash
|
|
1309
|
-
# Library builds
|
|
1310
|
-
npm run build # TypeScript + Vite library build
|
|
1311
|
-
npm run build:vanilla # Standalone UMD bundle
|
|
1312
|
-
npm run build:all # Both library and standalone
|
|
1313
|
-
|
|
1314
|
-
# Production validation
|
|
1315
|
-
npm run preview # Preview library build
|
|
1316
|
-
npm run serve:examples # Serve vanilla JS examples
|
|
1317
|
-
```
|
|
1318
|
-
|
|
1319
|
-
### 🧪 Testing
|
|
1320
|
-
|
|
1321
|
-
**Test Coverage**: Test suite focuses primarily on:
|
|
1322
|
-
- **Conditional Logic**: Comprehensive coverage for conditional systems (16+ test files)
|
|
1323
|
-
- **Wizard Forms**: Step navigation and validation
|
|
1324
|
-
- **Performance**: Array conditional optimization
|
|
1325
|
-
- **Note**: Limited coverage for UI components, event system, and utility functions
|
|
1326
|
-
|
|
1327
|
-
```bash
|
|
1328
|
-
npm run test # Run complete test suite
|
|
1329
|
-
npm run test -- --grep "conditional" # Test conditional logic
|
|
1330
|
-
npm run test -- --grep "wizard" # Test wizard forms
|
|
1331
|
-
npm run test -- --grep "array" # Test array handling
|
|
1332
|
-
```
|
|
1333
|
-
|
|
1334
|
-
### 🏗️ Architecture
|
|
1335
|
-
|
|
1336
|
-
**Build Configuration**:
|
|
1337
|
-
- **Library**: ES + UMD via Vite with TypeScript declarations
|
|
1338
|
-
- **Standalone**: Complete UMD bundle with React/antd included
|
|
1339
|
-
- **External Dependencies**: React, antd marked as externals in library build
|
|
1340
|
-
- **Type Generation**: `vite-plugin-dts` for .d.ts files
|
|
1341
|
-
|
|
1342
|
-
**File Structure**:
|
|
1343
484
|
```
|
|
1344
485
|
src/
|
|
1345
|
-
├──
|
|
1346
|
-
├──
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
├── widgets/
|
|
1352
|
-
|
|
1353
|
-
└── utils/ # Utilities (Note: 76% are unused)
|
|
1354
|
-
```
|
|
1355
|
-
|
|
1356
|
-
## Examples & Demos
|
|
1357
|
-
|
|
1358
|
-
### 🎮 Interactive Playground
|
|
1359
|
-
|
|
1360
|
-
The library includes a comprehensive **interactive playground** with extensive real-world demos demonstrating all features:
|
|
1361
|
-
|
|
1362
|
-
```bash
|
|
1363
|
-
# Run the playground locally
|
|
1364
|
-
npm run dev
|
|
1365
|
-
# or
|
|
1366
|
-
npm run playground
|
|
1367
|
-
```
|
|
1368
|
-
|
|
1369
|
-
### 📚 Demo Categories
|
|
1370
|
-
|
|
1371
|
-
> **Note**: Some playground demos may be outdated.
|
|
1372
|
-
|
|
1373
|
-
#### **Three Pillars Demonstrations**
|
|
1374
|
-
- **Global UI Rules**: Dynamic show/hide with JSON Patch operations
|
|
1375
|
-
- **Dynamic Array Items**: Per-item conditional logic with O(N) performance
|
|
1376
|
-
- **Conditional Validation**: Custom validation rules without schema changes
|
|
1377
|
-
|
|
1378
|
-
#### **🧙♂️ Wizard Form Showcases**
|
|
1379
|
-
- **Australian Company Onboarding**: 5-step enterprise form with:
|
|
1380
|
-
- Dynamic director arrays with per-item conditionals
|
|
1381
|
-
- Real-time fee calculations based on business rules
|
|
1382
|
-
- Complex validation (ABN/ACN numbers, postcodes)
|
|
1383
|
-
- Step-aware conditional logic
|
|
1384
|
-
- Professional business workflow
|
|
1385
|
-
|
|
1386
|
-
#### **🔗 Event System Demos**
|
|
1387
|
-
- **Real-time Form Analytics**: Webhook integration with retry logic
|
|
1388
|
-
- **Field-Level Event Handling**: Focus/blur/change event orchestration
|
|
1389
|
-
- **Form-Level Event Handling**: Submit, validation error, and step change events
|
|
1390
|
-
- **Webhook Configuration**: Multiple endpoints with custom headers and event filtering
|
|
1391
|
-
- **Webhook Status Tracking**: Callbacks for monitoring webhook success/failure
|
|
1392
|
-
|
|
1393
|
-
#### **🎨 Widget Showcase**
|
|
1394
|
-
- **Enhanced Standard Widgets**: All RJSF widgets with Ant Design styling
|
|
1395
|
-
- **Custom Widgets**: Color picker, rating, range slider, file upload
|
|
1396
|
-
- **Professional Fields**: Address, currency, phone number components
|
|
1397
|
-
|
|
1398
|
-
### 📱 Vanilla JavaScript Examples
|
|
1399
|
-
|
|
1400
|
-
Complete standalone examples in `/examples/vanilla-js/`:
|
|
1401
|
-
|
|
1402
|
-
```html
|
|
1403
|
-
<!-- Basic Form -->
|
|
1404
|
-
basic-form.html <!-- Simple contact form -->
|
|
1405
|
-
conditional-form.html <!-- Dynamic field visibility -->
|
|
1406
|
-
wizard-form.html <!-- Multi-step onboarding -->
|
|
1407
|
-
```
|
|
1408
|
-
|
|
1409
|
-
### 🔗 Live Demo Links
|
|
1410
|
-
|
|
1411
|
-
- **[Playground](https://extended-dynamic-forms-playground.vercel.app)** - Interactive demo environment
|
|
1412
|
-
- **[Australian Company Onboarding](https://extended-dynamic-forms-playground.vercel.app/wizard/australian-company)** - Full enterprise form showcase
|
|
1413
|
-
- **[Three Pillars Demo](https://extended-dynamic-forms-playground.vercel.app/conditionals/three-pillars)** - Modern conditional logic patterns
|
|
1414
|
-
|
|
1415
|
-
## Contributing
|
|
1416
|
-
|
|
1417
|
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
1418
|
-
|
|
1419
|
-
## Migration Guide: Enhanced Webhook Events
|
|
1420
|
-
|
|
1421
|
-
### Upgrading to Enhanced Webhook System
|
|
1422
|
-
|
|
1423
|
-
The enhanced webhook system is **fully backward compatible**. Existing webhook configurations will continue to work without any changes. The new event types are additive and opt-in.
|
|
1424
|
-
|
|
1425
|
-
#### For Existing Webhook Users
|
|
1426
|
-
|
|
1427
|
-
Your current webhook configurations will continue to work exactly as before:
|
|
1428
|
-
|
|
1429
|
-
```typescript
|
|
1430
|
-
// This existing configuration still works perfectly
|
|
1431
|
-
const webhookConfig: WebhookConfig = {
|
|
1432
|
-
url: 'https://api.example.com/form-events',
|
|
1433
|
-
method: 'POST',
|
|
1434
|
-
debounceMs: 500
|
|
1435
|
-
};
|
|
1436
|
-
// Without the 'events' array, it receives ALL events (backward compatible)
|
|
486
|
+
├── components/ # Core form components
|
|
487
|
+
├── conditionals/ # Three Pillars system
|
|
488
|
+
│ └── v2/ # Current implementation
|
|
489
|
+
├── events/ # Event orchestration
|
|
490
|
+
├── layouts/ # Layout systems
|
|
491
|
+
│ └── wizard/ # Wizard form components
|
|
492
|
+
├── widgets/ # Input widgets
|
|
493
|
+
└── utils/ # Utilities
|
|
1437
494
|
```
|
|
1438
495
|
|
|
1439
|
-
|
|
496
|
+
## Examples
|
|
1440
497
|
|
|
1441
|
-
|
|
1442
|
-
// Option 1: Subscribe to specific new events only
|
|
1443
|
-
const webhookConfig: WebhookConfig = {
|
|
1444
|
-
url: 'https://api.example.com/form-events',
|
|
1445
|
-
events: ['submit', 'validationError'], // Only these new events
|
|
1446
|
-
debounceMs: 1000
|
|
1447
|
-
};
|
|
498
|
+
Interactive examples available at `/playground/src/demos/`:
|
|
1448
499
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
debounceMs: 500
|
|
1454
|
-
};
|
|
1455
|
-
```
|
|
500
|
+
- **Wizard Forms**: Australian Company Onboarding, Employee Onboarding
|
|
501
|
+
- **Conditionals**: Dynamic visibility, validation, array handling
|
|
502
|
+
- **Events**: Webhook integration, field tracking
|
|
503
|
+
- **Widgets**: All available components
|
|
1456
504
|
|
|
1457
|
-
|
|
505
|
+
Run `npm run dev` to explore examples.
|
|
1458
506
|
|
|
1459
|
-
|
|
507
|
+
## Known Limitations
|
|
1460
508
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
headers: {
|
|
1468
|
-
'X-API-Key': 'your-api-key'
|
|
1469
|
-
},
|
|
1470
|
-
debounceMs: 500,
|
|
1471
|
-
retries: 3,
|
|
1472
|
-
timeout: 10000
|
|
1473
|
-
};
|
|
1474
|
-
|
|
1475
|
-
// Wizard-specific tracking
|
|
1476
|
-
const wizardWebhook: WebhookConfig = {
|
|
1477
|
-
url: 'https://analytics.example.com/wizard-tracking',
|
|
1478
|
-
events: ['stepChange', 'submit'],
|
|
1479
|
-
method: 'POST',
|
|
1480
|
-
debounceMs: 0, // No debouncing for navigation events
|
|
1481
|
-
retries: 1
|
|
1482
|
-
};
|
|
1483
|
-
|
|
1484
|
-
// Error monitoring only
|
|
1485
|
-
const errorWebhook: WebhookConfig = {
|
|
1486
|
-
url: 'https://monitoring.example.com/form-errors',
|
|
1487
|
-
events: ['validationError'],
|
|
1488
|
-
method: 'POST',
|
|
1489
|
-
retries: 5 // Ensure error tracking is reliable
|
|
1490
|
-
};
|
|
1491
|
-
```
|
|
509
|
+
1. **Dependencies**: Requires RJSF source in sibling directory
|
|
510
|
+
2. **Installation**: Cannot install from npm
|
|
511
|
+
3. **Vanilla JS**: JsonLogic not supported in standalone builds
|
|
512
|
+
4. **Arrays**: No drag-and-drop functionality
|
|
513
|
+
5. **JSON Patch**: Only supports add/remove/replace operations
|
|
514
|
+
6. **Test Coverage**: Limited for UI components and events
|
|
1492
515
|
|
|
1493
|
-
|
|
516
|
+
## TypeScript
|
|
1494
517
|
|
|
1495
|
-
|
|
518
|
+
Full TypeScript support with exported types:
|
|
1496
519
|
|
|
1497
520
|
```typescript
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
// Handle success
|
|
1510
|
-
if (response.ok) {
|
|
1511
|
-
showNotification('Data saved successfully');
|
|
1512
|
-
}
|
|
1513
|
-
}}
|
|
1514
|
-
onWebhookError={(webhook, event, error) => {
|
|
1515
|
-
// Hide loading indicator
|
|
1516
|
-
setWebhookLoading(false);
|
|
1517
|
-
|
|
1518
|
-
// Handle error gracefully
|
|
1519
|
-
console.error('Webhook failed:', error);
|
|
1520
|
-
showNotification('Saving to server failed, will retry later');
|
|
1521
|
-
|
|
1522
|
-
// Store for offline sync
|
|
1523
|
-
queueForLaterSync(event);
|
|
1524
|
-
}}
|
|
1525
|
-
/>
|
|
521
|
+
import {
|
|
522
|
+
ExtendedForm,
|
|
523
|
+
WizardForm,
|
|
524
|
+
UiRule,
|
|
525
|
+
SchemaRule,
|
|
526
|
+
ValidationRule,
|
|
527
|
+
JsonLogicRule,
|
|
528
|
+
FormEvent,
|
|
529
|
+
WebhookConfig
|
|
530
|
+
} from 'extended-dynamic-forms';
|
|
1526
531
|
```
|
|
1527
532
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
1. **Event Filtering**: If you specify the `events` array, only those events will trigger webhooks
|
|
1531
|
-
2. **Debouncing**: Form-level events (submit, stepChange, validationError) respect debounceMs settings
|
|
1532
|
-
3. **Payload Structure**: All events now include standard fields (timestamp, formData, eventType, source)
|
|
1533
|
-
4. **No Breaking Changes**: All existing functionality remains unchanged
|
|
1534
|
-
|
|
1535
|
-
## Community & Support
|
|
1536
|
-
|
|
1537
|
-
- 📚 [Documentation](https://extended-dynamic-forms.dev)
|
|
1538
|
-
- 🐛 [Issue Tracker](https://github.com/user/extended-dynamic-forms/issues)
|
|
1539
|
-
- 💬 [Discussions](https://github.com/user/extended-dynamic-forms/discussions)
|
|
1540
|
-
- 📧 [Email Support](mailto:support@extended-dynamic-forms.dev)
|
|
1541
|
-
|
|
533
|
+
## Contributing
|
|
1542
534
|
|
|
535
|
+
1. Fork the repository
|
|
536
|
+
2. Create your feature branch
|
|
537
|
+
3. Add tests for new functionality
|
|
538
|
+
4. Ensure all tests pass
|
|
539
|
+
5. Submit a pull request
|
|
1543
540
|
|
|
1544
|
-
|
|
541
|
+
## License
|
|
1545
542
|
|
|
1546
|
-
|
|
1547
|
-
Built with ❤️ on top of <a href="https://github.com/rjsf-team/react-jsonschema-form">React JSON Schema Form</a>
|
|
1548
|
-
</p>
|
|
543
|
+
MIT License - see LICENSE file for details.
|