extended-dynamic-forms 0.3.13 → 0.4.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.
Files changed (43) hide show
  1. package/README.md +253 -674
  2. package/dist/WizardForm.d.ts +17 -2
  3. package/dist/components/FieldFeedbackContext.d.ts +4 -0
  4. package/dist/components/FieldStyleContext.d.ts +7 -0
  5. package/dist/components/SchemaSelectionContext.d.ts +20 -0
  6. package/dist/components/URLBlurValidationContext.d.ts +38 -0
  7. package/dist/components/fields/TextChangeScope.d.ts +28 -0
  8. package/dist/components/fields/TextEmptyArrayField.d.ts +3 -0
  9. package/dist/conditionals/v2/createConditionalEngineHook.d.ts +3 -1
  10. package/dist/conditionals/v2/index.d.ts +2 -1
  11. package/dist/conditionals/v2/types.d.ts +13 -0
  12. package/dist/conditionals/v2/utils/stepRules.d.ts +35 -0
  13. package/dist/events/FormEventContext.d.ts +3 -0
  14. package/dist/index.es.js +41796 -32825
  15. package/dist/index.umd.js +1618 -1606
  16. package/dist/layouts/wizard/WizardLayout.d.ts +1 -0
  17. package/dist/style.css +1 -1
  18. package/dist/utils/normalizeUrlValue.d.ts +3 -0
  19. package/dist/utils/urlValue.d.ts +12 -0
  20. package/dist/widgets/base/URLParts.d.ts +5 -0
  21. package/dist/widgets/base/URLPreviewLink.d.ts +5 -0
  22. package/dist/widgets/base/inputStyles.d.ts +4 -0
  23. package/dist/widgets/base/inputTypography.d.ts +3 -0
  24. package/dist/widgets/base/numberInputStyles.d.ts +4 -0
  25. package/dist/widgets/base/telephoneAddons.d.ts +3 -0
  26. package/dist/widgets/base/types.d.ts +15 -1
  27. package/dist/widgets/base/useInputChrome.d.ts +5 -0
  28. package/dist/widgets/base/utils.d.ts +9 -1
  29. package/dist/widgets/choice/ChoiceWidget.types.d.ts +2 -0
  30. package/dist/widgets/choice/dropdownValidation.d.ts +8 -0
  31. package/dist/widgets/choice/inlineChoiceValidation.d.ts +5 -0
  32. package/dist/widgets/choice/presenters/InlineChoiceGroup.d.ts +888 -0
  33. package/dist/widgets/date/DateTimeWidget.d.ts +2 -21
  34. package/dist/widgets/date/calendarConstraints.d.ts +5 -0
  35. package/dist/widgets/date/dateRangeValue.d.ts +20 -0
  36. package/dist/widgets/date/dateTimeValue.d.ts +15 -0
  37. package/dist/widgets/file/FileUploadWidget.d.ts +3 -32
  38. package/dist/widgets/index.d.ts +6 -6
  39. package/dist/widgets/input/TelephoneWidget.d.ts +1 -1
  40. package/dist/widgets/input/emailValidation.d.ts +7 -0
  41. package/dist/widgets/input/telephoneMask.d.ts +7 -0
  42. package/dist/widgets/input/telephoneOptions.d.ts +14 -0
  43. package/package.json +9 -11
package/README.md CHANGED
@@ -1,741 +1,320 @@
1
1
  # Extended Dynamic Forms
2
2
 
3
- ## Declarative Configuration (JSON-only)
4
-
5
- Conditional logic is configured with pure JSON via `x-edf:rules` in your `schema` and `uiSchema`. Function-based conditions and external hooks are not required and should not be used in app code.
6
-
7
- ## Project Status
8
-
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
- - Automated coverage is provided by logic and snapshot suites; component/UI tests
15
- are intentionally omitted
16
- - JSON Patch implementation supports add/remove/replace only (not move/copy/test)
17
-
18
- ## Overview
19
-
20
- Extended Dynamic Forms is an extension library for React JSON Schema Form (RJSF) v6 that adds:
21
-
22
- - **Declarative conditionals** with O(N) performance (rules + dynamic arrays)
23
- - **Multi-step wizard forms** with automatic step detection
24
- - **Central event orchestration** with webhook support
25
- - **25+ Ant Design components** for professional UI
26
- - **TypeScript-first** development with full type safety
27
-
28
- ### Ant Design Prop Pass-through
29
-
30
- All custom widgets built on Ant Design components forward their props via `ui:options`. For example, Rate, Slider, DatePicker, Input, InputNumber, ColorPicker, and TimePicker can be configured using the same prop names from AntD. In addition, you can customize the containing `Form.Item` using `ui:options.formItem`. See docs/custom-widgets-configuration.md for details and examples.
31
-
32
- - DatePicker notes:
33
- - JSON-only flags: `disablePastDates`, `disableFutureDates`, `disableWeekends` (builds `disabledDate` internally)
34
- - Range support: set `ui:options.range: true` to render AntD `RangePicker` (see examples below)
3
+ Extended Dynamic Forms (EDF) extends React JSON Schema Form (RJSF) v6 with
4
+ Ant Design widgets, JSON-configured conditional logic, multi-step forms and
5
+ field events. Use it in a React application with JSON Schema for data constraints
6
+ and `uiSchema` for presentation.
35
7
 
36
8
  ## Installation
37
9
 
38
- **Note:** This library currently requires local development setup and cannot be installed via npm due to local file dependencies.
39
-
40
10
  ```bash
41
- # Clone RJSF as sibling directory (required)
42
- cd ..
43
- git clone https://github.com/rjsf-team/react-jsonschema-form.git
44
-
45
- # Setup Extended Dynamic Forms
46
- cd extended-dynamic-forms
47
- npm install
48
- npm run dev
11
+ npm install extended-dynamic-forms
49
12
  ```
50
13
 
51
- **Important:** The library is currently designed for internal use or as part of a monorepo setup. Standalone npm package distribution is not yet supported.
52
-
53
- ## Testing
54
-
55
- The project uses Vitest with **Node** environment to cover pure logic and serialized
56
- outputs. Component or DOM-level tests are intentionally excluded.
57
-
58
- - Run the full suite: `npm test`
59
- - Logic-only focus: `npm test -- tests/logic`
60
- - Snapshot updates: `npm test -- tests/snapshots -- -u`
61
-
62
- After pulling dependencies or updating `package.json`, run `npm install` (or
63
- `yarn install`) so the lockfile reflects removed packages such as
64
- `@testing-library/*` and `jsdom`.
65
-
66
- ## Core Architecture
67
-
68
- ### Declarative Conditionals (JSON-only)
14
+ Install compatible peer dependencies if your application does not already have them:
69
15
 
70
- Embed rules directly inside your configs. EDF evaluates JsonLogic `condition` and applies JSON Patch `effect` internally. No hooks, functions, or classes in user code.
71
-
72
- - UI rules → `uiSchema['x-edf:rules']`
73
- - Schema rules → `schema['x-edf:rules']`
74
-
75
- Example (UI rules):
76
- ```json
77
- {
78
- "x-edf:rules": [
79
- {
80
- "name": "Show license number when has license",
81
- "condition": { "==": [{ "var": "hasDriversLicense" }, true] },
82
- "effect": [{ "op": "remove", "path": "/licenseNumber/ui:widget" }]
83
- }
84
- ]
85
- }
86
- ```
87
-
88
- Example (Schema rules):
89
- ```json
90
- {
91
- "x-edf:rules": [
92
- {
93
- "name": "Add premium fields",
94
- "condition": { "==": [{ "var": "accountType" }, "premium"] },
95
- "effect": [
96
- { "op": "add", "path": "/properties/premiumFeatures", "value": { "type": "object" } }
97
- ]
98
- }
99
- ]
100
- }
16
+ ```bash
17
+ npm install 'react@^18.0.0' 'react-dom@^18.0.0' 'antd@^5.24.0' 'dayjs@^1.11.13'
101
18
  ```
102
19
 
103
- ### Dynamic Arrays with O(N) Performance
104
-
105
- Use declarative `ui:dynamicItems` with per-item JsonLogic. No functions:
106
-
107
- ```json
108
- {
109
- "directors": {
110
- "items": {
111
- "ui:dynamicItems": {
112
- "base": { "shareholdingPercentage": { "ui:widget": "hidden" } },
113
- "rules": [
114
- {
115
- "condition": { "===": [{ "var": "hasShareholding" }, true] },
116
- "ui": { "shareholdingPercentage": { "ui:widget": "updown" } },
117
- "otherwise": { "shareholdingPercentage": { "ui:widget": "hidden" } }
118
- }
119
- ]
120
- }
121
- }
122
- }
123
- }
124
- ```
20
+ React and React DOM 19 are also supported by the peer ranges. Keep their major
21
+ versions aligned. RJSF and the AJV validator are included in the library bundle;
22
+ you do not need a sibling RJSF checkout.
125
23
 
126
- ## Multi-Step Wizard Forms
24
+ Use ESM imports and include the exported stylesheet. TypeScript declarations are
25
+ provided; use an ESNext/Bundler-compatible module configuration.
127
26
 
128
- Create wizard forms with automatic step detection:
27
+ ## Features
129
28
 
130
- ```typescript
131
- import { WizardForm } from 'extended-dynamic-forms';
29
+ - Single-page forms and wizards with step visibility, navigation and validation.
30
+ - Declarative JsonLogic conditions and JSON Patch effects in JSON configuration.
31
+ - Ant Design text, choice, numeric, date/time, file and display widgets.
32
+ - Per-field options, custom widgets, fields, templates and grid layouts.
33
+ - Field focus, blur and change events, submission hooks and webhook configuration.
132
34
 
133
- <WizardForm
134
- schema={wizardSchema}
135
- uiSchema={uiSchema}
136
- formData={formData}
137
- onStepChange={(fromStep, toStep) => {
138
- console.log(`Step ${fromStep} → ${toStep}`);
139
- }}
140
- />
141
- ```
35
+ ## Basic usage
142
36
 
143
- **Important:** Wizard forms flatten nested step data. Use flat paths in conditions:
37
+ ```tsx
38
+ import { useState } from 'react';
39
+ import { ExtendedForm, type RJSFSchema, type UiSchema } from 'extended-dynamic-forms';
40
+ import 'extended-dynamic-forms/dist/style.css';
144
41
 
145
- ```typescript
146
- // Schema uses STEP_ prefixes
147
- const schema = {
42
+ const schema: RJSFSchema = {
43
+ type: 'object',
44
+ required: ['name'],
148
45
  properties: {
149
- STEP_PERSONAL: {
150
- properties: {
151
- hasDriversLicense: { type: 'boolean' }
152
- }
153
- }
154
- }
46
+ name: { type: 'string', title: 'Name', minLength: 1 },
47
+ email: { type: 'string', title: 'Email', format: 'email' },
48
+ },
49
+ };
50
+
51
+ const uiSchema: UiSchema = {
52
+ name: { 'ui:widget': 'text', 'ui:options': { allowClear: true } },
53
+ email: { 'ui:widget': 'email' },
155
54
  };
156
55
 
157
- // Conditions refer to flattened form data (no STEP_ prefix)
158
- // Example rule (in uiSchema.x-edf:rules): { "==": [{ "var": "hasDriversLicense" }, true] }
56
+ export default function ContactForm() {
57
+ const [formData, setFormData] = useState({});
58
+
59
+ return (
60
+ <ExtendedForm
61
+ schema={schema}
62
+ uiSchema={uiSchema}
63
+ formData={formData}
64
+ onChange={({ formData }) => setFormData(formData)}
65
+ onSubmit={({ formData }) => console.log('Accepted answers', formData)}
66
+ />
67
+ );
68
+ }
159
69
  ```
160
70
 
161
- Note on wizard detection:
162
- - Wizard mode activates only when the root schema contains two or more properties whose keys start with `STEP_`.
163
- - With a single `STEP_…` property, the form renders as a single-page form (no progress/navigation UI).
164
- - To display steps UI, define at least two step objects (e.g., `STEP_PERSONAL`, `STEP_REVIEW`).
71
+ EDF supplies its widgets and validator by default. Override the RJSF `widgets`,
72
+ `fields` or `templates` props when your application needs custom rendering.
73
+ `customWidgets`, `validator` and commonly used RJSF types are exported from
74
+ `extended-dynamic-forms`; no private subpath import is needed.
165
75
 
166
- ### Step Layouts with LayoutGridField
76
+ ## Multi-step forms
167
77
 
168
- Wizard steps can define their own grid layout using a custom object field (e.g., `LayoutGridField`). Place the layout at the step key in `uiSchema` and pass your custom field via `fields` to `WizardForm`.
78
+ `WizardForm` detects steps from root properties beginning with `STEP_`. Define at
79
+ least two step objects to show wizard navigation; one step renders as a
80
+ single-page form.
169
81
 
170
82
  ```tsx
171
83
  import { WizardForm } from 'extended-dynamic-forms';
84
+ import 'extended-dynamic-forms/dist/style.css';
172
85
 
173
86
  const schema = {
174
87
  type: 'object',
175
88
  properties: {
176
- STEP_LAYOUT: {
89
+ STEP_PERSONAL: {
177
90
  type: 'object',
178
- title: 'Personal Info',
179
- properties: {
180
- firstName: { type: 'string', title: 'First Name' },
181
- lastName: { type: 'string', title: 'Last Name' },
182
- age: { type: 'number', title: 'Age' },
183
- gender: { type: 'string', enum: ['Male','Female','Other'] },
184
- favoriteBook: { type: 'string' },
185
- }
186
- }
187
- }
188
- };
189
-
190
- const uiSchema = {
191
- STEP_LAYOUT: {
192
- 'ui:field': 'LayoutGridField',
193
- 'ui:layoutGrid': {
194
- 'ui:row': [
195
- { 'ui:row': { gutter: [16,16], children: [
196
- { 'ui:col': { span: 12, children: ['firstName'] } },
197
- { 'ui:col': { span: 12, children: ['lastName'] } }
198
- ]}},
199
- { 'ui:row': { gutter: [16,16], children: [
200
- { 'ui:col': { span: 24, children: ['age'] } }
201
- ]}},
202
- { 'ui:row': { gutter: [16,16], children: [
203
- { 'ui:col': { span: 24, children: ['gender'] } }
204
- ]}},
205
- { 'ui:row': { gutter: [16,16], children: [
206
- { 'ui:col': { span: 24, children: ['favoriteBook'] } }
207
- ]}}
208
- ]
209
- }
210
- }
211
- };
212
-
213
- // LayoutGridField is included by default in EDF; no extra wiring needed
214
- <WizardForm schema={schema} uiSchema={uiSchema} />
215
- ```
216
-
217
- Notes:
218
- - EDF includes `LayoutGridField` out of the box. Just set `ui:field: 'LayoutGridField'` on the step object.
219
- - EDF variant uses `'ui:layoutGrid'` with AntD gutter and 24-grid spans. The RJSF canonical layout (`'ui:layout'` with 12-grid widths) is also parsed.
220
- - You can still override or extend via the `fields` prop if needed.
221
- - LayoutGridField now writes nested updates immutably using path metadata. This is enabled by default; set `ui:options.pathAware: false` on a step to fall back to the previous shallow merge behaviour.
222
-
223
- ## Event System & Webhooks
224
-
225
- Central event orchestration for all form interactions:
226
-
227
- ```typescript
228
- <ExtendedForm
229
- schema={schema}
230
- uiSchema={uiSchema}
231
- webhooks={[{
232
- url: 'https://api.example.com/events',
233
- events: ['change', 'blur'],
234
- debounceMs: 500,
235
- retries: 3
236
- }]}
237
- onFieldChange={(event) => console.log(event.fieldId, event.fieldValue)}
238
- />
239
- ```
240
-
241
- **Event Types:**
242
- - `focus` - Field focus
243
- - `blur` - Field blur
244
- - `change` - Value changes
245
- - `submit` - Form submission
246
- - `stepChange` - Wizard navigation
247
- - `validationError` - Validation failures
248
-
249
- ## Custom Widgets
250
-
251
- All widgets must integrate with the event system:
252
-
253
- ```typescript
254
- import { WidgetProps } from '@rjsf/utils';
255
- import { useFieldEventHandlers } from 'extended-dynamic-forms/events';
256
-
257
- export const CustomWidget: FC<WidgetProps> = ({ id, value, onChange, schema }) => {
258
- const fieldEventHandlers = useFieldEventHandlers(id, schema);
259
-
260
- const handleChange = async (newValue) => {
261
- onChange(newValue); // RJSF update
262
- await fieldEventHandlers.onChange(newValue); // Event system
263
- };
264
-
265
- return <Input value={value} onChange={(e) => handleChange(e.target.value)} />;
91
+ title: 'Personal details',
92
+ required: ['name'],
93
+ properties: { name: { type: 'string', minLength: 1 } },
94
+ },
95
+ STEP_CONTACT: {
96
+ type: 'object',
97
+ title: 'Contact details',
98
+ properties: { email: { type: 'string', format: 'email' } },
99
+ },
100
+ },
266
101
  };
267
- ```
268
-
269
- ### Auto-registered by Default
270
- - All EDF custom widgets are included by default; no wiring is required.
271
- - Every widget is also exposed as a field, so you can use either `ui:widget` or `ui:field` with the same key.
272
-
273
- Examples:
274
-
275
- ```json
276
- {
277
- "schema": { "type": "string", "title": "First Name" },
278
- "uiSchema": { "ui:widget": "text" }
279
- }
280
- ```
281
102
 
282
- ```json
283
- {
284
- "schema": { "type": "string", "title": "First Name" },
285
- "uiSchema": { "ui:field": "text" }
103
+ export default function RegistrationForm() {
104
+ return (
105
+ <WizardForm
106
+ schema={schema}
107
+ uiSchema={{}}
108
+ onStepChange={(stepIndex, data, stepId) => {
109
+ console.log('Current step', stepIndex, stepId, data);
110
+ }}
111
+ onLiveChange={(flatData) => console.log('Current answers', flatData)}
112
+ onSubmit={({ formData, flatFormData }) => {
113
+ console.log('Accepted step answers', formData);
114
+ console.log('Accepted flat answers', flatFormData);
115
+ }}
116
+ />
117
+ );
286
118
  }
287
119
  ```
288
120
 
289
- ## JsonLogic Migration Examples
290
-
291
- Common patterns for migrating from function-based conditions:
292
-
293
- ```typescript
294
- // Equality
295
- // Before: (formData) => formData.field === "value"
296
- // After: { "==": [{ "var": "field" }, "value"] }
297
-
298
- // Boolean check
299
- // Before: (formData) => formData.isEnabled
300
- // After: { "var": "isEnabled" }
301
-
302
- // AND condition
303
- // Before: (formData) => formData.a && formData.b
304
- // After: { "and": [{ "var": "a" }, { "var": "b" }] }
305
-
306
- // Array contains
307
- // Before: (formData) => formData.roles?.includes("admin")
308
- // After: { "in": ["admin", { "var": "roles" }] }
309
- ```
310
-
311
- ## Available Widgets
312
-
313
- ### Complete Widget List
314
-
315
- #### Choice Widget (Unified Selection)
316
- - **ChoiceWidget** - Intelligent widget that automatically adapts based on schema:
317
- - Renders as dropdown/select for regular choices
318
- - Renders as radio group when `ui:widget` is "radio"
319
- - Renders as checkbox group for multi-select arrays
320
- - Supports static and dynamic data sources (API, WebSocket)
321
-
322
- #### Text Input Widgets
323
- - **TextWidget** - Standard text input with Ant Design styling
324
- - **TextareaWidget** - Multi-line text input
325
- - **PasswordWidget** - Password input with visibility toggle
121
+ Conditions use flat answer paths without the `STEP_` prefix. Initial answers may
122
+ be flat or nested under step keys; explicit nested snapshots take precedence.
123
+ Use distinct answer keys across steps when working with flat data.
326
124
 
327
- #### Specialized Input Widgets
328
- - **ColorWidget** - Color picker using HTML5 color input
329
- - **EmailWidget** - Email input with validation
330
- - **URLWidget** - URL input with validation
331
- - **TelephoneWidget** - Phone number input
332
- - **NumberWidget** - Numeric input with increment/decrement controls
125
+ Step visibility rules use `x-edf:stepRules`. Hidden steps are skipped in navigation
126
+ and excluded from structured submissions unless `includeHiddenStepsInSubmit` is
127
+ true; `flatFormData` retains the all-step context. `onStepChange` reports the
128
+ visible-step index; `onStepChangeDetailed` also includes the original index as
129
+ `allStepsIndex`. The last visible step can submit, and final submission validates
130
+ all visible steps, including an earlier step newly revealed by a later answer.
333
131
 
334
- #### Visual Input Widgets
335
- - **RangeWidget** / **SliderWidget** - Slider for numeric ranges
336
- - **RatingWidget** - Star rating component
132
+ Array positions and blank array entries are preserved through editing and
133
+ navigation. Supplied strings with directly declared `pattern`, positive
134
+ `minLength`, or a valid explicit `maxLength` retain their characters for
135
+ validation. Other nested/edit blank-string normalization remains in place.
136
+ JSON Schema `required` checks presence. A positive `minLength` rejects an empty
137
+ string; rejecting whitespace-only answers requires an appropriate non-whitespace
138
+ `pattern`. Malformed schema patterns block progression and show configuration
139
+ errors by default; hiding the error list does not bypass validation.
337
140
 
338
- #### Date & Time Widgets
339
- - **DateWidget** / **EnhancedDateWidget** - Date picker with Ant Design DatePicker
340
- - **DateTimeWidget** - Combined date and time picker
141
+ `LayoutGridField` is included for step layouts. Configure `ui:field` as
142
+ `LayoutGridField` and use `ui:layoutGrid` with Ant Design's 24-column spans and
143
+ gutters. Custom fields and templates remain overridable.
341
144
 
342
- #### File Upload Widgets
343
- - **FileWidget** - Basic file input
344
- - **FileUploadWidget** - Enhanced upload with progress, preview, and base64 support
145
+ ## Conditional logic
345
146
 
346
- ### Widget Configuration Examples
147
+ Put JsonLogic conditions and JSON Patch effects in `x-edf:rules` on the schema
148
+ or UI schema. Configuration is JSON-only; JavaScript source strings are not
149
+ executed.
347
150
 
348
- #### Basic Text Input
349
- ```json
350
- {
351
- "schema": {
352
- "type": "string",
353
- "title": "Full Name"
354
- },
355
- "uiSchema": {
356
- "ui:widget": "text",
357
- "ui:placeholder": "Enter your full name"
358
- }
359
- }
360
- ```
361
-
362
- #### Text Input with Variant and Auto-complete
363
- ```json
364
- {
365
- "schema": { "type": "string", "title": "Username" },
366
- "uiSchema": {
367
- "ui:widget": "text",
368
- "ui:options": {
369
- "variant": "outlined",
370
- "autoFocus": true,
371
- "autoComplete": "username",
372
- "allowClear": true
373
- }
374
- }
375
- }
376
- ```
377
-
378
- #### Date Range (RangePicker)
379
- ```json
380
- {
381
- "schema": {
382
- "type": "array",
383
- "title": "Date Range",
384
- "items": { "type": "string" },
385
- "minItems": 2,
386
- "maxItems": 2
387
- },
388
- "uiSchema": {
389
- "ui:widget": "date",
390
- "ui:options": {
391
- "range": true,
392
- "format": "YYYY-MM-DD",
393
- "placeholder": ["Start date", "End date"],
394
- "picker": "date"
395
- }
396
- }
397
- }
398
- ```
399
-
400
- With time selection:
401
- ```json
402
- {
403
- "schema": {
404
- "type": "array",
405
- "title": "Date Range With Time",
406
- "items": { "type": "string" },
407
- "minItems": 2,
408
- "maxItems": 2
409
- },
410
- "uiSchema": {
411
- "ui:widget": "date",
412
- "ui:options": {
413
- "range": true,
414
- "showTime": true,
415
- "format": "YYYY-MM-DD HH:mm",
416
- "placeholder": ["Start date/time", "End date/time"]
417
- }
418
- }
419
- }
420
- ```
151
+ For example, this schema rule adds a property when `accountType` is `premium`:
421
152
 
422
- Month range:
423
153
  ```json
424
154
  {
425
- "schema": {
426
- "type": "array",
427
- "title": "Month Range",
428
- "items": { "type": "string" },
429
- "minItems": 2,
430
- "maxItems": 2
155
+ "type": "object",
156
+ "properties": {
157
+ "accountType": { "type": "string", "enum": ["basic", "premium"] }
431
158
  },
432
- "uiSchema": {
433
- "ui:widget": "date",
434
- "ui:options": {
435
- "range": true,
436
- "picker": "month",
437
- "format": "YYYY-MM",
438
- "placeholder": ["Start month", "End month"]
159
+ "x-edf:rules": [
160
+ {
161
+ "name": "Premium details",
162
+ "condition": { "==": [{ "var": "accountType" }, "premium"] },
163
+ "effect": [
164
+ { "op": "add", "path": "/properties/premiumFeatures", "value": { "type": "string" } }
165
+ ]
439
166
  }
440
- }
167
+ ]
441
168
  }
442
169
  ```
443
170
 
444
- #### Number Input with Range
445
- ```json
446
- {
447
- "schema": {
448
- "type": "number",
449
- "title": "Age",
450
- "minimum": 0,
451
- "maximum": 120
452
- },
453
- "uiSchema": {
454
- "ui:widget": "number"
455
- }
456
- }
457
- ```
171
+ UI rules belong in `uiSchema['x-edf:rules']`; schema rules belong in
172
+ `schema['x-edf:rules']`. Array-item presentation can use `ui:dynamicItems` with
173
+ per-item JsonLogic. Conditional requiredness changes validation, not just a
174
+ visual required marker.
458
175
 
459
- #### Rating Widget
460
- ```json
461
- {
462
- "schema": {
463
- "type": "number",
464
- "title": "Rate your experience",
465
- "minimum": 0,
466
- "maximum": 5
467
- },
468
- "uiSchema": {
469
- "ui:widget": "rating",
470
- "ui:options": {
471
- "count": 5,
472
- "allowHalf": true
473
- }
474
- }
475
- }
476
- ```
176
+ ## Widgets and options
477
177
 
478
- #### Choice Widget (Dropdown)
479
- ```json
480
- {
481
- "schema": {
482
- "type": "string",
483
- "title": "Country",
484
- "enum": ["us", "uk", "au", "ca"],
485
- "enumNames": ["United States", "United Kingdom", "Australia", "Canada"]
486
- },
487
- "uiSchema": {
488
- "ui:widget": "choice"
489
- }
490
- }
491
- ```
178
+ Select a registered widget using `ui:widget`. The same registered key can also
179
+ be used as `ui:field`; an explicit field override controls its own rendering.
492
180
 
493
- #### Choice Widget with AntD props (Dropdown)
494
- ```json
495
- {
496
- "schema": { "type": "string", "title": "Country" },
497
- "uiSchema": {
498
- "ui:widget": "choice",
499
- "ui:options": {
500
- "choiceConfig": {
501
- "presentationMode": "dropdown",
502
- "dataSource": { "type": "static", "options": [
503
- { "label": "USA", "value": "us" }, { "label": "UK", "value": "uk" }
504
- ]},
505
- "antd": { "select": { "dropdownMatchSelectWidth": 280, "placement": "bottomLeft" } }
506
- }
507
- }
508
- }
509
- }
510
- ```
181
+ | Input | Registered keys |
182
+ | --- | --- |
183
+ | Text | `text`, `textarea`, `password` |
184
+ | Email, URL and phone | `email`, `url`, `tel`, `telephone` |
185
+ | Number | `number`, `updown` |
186
+ | Choices | `choice` with dropdown, radio or checkbox presentation |
187
+ | Date and time | `date`, `time`, `datetime-local` |
188
+ | Visual inputs | `color`, `range`, `slider`, `rating`, `switch` |
189
+ | Files | `file`, `fileUpload` |
190
+ | Display | `title`, `heading`, `html`, `markdown`, `image`, `alert`, `spacer`, `divider` |
511
191
 
512
- #### Choice Widget (Radio Group)
513
- ```json
514
- {
515
- "schema": {
516
- "type": "string",
517
- "title": "Gender",
518
- "enum": ["male", "female", "other"]
519
- },
520
- "uiSchema": {
521
- "ui:widget": "choice",
522
- "ui:options": {
523
- "presenter": "radio"
524
- }
525
- }
526
- }
527
- ```
192
+ Configure supported Ant Design props in `ui:options`. Container options belong
193
+ in `ui:options.formItem`; input styling and container styling are separate.
194
+ For the unified choice widget, use `ui:options.choiceConfig` for data sources,
195
+ presentation and presenter-specific Ant Design options.
528
196
 
529
- #### Choice Widget (Checkbox Group - Multi-select)
530
197
  ```json
531
198
  {
532
- "schema": {
533
- "type": "array",
534
- "title": "Interests",
535
- "items": {
536
- "type": "string",
537
- "enum": ["sports", "music", "reading", "travel"]
538
- },
539
- "uniqueItems": true
540
- },
541
- "uiSchema": {
542
- "ui:widget": "choice",
199
+ "name": {
200
+ "ui:widget": "text",
543
201
  "ui:options": {
544
- "presenter": "checkbox"
202
+ "allowClear": true,
203
+ "autoComplete": "name",
204
+ "variant": "outlined",
205
+ "style": { "fontSize": 18 },
206
+ "formItem": { "style": { "marginBottom": 24 } }
545
207
  }
546
- }
547
- }
548
- ```
549
-
550
- #### Date Picker
551
- ```json
552
- {
553
- "schema": {
554
- "type": "string",
555
- "title": "Date of Birth",
556
- "format": "date"
557
208
  },
558
- "uiSchema": {
209
+ "birthday": {
559
210
  "ui:widget": "date",
560
- "ui:options": {
561
- "format": "DD/MM/YYYY",
562
- "picker": "date"
563
- }
564
- }
565
- }
566
- ```
567
-
568
- #### File Upload
569
- ```json
570
- {
571
- "schema": {
572
- "type": "string",
573
- "title": "Resume",
574
- "format": "data-url"
575
- },
576
- "uiSchema": {
577
- "ui:widget": "fileUpload",
578
- "ui:options": {
579
- "accept": ".pdf,.doc,.docx",
580
- "maxSize": 5242880,
581
- "multiple": false
582
- }
583
- }
584
- }
585
- ```
586
-
587
- #### File Upload (Ant Upload options)
588
- ```json
589
- {
590
- "schema": { "type": "string", "title": "Profile Picture" },
591
- "uiSchema": {
592
- "ui:widget": "fileUpload",
593
- "ui:options": {
594
- "useAntUpload": true,
595
- "listType": "picture-card",
596
- "showUploadList": { "showPreviewIcon": true, "showRemoveIcon": true },
597
- "maxCount": 1,
598
- "accept": "image/*"
599
- }
600
- }
601
- }
602
- ```
603
-
604
- #### Color Picker
605
- ```json
606
- {
607
- "schema": {
608
- "type": "string",
609
- "title": "Favorite Color",
610
- "format": "color"
611
- },
612
- "uiSchema": {
613
- "ui:widget": "color"
614
- }
615
- }
616
- ```
617
-
618
- #### Range/Slider Widget
619
- ```json
620
- {
621
- "schema": {
622
- "type": "number",
623
- "title": "Volume",
624
- "minimum": 0,
625
- "maximum": 100
626
- },
627
- "uiSchema": {
628
- "ui:widget": "range",
629
- "ui:options": {
630
- "marks": {
631
- "0": "Mute",
632
- "50": "50%",
633
- "100": "Max"
634
- }
635
- }
636
- }
637
- }
638
- ```
639
-
640
- #### Hidden Field
641
- ```json
642
- {
643
- "schema": {
644
- "type": "string",
645
- "title": "User ID"
211
+ "ui:options": { "format": "DD/MM/YYYY", "disableFutureDates": true }
646
212
  },
647
- "uiSchema": {
648
- "ui:widget": "hidden"
213
+ "score": {
214
+ "ui:widget": "rating",
215
+ "ui:options": { "count": 5, "allowHalf": true }
649
216
  }
650
217
  }
651
218
  ```
652
219
 
653
- ### Widget Registration (optional)
654
-
655
- Widgets are auto-registered. You can still override or pass a subset if needed:
656
-
657
- ```typescript
658
- import { ExtendedForm, customWidgets } from 'extended-dynamic-forms';
659
-
660
- // Override or register specific widgets
661
- <ExtendedForm
662
- widgets={{
663
- text: customWidgets.text,
664
- date: customWidgets.date,
665
- choice: customWidgets.choice
666
- }}
667
- />
668
- ```
669
-
670
- ## Development
671
-
672
- ```bash
673
- # Development
674
- npm run dev # Start playground
675
- npm run test # Run tests
676
- npm run test:ui # Test with UI
677
-
678
- # Building
679
- npm run build # Build library
680
- npm run build:vanilla # Build standalone (currently broken - JsonLogic incompatible)
681
- npm run preview # Preview build
682
-
683
- # Code Quality
684
- npm run lint # ESLint
685
- npm run format # Prettier
686
- ```
687
-
688
- ## Project Structure
689
-
690
- ```
691
- src/
692
- ├── components/ # Core form components
693
- ├── conditionals/ # Declarative conditional engines (internal)
694
- │ └── v2/ # Current implementation
695
- ├── events/ # Event orchestration
696
- ├── layouts/ # Layout systems
697
- │ └── wizard/ # Wizard form components
698
- ├── widgets/ # Input widgets
699
- └── utils/ # Utilities
700
- ```
701
-
702
- ## Examples
703
-
704
- Interactive examples available at `/playground/src/demos/`:
705
-
706
- - **Wizard Forms**: Australian Company Onboarding, Employee Onboarding
707
- - **Conditionals**: Dynamic visibility, validation, array handling
708
- - **Events**: Webhook integration, field tracking
709
- - **Widgets**: All available components
710
-
711
- Run `npm run dev` to explore examples.
712
-
713
- ## Known Limitations
714
-
715
- 1. **Dependencies**: Requires RJSF source in sibling directory
716
- 2. **Installation**: Cannot install from npm
717
- 3. **Vanilla JS**: JsonLogic not supported in standalone builds
718
- 4. **Arrays**: No drag-and-drop functionality
719
- 5. **JSON Patch**: Only supports add/remove/replace operations
720
- 6. **Test Coverage**: Limited for UI components and events
721
-
722
- ## TypeScript
723
-
724
- Full TypeScript support with exported types:
725
-
726
- ```typescript
727
- import { ExtendedForm, WizardForm } from 'extended-dynamic-forms';
728
- // Config is JSON-only; rules are embedded via x-edf:rules
729
- ```
730
-
731
- ## Contributing
732
-
733
- 1. Fork the repository
734
- 2. Create your feature branch
735
- 3. Add tests for new functionality
736
- 4. Ensure all tests pass
737
- 5. Submit a pull request
738
-
739
- ## License
740
-
741
- MIT License - see LICENSE file for details.
220
+ - Choices can use schema enums or configured static/remote data sources. Single
221
+ choices emit scalars; multiple choices emit arrays. Remote paging is not implemented.
222
+ - Date options include `disablePastDates`, `disableFutureDates`,
223
+ `disableWeekends` and `range: true` for a range picker.
224
+ - Number options include `min`, `max`, `step`, `precision`, `groupSeparator`,
225
+ `decimalSeparator`, `controls`, `keyboard` and `changeOnWheel`. Display grouping
226
+ does not become part of numeric answers. Schema bounds validate independently
227
+ of input clamping.
228
+ - Exact decimals require `stringMode: true` with a string schema and suitable
229
+ string/decimal-aware validation. Numeric schemas convert values to JavaScript
230
+ numbers; numeric JSON Schema bounds do not validate decimal strings.
231
+ - Telephone `mask` uses `9` for a digit, `a` for an ASCII letter and `*` for any
232
+ character. Mask literals affect display, while answers retain digits and an
233
+ optional leading `+`. `useCountryMasks: true` enables country-pattern fallback;
234
+ an explicit field mask takes precedence. Letter slots are display-only.
235
+ - URL answers accept HTTP/HTTPS by default. `allowedProtocols` replaces those
236
+ defaults; `[]` rejects every nonempty URL. `javascript:`, `data:` and `vbscript:`
237
+ remain blocked even if listed. Link previews are restricted to HTTP/HTTPS.
238
+ - File upload options include `accept`, `maxSize`, `multiple` and Ant Upload
239
+ presentation controls. Use a schema compatible with the selected value mode.
240
+
241
+ Choice presenter-specific Ant Design options use `choiceConfig.antd`, such as
242
+ `choiceConfig.antd.select` for dropdown props.
243
+
244
+ ## Values and validation
245
+
246
+ ### Empty answers
247
+
248
+ The built-in Text widget emits `undefined` when cleared by default. Object
249
+ properties are omitted when serialized to JSON, including properties in array
250
+ items; callbacks may still contain undefined-valued own properties. Array rows
251
+ are not deleted or compacted. An explicit `emptyValue`, including `""` or `null`,
252
+ is an override and must satisfy the schema. Password, Textarea and other widget
253
+ empty defaults are not globally changed. Custom fields/widgets control their
254
+ own child-change behavior.
255
+
256
+ Stored/default answers are not rewritten on load. Turning off interaction
257
+ feedback does not disable Submit or Wizard Next validation. Change callbacks
258
+ can contain incomplete or invalid drafts; only accepted submissions should be
259
+ stored as completed responses.
260
+
261
+ ### Date and time answers
262
+
263
+ | Widget/schema | Accepted answer |
264
+ | --- | --- |
265
+ | Single Date with `format: "date"` | Calendar day, `YYYY-MM-DD` |
266
+ | Time with `format: "time"` | Wall-clock time, `HH:mm:ss`, without timezone conversion |
267
+ | DateTime | UTC ISO instant with `Z`, displayed in the respondent's browser-local timezone |
268
+ | Calendar Date Range (`date` with `range: true`) | Pair of ISO calendar days, even with plain string item schemas |
269
+
270
+ Date/Time display formats do not change those schema-formatted answers. Native
271
+ Date/Time string schemas without their respective `format` retain configured-format
272
+ output. Explicit `showTime` / `withTime` ranges retain formatted time-bearing
273
+ pairs rather than the UTC DateTime contract.
274
+
275
+ DateTime rejects nonexistent daylight-saving times and requires an explicit
276
+ occurrence choice for repeated local times. Restored valid offset-bearing instants
277
+ and fractional-second precision are preserved until edited. Zone-less or invalid
278
+ saved DateTime strings require re-entry; no timezone is guessed. Pending invalid
279
+ edits block submission and wizard progression, even on optional fields, and can
280
+ emit the `Invalid date-time` draft marker. DateTime clear emits `undefined`
281
+ regardless of an old `emptyValue` setting.
282
+
283
+ Calendar ranges reject impossible, reversed and incomplete pairs. Clear emits
284
+ `[]`; optional schemas must allow zero items if clear should be valid. Required
285
+ ranges reject `[]`. Explicit `allowEmpty` endpoint options also need schemas
286
+ that permit an empty string at that endpoint. Calendar bounds compare local days;
287
+ an explicit `disabledDate` callback takes precedence over convenience flags,
288
+ while explicit bounds still apply.
289
+
290
+ When upgrading from 0.3.x, review the date/time, empty-answer and URL contracts
291
+ against your schemas, callbacks and stored responses. Do not rewrite persisted
292
+ or in-progress answers automatically. See the changelog for version-specific changes.
293
+
294
+ ## Events and webhooks
295
+
296
+ `ExtendedForm` delivers `onFieldFocus`, `onFieldBlur` and `onFieldChange` events
297
+ with `fieldId` and `fieldValue`. `WizardForm` currently does not deliver those
298
+ field callback props; use `onLiveChange` to subscribe to wizard answer snapshots.
299
+ Use `onSubmit` for accepted form answers. Optional `webhooks` configure event
300
+ destinations, event names, debounce timing and retries. Keep endpoint authorization
301
+ and response-storage policy in your host application.
302
+
303
+ Custom widgets can import `useFieldEventHandlers` from the root package alongside
304
+ other event exports. Call the RJSF `onChange` to update the answer and the field
305
+ event handler to participate in EDF events.
306
+
307
+ ## Known limitations
308
+
309
+ - CommonJS `require` is unsupported; use ESM imports. NodeNext declaration
310
+ compatibility is not established; use the ESNext/Bundler configuration above.
311
+ - The separate vanilla JavaScript build lacks JsonLogic support. Use the React
312
+ package for declarative conditional forms.
313
+ - Arrays do not support drag-and-drop.
314
+ - JSON Patch effects support `add`, `remove` and `replace` only, not `move`, `copy` or `test`.
315
+
316
+ ## Links and license
317
+
318
+ - [npm package](https://www.npmjs.com/package/extended-dynamic-forms)
319
+
320
+ MIT. See [LICENSE](LICENSE).