extended-dynamic-forms 0.4.0 → 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 (2) hide show
  1. package/README.md +253 -1229
  2. package/package.json +1 -9
package/README.md CHANGED
@@ -1,1296 +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
- - Source development uses the pinned RJSF packages in `yarn.lock`; no sibling checkout is needed.
11
- - Source package version is `0.4.0`; a checkout or local tarball is not proof of registry publication.
12
- - JSON logic conditions don't work with vanilla JavaScript builds
13
- - Array drag-and-drop is not implemented
14
- - Automated coverage includes logic/snapshot suites and real jsdom wizard component regressions.
15
- - JSON Patch implementation supports add/remove/replace only (not move/copy/test)
16
-
17
- ## Overview
18
-
19
- Extended Dynamic Forms is an extension library for React JSON Schema Form (RJSF) v6 that adds:
20
-
21
- - **Declarative conditionals** with O(N) performance (rules + dynamic arrays)
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
26
-
27
- ### Ant Design Prop Pass-through
28
-
29
- 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.
30
-
31
- - DatePicker notes:
32
- - JSON-only flags: `disablePastDates`, `disableFutureDates`, `disableWeekends` (builds `disabledDate` internally)
33
- - Range support: set `ui:options.range: true` to render AntD `RangePicker` (see examples below)
34
-
35
- ### Text Input clear (BUGS-S0-R020, 0.4.0)
36
-
37
- This correction, including fixes added after the earlier !6 artifact, is part of
38
- 0.4.0 release preparation. Publication and Formstudio adoption remain separate gates.
39
-
40
- Clearing the built-in `text` / `TextWidget` now emits `undefined` by default.
41
- Object properties, including properties inside array items, are omitted when
42
- serialized as JSON. Undefined-valued own properties may still exist in callbacks.
43
- Required fields and invalid nonempty formats remain invalid; clearing an optional
44
- formatted Text Input no longer creates an invalid empty string.
45
-
46
- Defined `emptyValue` options remain explicit overrides, including `""`, `null`
47
- and other sentinels. They must satisfy the consumer's schema: an explicit empty
48
- string still fails an email format. Wizard blank-string normalization is retained
49
- unless a positive string `minLength`, a valid explicit `maxLength` (including
50
- zero), or an explicit string `pattern` (including the empty regex) constrains
51
- that property. See [Wizard explicit string minimums](#wizard-explicit-string-minimums),
52
- [Wizard supplied pattern strings](#wizard-supplied-pattern-strings), and
53
- [Wizard explicit string maximums](#wizard-explicit-string-maximums).
54
- An unconstrained widget's legacy empty string is not a promise of a
55
- final Wizard empty-string submission.
56
-
57
- Stored/default answers are not rewritten on load. Array positions and row
58
- identity are preserved; this is not a primitive-array deletion or compaction
59
- policy. The array-item integration forwards built-in TextWidget property clears
60
- as leaf paths, so batched sibling updates use current form state rather than a
61
- render-time object snapshot. It delegates host `ArraySchemaField`/`SchemaField`
62
- components and retains array row keys. Consumer-supplied fields/widgets remain
63
- authoritative; an overridden `ArrayField` owns its child-change policy.
64
- Password, Textarea and other widget empty defaults are unchanged.
65
- Traversal uses RJSF's effective schema-type resolver after reference resolution,
66
- including supported nullable array/object containers. Untouched explicit nulls
67
- are preserved; this is not global null normalization.
68
- Field authority is resolved before TextWidget eligibility at the leaf and each
69
- ancestor: normalized UI/global field options take precedence, then registered
70
- schema `$id`, then the type's registry field. Unknown UI field names fall through
71
- as in RJSF. A custom field selected by schema ID retains the same ordinary array
72
- handling as that field selected by `ui:field`; it does not inherit TextWidget's
73
- omission policy.
74
- Host item/schema renderers that replace TextWidget retain ordinary array
75
- undefined-to-null handling. Delegating renderers retain omission for the actual
76
- TextWidget they render, including public relative-path changes and batched host
77
- controls. The adapter checks committed field-scoped rendering and the public
78
- callback's target path, not widget IDs or schema metadata alone; replacement,
79
- unmount and row moves withdraw obsolete rendering authority.
80
-
81
- ### 0.4.0 adoption and migration
82
-
83
- `0.4.0` intentionally changes the Date, Time, DateTime and calendar Date Range
84
- serialization contracts described below. Treat this pre-1.0 minor release as a
85
- contract boundary, not a transparent patch to `0.3.13`. Consumers using `^0.3.x`
86
- do not receive `0.4.x` automatically.
87
-
88
- External/native integrators must explicitly adopt `0.4.x`, update their dependency
89
- lockfile, and check callback handlers, schema validation and submission storage
90
- against the relevant contract. Date/Time schemas without their respective
91
- `format` retain their documented native formatted-output exception. Calendar
92
- ranges use ISO dates even with plain string items; explicit `showTime` / `withTime`
93
- ranges retain their time-bearing contract. DateTime uses the UTC-instant contract
94
- for both Ant Design and native inputs, not a configurable floating-time mode.
95
-
96
- Do not automatically rewrite persisted or in-progress responses during an
97
- upgrade. Restore supported values without load-time change callbacks; legacy
98
- values that fail the applicable schema/instant contract require respondent
99
- re-entry rather than a guessed timezone or bulk normalization. Pending invalid
100
- edit callbacks are not accepted submissions. Existing non-production Formstudio
101
- forms need no migration infrastructure; this does not authorize changing external
102
- stored data.
103
-
104
- Formstudio adoption is a separate step **after published registry proof** of the
105
- new SDK. Pair the reviewed producer changes with the exact released dependency
106
- and lockfile, then verify the resolved package in the consumer. Local packed
107
- integration alone does not establish publication, authenticated hosted/managed
108
- behavior or deployed adoption. Never replace registry `0.3.13` with these bytes.
109
-
110
- Release preparation covers the declared ESM and browser UMD bundles, CSS and
111
- ESNext/Bundler declarations. The existing CommonJS/NodeNext packaging limitations
112
- and the separate vanilla build's missing entry are not repaired by this release.
113
-
114
- Only the browser UMD output resolves `process.env.NODE_ENV` to production at build
115
- time; script-tag hosts do not need a Node `process` shim. ESM retains its
116
- environment checks so consumers control development diagnostics and production
117
- behavior. The normal UMD peer globals remain required: `React`, `ReactDOM`,
118
- `React.jsxRuntime`, `antd` and `dayjs`.
119
-
120
- ### Single Date Picker values (ON-41, 0.4.0)
121
-
122
- For `ui:widget: "date"` with a scalar `schema.format: "date"`, selection emits
123
- `YYYY-MM-DD` to form data and field-change events. `ui:options.format` controls
124
- display only: `DD/MM/YYYY` displays `23/10/2026` but submits `2026-10-23`.
125
- The value is a calendar day, not a UTC timestamp; no timezone conversion is used.
126
- Clearing still emits the configured `emptyValue`.
127
-
128
- This intentionally corrects the previous, schema-invalid formatted output in
129
- `0.4.0`; it does not replace published `0.3.13`.
130
- Native string schemas without `format: "date"` retain their formatted output.
131
- Existing ISO dates and legacy values matching the configured string format are
132
- readable, including day-first dates. Loading does not rewrite stored answers:
133
- a legacy non-ISO answer still fails a date schema until the respondent selects
134
- a valid date. Impossible ISO dates are not rolled into a different calendar day.
135
- Consumers upgrading date-schema forms must expect canonical values in callbacks
136
- and submissions; do not rewrite saved/in-progress responses as an upgrade side effect.
137
-
138
- This change covers the single Date Picker only, not Time, DateTime or Date Range.
139
-
140
- ### Time Picker values (BUGS-S0-R272, 0.4.0)
141
-
142
- For `ui:widget: "time"` with `schema.format: "time"`, selection emits
143
- `HH:mm:ss` wall-clock values to form data, callbacks and submissions.
144
- `ui:options.format` controls display: `hh:mm A` displays `02:35 PM` while
145
- the value is `14:35:00`. No `Z`, offset or timezone conversion is added.
146
- Clearing retains the configured `emptyValue`.
147
-
148
- Canonical values and legacy strings matching the configured format are parsed
149
- strictly. Invalid values are not displayed as `Invalid Date` or rolled into a
150
- different time. Reading does not call `onChange` or rewrite saved answers:
151
- schema-invalid legacy values still require reselection before valid submission.
152
- The readonly native input shows the same clock time.
153
-
154
- Native string schemas without `format: "time"` retain configured-format output
155
- and prefer that format when reading ambiguous values. This approved time-schema
156
- contract correction requires explicit `0.4.x` consumer adoption; do not replace
157
- registry `0.3.13` or migrate saved/in-progress responses implicitly.
158
- It does not change DateTime or Date Range behavior.
159
-
160
- ### DateTime values (BUGS-S0-R273, 0.4.0)
161
-
162
- DateTime (`datetime`, `datetime-local`, `DateTimeWidget`, or the default
163
- `format: "date-time"` widget) captures and displays the respondent's **browser-local
164
- wall clock**. Every newly accepted selection emits a UTC ISO timestamp with `Z`,
165
- for example `2024-11-03T05:30:00.000Z`. `ui:options.format` controls presentation
166
- only. Ant Design, native `useNativeInput`, readonly display, plain forms and
167
- wizard validation share this instant contract. There is no form timezone setting.
168
-
169
- The widget rejects a nonexistent local time during a daylight-saving gap. For a
170
- repeated local time it requires an explicit **First occurrence** or **Second
171
- occurrence**, labelled with the corresponding UTC offset, before accepting an
172
- instant. A restored UTC/offset-bearing instant already identifies its occurrence;
173
- loading it does not call `onChange`, normalize storage or ask the respondent to
174
- choose again. Different browser timezones may display different calendar dates
175
- for the same instant.
176
-
177
- Valid restored fractional seconds may exceed three digits (for example `.1230Z`
178
- or `.123456789Z`). The original string is preserved on load and unchanged
179
- submission; millisecond-resolution controls do not rewrite stored precision.
180
- New respondent edits emit the standard ISO millisecond precision shown above.
181
-
182
- Invalid or zone-less saved strings require re-entry; their timezone is never
183
- guessed. Pending/invalid edits invalidate the previous form value and block submit
184
- and wizard progression, even on optional fields. During such an edit, change
185
- callbacks can contain the non-instant `Invalid date-time` marker and validation
186
- errors; do not persist it as an accepted answer. Only a valid, completed selection
187
- emits an instant. Clearing emits `undefined` (omitted in JSON), regardless of old
188
- DateTime `emptyValue` options; required clear remains invalid. Other widgets'
189
- empty sentinels and Date/Time/Range semantics are unchanged.
190
- Wizard `onLiveChange` publishes the same deletion-aware flat snapshot used
191
- internally: clearing removes the answer while retaining sibling and other-step
192
- data. Validation also covers positional tuple entries and additional items using
193
- their corresponding schema references and item UI schema, including explicit
194
- native DateTime widgets whose string schema has no `format`.
195
- The supported `ui:field` aliases obey the same contract and forward widget errors.
196
- For `oneOf`/`anyOf`, validation follows RJSF's active schema and corresponding
197
- branch UI, retaining explicit choices when branches match equally. Inactive text
198
- branches are not treated as DateTime. Wizard final submission validates every
199
- visible step with its own UI and retained branch choice, including earlier steps
200
- newly revealed by a final-step answer.
201
- Returning to a wizard step restores its actual rendered selector and nested/ref
202
- branch UI, not only a validation hint. Navigation preserves answers and emits no
203
- answer-change callback merely to restore a choice. Choices are scoped by step and
204
- nested branch; authored discriminators still determine matching when data differs.
205
- In arrays, choices follow RJSF's stable row identities through removal and both
206
- reorder directions, including equal-valued and nested rows. Removed-row choices
207
- are retired; added rows use normal matching/defaults. Answer/error paths remain
208
- positional, with no IDs added to responses. Registry and per-field
209
- `ArrayFieldTemplate` / `ArrayFieldItemTemplate` overrides retain their rendering
210
- and public row actions; identity tracking neither replays nor replaces callbacks.
211
-
212
- `disablePastDates`, `disableFutureDates`, `disableWeekends` and explicit date bounds
213
- compare browser-local calendar labels, not the UTC instant of a picker container.
214
- Picker selection, typed/native input and restored-answer validation agree.
215
- An explicit `disabledDate` callback takes precedence over convenience flags;
216
- bounds still apply. Callback calendar/clock fields are preserved, using a
217
- fixed-offset carrier for a skipped local date or clock rather than shifting it.
218
-
219
- This is the approved **clean DateTime cutover**, not an opt-in or floating-time
220
- mode. Existing non-production Formstudio forms need no migration infrastructure.
221
- External consumers must explicitly adopt `0.4.x` and account for the changed
222
- DateTime callbacks/submissions; never replace published `0.3.13` with these
223
- contents or rewrite external stored answers on load.
224
-
225
- ### Calendar Date Range values (BUGS-S0-R274, 0.4.0)
226
-
227
- `ui:widget: "date"` with `ui:options.range: true` reuses Ant Design's
228
- `RangePicker`, including RJSF array dispatch. Calendar endpoints are always
229
- `[YYYY-MM-DD, YYYY-MM-DD]`, even for native SDK schemas whose items are plain
230
- strings. `format` controls display/input only. Dates are calendar days, not
231
- instants: there is no UTC conversion or timezone shifting. Readonly mode displays
232
- both endpoints using native date inputs.
233
-
234
- Impossible dates, reversed pairs and incomplete pairs are rejected in plain forms
235
- and wizard progression. Invalid typed drafts invalidate the answer rather than
236
- submitting a previous valid pair; do not persist change callbacks as accepted
237
- submissions without validation. Typed/restored dates also obey picker constraints.
238
- Loading valid ISO data does not rewrite it or emit a change callback.
239
- Character-by-character endpoint edits retain their text and caret through parent
240
- updates while each partial draft still reaches change callbacks and validation.
241
- A genuine external answer replacement replaces the draft without replaying it.
242
- Explicit Clear discards both endpoint texts, including a partial draft entered
243
- into an already-empty range, and emits `[]` without replaying the discarded draft.
244
- Fresh typing starts from blank inputs; optional `[]` remains valid and required
245
- `[]` remains invalid.
246
- The same guards apply to the registered `date` / `DateWidget` field aliases
247
- (`ui:field` takes precedence over `ui:widget`) and the actually selected referenced
248
- `oneOf` / `anyOf` branch. Inactive text branches remain text, including through
249
- Wizard Back/final validation and stable array-row changes.
250
-
251
- Past/future restrictions compare calendar-day keys with the respondent's **local
252
- today**, not UTC-midnight containers with local-midnight instants. Picker cells,
253
- typed/restored validation and wizard progression use the same internal calendar
254
- constraint adapter as DateTime. Native min/max bounds retain their displayed
255
- calendar days; an explicit `disabledDate` callback still takes precedence over
256
- JSON flags, independently of those bounds. Its date and `info.from` preserve
257
- calendar and clock fields in local mode where representable; a locally skipped
258
- day or clock uses a separate fixed-offset carrier without mutating the picker's
259
- input. Range-only options and explicit with-time behavior remain unchanged.
260
- This does not repair the separate pre-existing full-form UI-option normalization
261
- of native Dayjs instances.
262
-
263
- Calendar clear emits `[]`: optional answers may submit it; required answers may
264
- not. An optional array schema must permit zero or two items (for example,
265
- `minItems: 0`, `maxItems: 2`, `not: { minItems: 1, maxItems: 1 }`).
266
- Explicit `allowEmpty: [true, false]` preserves `["", "YYYY-MM-DD"]`; only the
267
- allowed endpoint may be empty, and populated endpoints remain ISO. Native schema
268
- constraints still apply: `items.format: "date"` does not permit `""`; use matching
269
- tuple item schemas if an empty endpoint is intentional.
270
-
271
- Explicit `showTime` / `withTime` ranges remain outside this calendar cutover:
272
- they retain their existing formatted string pair and time information. No UTC
273
- DateTime policy is applied to them. Single Date, Time and DateTime are unchanged.
274
-
275
- This explicitly approved non-production cutover supersedes the old configured
276
- string calendar output; it is not a migration or compatibility mode. External
277
- native consumers must explicitly adopt `0.4.x` with this contract. Do not replace
278
- published `0.3.13`. Formstudio's reviewed producer change must be paired with
279
- the new SDK only after registry publication is proved; an older installed package
280
- or a local packed fixture is not proof of released consumer adoption.
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.
281
7
 
282
8
  ## Installation
283
9
 
284
- For this source checkout, use the maintained Yarn lockfile. The older npm lockfile
285
- still contains local RJSF references and is not a reproducible install for this tree.
286
-
287
10
  ```bash
288
- yarn install --frozen-lockfile
289
- npm run build
11
+ npm install extended-dynamic-forms
290
12
  ```
291
13
 
292
- For local consumer verification, run `npm pack --ignore-scripts` after building and
293
- install the resulting `extended-dynamic-forms-0.4.0.tgz` in an isolated consumer.
294
- The root version fields in the legacy npm lockfile match `0.4.0`; its stale
295
- dependency graph is deliberately unchanged and must not be used for this install.
296
- Package preparation does not authorize publication. See the adoption boundary
297
- above and the existing packaging limitations below.
298
-
299
- ## Testing
14
+ Install compatible peer dependencies if your application does not already have them:
300
15
 
301
- Vitest defaults to the **Node** environment for logic and serialized-output tests.
302
- `tests/integration/WizardForm.regression.test.tsx` opts into **jsdom** and mounts
303
- the real wizard, renderer, widgets, validator and Ant Design components.
304
-
305
- - Full suite: `npm test -- --run`
306
- - Wizard and step-rule regressions: `npm test -- --run tests/integration/WizardForm.regression.test.tsx tests/logic/conditionals/stepRules.test.ts`
307
- - Date selection, display/value round-trip and plain/wizard validation: `npm test -- --run tests/integration/DatePicker.regression.test.tsx`
308
- - Time selection, canonical/native values, strict parsing and required plain/wizard submission: `npm test -- --run tests/integration/TimePicker.regression.test.tsx`
309
- - Logic-only focus: `npm test -- --run tests/logic`
310
- - Real Chromium Text status/color and host-CSS regressions: `npm run test:browser`. This opt-in suite builds and packs the current SDK, builds an isolated public consumer and asserts rendered colors, token/neutral transitions, authored plain/addon-affix backgrounds and submission behavior; it is separate from the default Node/jsdom suite.
311
-
312
- The browser suite needs Chromium/Chrome (`CHROME_BIN` overrides discovery), `npm` and `tar`.
313
- It creates a fresh profile and consumer under `TMPDIR` and closes/removes only its own runtime.
314
- Set `EDF_BROWSER_ARTIFACT_DIR` to retain genuine PNG, DOM/raw observations and package/resource identities outside the temporary runtime.
315
-
316
- The plain-form array comparisons use `noHtml5Validate` so JSON Schema, rather
317
- than native HTML `required` attributes on array-item inputs, determines whether
318
- empty strings are valid.
319
-
320
- ## Core Architecture
321
-
322
- ### Declarative Conditionals (JSON-only)
323
-
324
- Embed rules directly inside your configs. EDF evaluates JsonLogic `condition` and applies JSON Patch `effect` internally. No hooks, functions, or classes in user code.
325
-
326
- - UI rules → `uiSchema['x-edf:rules']`
327
- - Schema rules → `schema['x-edf:rules']`
328
-
329
- Example (UI rules):
330
- ```json
331
- {
332
- "x-edf:rules": [
333
- {
334
- "name": "Show license number when has license",
335
- "condition": { "==": [{ "var": "hasDriversLicense" }, true] },
336
- "effect": [{ "op": "remove", "path": "/licenseNumber/ui:widget" }]
337
- }
338
- ]
339
- }
340
- ```
341
-
342
- Example (Schema rules):
343
- ```json
344
- {
345
- "x-edf:rules": [
346
- {
347
- "name": "Add premium fields",
348
- "condition": { "==": [{ "var": "accountType" }, "premium"] },
349
- "effect": [
350
- { "op": "add", "path": "/properties/premiumFeatures", "value": { "type": "object" } }
351
- ]
352
- }
353
- ]
354
- }
16
+ ```bash
17
+ npm install 'react@^18.0.0' 'react-dom@^18.0.0' 'antd@^5.24.0' 'dayjs@^1.11.13'
355
18
  ```
356
19
 
357
- ### Dynamic Arrays with O(N) Performance
358
-
359
- Use declarative `ui:dynamicItems` with per-item JsonLogic. No functions:
360
-
361
- ```json
362
- {
363
- "directors": {
364
- "items": {
365
- "ui:dynamicItems": {
366
- "base": { "shareholdingPercentage": { "ui:widget": "hidden" } },
367
- "rules": [
368
- {
369
- "condition": { "===": [{ "var": "hasShareholding" }, true] },
370
- "ui": { "shareholdingPercentage": { "ui:widget": "updown" } },
371
- "otherwise": { "shareholdingPercentage": { "ui:widget": "hidden" } }
372
- }
373
- ]
374
- }
375
- }
376
- }
377
- }
378
- ```
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.
379
23
 
380
- ## 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.
381
26
 
382
- Create wizard forms with automatic step detection:
27
+ ## Features
383
28
 
384
- ```typescript
385
- 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.
386
34
 
387
- <WizardForm
388
- schema={wizardSchema}
389
- uiSchema={uiSchema}
390
- formData={formData}
391
- onStepChange={(fromStep, toStep) => {
392
- console.log(`Step ${fromStep} → ${toStep}`);
393
- }}
394
- />
395
- ```
35
+ ## Basic usage
396
36
 
397
- **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';
398
41
 
399
- ```typescript
400
- // Schema uses STEP_ prefixes
401
- const schema = {
42
+ const schema: RJSFSchema = {
43
+ type: 'object',
44
+ required: ['name'],
402
45
  properties: {
403
- STEP_PERSONAL: {
404
- properties: {
405
- hasDriversLicense: { type: 'boolean' }
406
- }
407
- }
408
- }
46
+ name: { type: 'string', title: 'Name', minLength: 1 },
47
+ email: { type: 'string', title: 'Email', format: 'email' },
48
+ },
409
49
  };
410
50
 
411
- // Conditions refer to flattened form data (no STEP_ prefix)
412
- // Example rule (in uiSchema.x-edf:rules): { "==": [{ "var": "hasDriversLicense" }, true] }
413
- ```
414
-
415
- Note on wizard detection:
416
- - Wizard mode activates only when the root schema contains two or more properties whose keys start with `STEP_`.
417
- - With a single `STEP_…` property, the form renders as a single-page form (no progress/navigation UI).
418
- - To display steps UI, define at least two step objects (e.g., `STEP_PERSONAL`, `STEP_REVIEW`).
419
-
420
- ### Completion and array-value guarantees
421
-
422
- - The final visible step can submit without a following step. Submission
423
- validates every visible step snapshot against its step schema before calling
424
- `onSubmit`; a newly revealed invalid earlier step is activated for completion.
425
- - A trailing step skipped by `x-edf:stepRules` does not disable completion.
426
- Hidden steps are excluded from structured `formData` unless
427
- `includeHiddenStepsInSubmit` is enabled; `flatFormData` retains its existing
428
- all-step value context.
429
- - A root skip rule on a single-page form renders the no-visible-steps state,
430
- not inputs with a submit action that cannot complete.
431
- - Public `WizardLayout`/`StepNavigation` omit hidden steps from navigation and
432
- progress while retaining original step indices in `onStepChange`.
433
- - Supplied step data is available on the first render. Empty-string and whitespace
434
- array entries, nested arrays and array positions survive editing, step
435
- navigation, `onLiveChange`, and both structured and flat submission data.
436
- - Flat initial answers are assigned to their schema-owned step snapshots, so
437
- untouched answers appear in structured submission as well as `flatFormData`.
438
- Supplied flat values reach validation unchanged, including composed constraints.
439
- Explicit nested `STEP_*` snapshots retain precedence; editing or clearing an
440
- answer does not restore its initial value.
441
- - For nested snapshots and respondent edits, blank-string normalization outside
442
- arrays remains in place when no string `pattern`, positive `minLength`, or
443
- valid explicit `maxLength` applies. Explicit constraints follow the contracts below.
444
- Array contents are not compacted or normalized by the wizard.
445
-
446
- ### Wizard explicit string minimums
447
-
448
- 0.4.0 R139 correction: a string property with an explicit positive
449
- `minLength` retains supplied empty and whitespace-only strings for validation.
450
- An optional absent property remains optional, but an explicit `""` cannot bypass
451
- the minimum by being removed from the step snapshot. This applies on initial
452
- load, editing/clearing, Next, and final submission.
453
-
454
- Length counts the original characters, not digits or trimmed text. Nonempty
455
- Phone values and sibling callback data are not normalized by this correction.
456
- Blank normalization without a pattern, positive minimum, or valid explicit maximum,
457
- date empty defaults, and array positions remain unchanged.
458
-
459
- This correction is included in 0.4.0, not registry `0.3.13`.
460
- Publication and Formstudio adoption remain separate gates.
461
-
462
- ### Wizard supplied pattern strings
463
-
464
- 0.4.0 R138 correction: a string property with an explicit string `pattern`
465
- retains every supplied string unchanged for AJV validation, including `""` and
466
- whitespace-only strings. Nonmatching blanks block Next and final submission;
467
- matching blanks remain intact in live changes and both structured and flat
468
- submission data. This applies to restored values and editing/clearing, not only
469
- Phone fields. Native `pattern: ""` is an explicit regex matching all strings;
470
- an omitted pattern is distinct.
471
-
472
- An actually absent optional property remains absent. This does not add
473
- requiredness or a minimum, trim values, change widget clear defaults, or alter
474
- the positive-minimum contract above. A widget emitting absence is not treated
475
- as supplying an empty string.
476
-
477
- This correction is included in 0.4.0, not registry `0.3.13`.
478
- Publication and Formstudio adoption remain separate gates.
479
-
480
- ### Wizard explicit string maximums
481
-
482
- 0.4.0 R140 correction: a string property with a valid explicit nonnegative
483
- integer `maxLength` retains supplied strings unchanged for AJV validation,
484
- including empty and whitespace-only values. Eleven spaces under `maxLength: 10`
485
- and one space under `maxLength: 0` block Next and final submission, just as in a
486
- plain form. Permitted blanks and clears remain intact in live changes and both
487
- structured and flat submission data, including after back navigation.
488
-
489
- This deliberately changes required-plus-maximum blanks: JSON Schema `required`
490
- checks presence, so a supplied matching `""` or whitespace value is present
491
- rather than normalized to absence. Plain schema validation already accepts it.
492
- A genuinely missing required property still rejects. Use a positive `minLength`
493
- to reject empty strings, or an appropriate `pattern` to reject whitespace;
494
- neither content rule is added implicitly. Native HTML validation is separate.
495
-
496
- An absent optional property stays optional; an undefined value is not converted
497
- to a supplied string. This does not trim values, count only digits, change widget
498
- clear defaults, or change unconstrained/zero-minimum-only blank normalization.
499
- Array positions remain unchanged.
500
-
501
- This correction is included in 0.4.0, not registry `0.3.13`.
502
- Publication and Formstudio adoption remain separate gates.
503
-
504
- ### Wizard configuration errors
505
-
506
- In multi-step mode, invalid schema configuration (for example, a malformed
507
- `pattern`) blocks Next or Submit and shows the validator's root error by default.
508
- This does not change pattern matching or remove the malformed constraint.
509
- Ordinary answer-validation errors remain field-local by default.
510
-
511
- An explicit `showErrorList={false}` opts out of the root error list without
512
- allowing invalid progression; `showErrorList={true}` enables the list. Custom
513
- `templates.ErrorListTemplate` renders this feedback as it does other RJSF errors.
514
- Configuration feedback tracks schema-only edits on the same mounted Wizard,
515
- including when answers do not change. Correcting the schema clears configuration
516
- feedback and restores navigation without discarding current values or replaying
517
- answer-change callbacks. Initial and final step guards use the current validation
518
- result; the error list does not replace those guards.
51
+ const uiSchema: UiSchema = {
52
+ name: { 'ui:widget': 'text', 'ui:options': { allowClear: true } },
53
+ email: { 'ui:widget': 'email' },
54
+ };
519
55
 
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
+ }
69
+ ```
520
70
 
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.
521
75
 
522
- ### Step Layouts with LayoutGridField
76
+ ## Multi-step forms
523
77
 
524
- 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.
525
81
 
526
82
  ```tsx
527
83
  import { WizardForm } from 'extended-dynamic-forms';
84
+ import 'extended-dynamic-forms/dist/style.css';
528
85
 
529
86
  const schema = {
530
87
  type: 'object',
531
88
  properties: {
532
- STEP_LAYOUT: {
89
+ STEP_PERSONAL: {
533
90
  type: 'object',
534
- title: 'Personal Info',
535
- properties: {
536
- firstName: { type: 'string', title: 'First Name' },
537
- lastName: { type: 'string', title: 'Last Name' },
538
- age: { type: 'number', title: 'Age' },
539
- gender: { type: 'string', enum: ['Male','Female','Other'] },
540
- favoriteBook: { type: 'string' },
541
- }
542
- }
543
- }
544
- };
545
-
546
- const uiSchema = {
547
- STEP_LAYOUT: {
548
- 'ui:field': 'LayoutGridField',
549
- 'ui:layoutGrid': {
550
- 'ui:row': [
551
- { 'ui:row': { gutter: [16,16], children: [
552
- { 'ui:col': { span: 12, children: ['firstName'] } },
553
- { 'ui:col': { span: 12, children: ['lastName'] } }
554
- ]}},
555
- { 'ui:row': { gutter: [16,16], children: [
556
- { 'ui:col': { span: 24, children: ['age'] } }
557
- ]}},
558
- { 'ui:row': { gutter: [16,16], children: [
559
- { 'ui:col': { span: 24, children: ['gender'] } }
560
- ]}},
561
- { 'ui:row': { gutter: [16,16], children: [
562
- { 'ui:col': { span: 24, children: ['favoriteBook'] } }
563
- ]}}
564
- ]
565
- }
566
- }
567
- };
568
-
569
- // LayoutGridField is included by default in EDF; no extra wiring needed
570
- <WizardForm schema={schema} uiSchema={uiSchema} />
571
- ```
572
-
573
- Notes:
574
- - EDF includes `LayoutGridField` out of the box. Just set `ui:field: 'LayoutGridField'` on the step object.
575
- - 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.
576
- - You can still override or extend via the `fields` prop if needed.
577
- - 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.
578
-
579
- ## Event System & Webhooks
580
-
581
- Central event orchestration for all form interactions:
582
-
583
- ```typescript
584
- <ExtendedForm
585
- schema={schema}
586
- uiSchema={uiSchema}
587
- webhooks={[{
588
- url: 'https://api.example.com/events',
589
- events: ['change', 'blur'],
590
- debounceMs: 500,
591
- retries: 3
592
- }]}
593
- onFieldChange={(event) => console.log(event.fieldId, event.fieldValue)}
594
- />
595
- ```
596
-
597
- **Event Types:**
598
- - `focus` - Field focus
599
- - `blur` - Field blur
600
- - `change` - Value changes
601
- - `submit` - Form submission
602
- - `stepChange` - Wizard navigation
603
- - `validationError` - Validation failures
604
-
605
- ## Custom Widgets
606
-
607
- All widgets must integrate with the event system:
608
-
609
- ```typescript
610
- import { WidgetProps } from '@rjsf/utils';
611
- import { useFieldEventHandlers } from 'extended-dynamic-forms/events';
612
-
613
- export const CustomWidget: FC<WidgetProps> = ({ id, value, onChange, schema }) => {
614
- const fieldEventHandlers = useFieldEventHandlers(id, schema);
615
-
616
- const handleChange = async (newValue) => {
617
- onChange(newValue); // RJSF update
618
- await fieldEventHandlers.onChange(newValue); // Event system
619
- };
620
-
621
- 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
+ },
622
101
  };
623
- ```
624
-
625
- ### Auto-registered by Default
626
- - All EDF custom widgets are included by default; no wiring is required.
627
- - Every widget is also exposed as a field, so you can use either `ui:widget` or `ui:field` with the same key.
628
-
629
- Examples:
630
-
631
- ```json
632
- {
633
- "schema": { "type": "string", "title": "First Name" },
634
- "uiSchema": { "ui:widget": "text" }
635
- }
636
- ```
637
102
 
638
- ```json
639
- {
640
- "schema": { "type": "string", "title": "First Name" },
641
- "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
+ );
642
118
  }
643
119
  ```
644
120
 
645
- ## JsonLogic Migration Examples
646
-
647
- Common patterns for migrating from function-based conditions:
648
-
649
- ```typescript
650
- // Equality
651
- // Before: (formData) => formData.field === "value"
652
- // After: { "==": [{ "var": "field" }, "value"] }
653
-
654
- // Boolean check
655
- // Before: (formData) => formData.isEnabled
656
- // After: { "var": "isEnabled" }
657
-
658
- // AND condition
659
- // Before: (formData) => formData.a && formData.b
660
- // After: { "and": [{ "var": "a" }, { "var": "b" }] }
661
-
662
- // Array contains
663
- // Before: (formData) => formData.roles?.includes("admin")
664
- // After: { "in": ["admin", { "var": "roles" }] }
665
- ```
666
-
667
- ## Available Widgets
668
-
669
- ### Complete Widget List
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.
670
124
 
671
- #### Choice Widget (Unified Selection)
672
- - **ChoiceWidget** - Intelligent widget that automatically adapts based on schema:
673
- - Renders as dropdown/select for regular choices
674
- - Renders as radio group when `ui:widget` is "radio"
675
- - Renders as checkbox group for multi-select arrays
676
- - Supports static and dynamic data sources (API, WebSocket)
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.
677
131
 
678
- #### Text Input Widgets
679
- - **TextWidget** - Standard text input with Ant Design styling
680
- - **TextareaWidget** - Multi-line text input
681
- - **PasswordWidget** - Password input with visibility toggle
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.
682
140
 
683
- With the SDK's default `FieldTemplate`, Text, Email, URL and Password
684
- `ui:options.style` overrides the selected input's theme presentation.
685
- For example: `{ "ui:widget": "text", "ui:options": { "style": { "color": "#b4141e", "fontSize": 18 } } }`.
686
- Updating or removing this style does not change answers; styles stay scoped to
687
- that field, including nested fields. `ui:style` and `ui:options.formItem.style`
688
- remain container styling. A custom widget's explicit `options.style` takes
689
- precedence, and a host replacing `FieldTemplate` retains control of its styling.
690
- With addons, the complete style still reaches Ant Design's outer `Input.style`
691
- surface. Only explicit control presentation is additionally applied to the
692
- editable control: text color/font/spacing/alignment/decoration/caret, background
693
- paint, padding, and border color/style/width/radius (including physical/logical
694
- padding and border sides). Inputs with icons, prefix/suffix, clear or count use
695
- the affix control so inner padding is not applied twice.
696
- Other properties retain native outer ownership rather than being copied onto a
697
- second surface: sizing, margins, positioning/insets, transforms, layout/alignment
698
- and compositional effects are not duplicated. No properties are removed from
699
- the original `Input.style`; this rule also applies to explicit host
700
- `options.style`. Without addons, native control styling is unchanged.
701
-
702
- `PasswordWidget` uses the same field-scoped style and control-presentation rules,
703
- including its masked and revealed states. The default template supplies a style
704
- only for the matching field ID when the widget has no explicit `options.style`.
705
- An explicit host style wins as a complete object, including `{}`; parent/object
706
- styles do not become a nested password's control style. The password eye, clear,
707
- count, prefix and addons retain their native Ant Design behavior. Numeric zero
708
- values and the original style declaration order are preserved when presentation
709
- properties are mirrored onto the control. Style changes do not replace answers
710
- or synthesize change/submission callbacks.
711
- Recovered field typography also reaches the editable input inside an affix
712
- wrapper, so its own theme font does not override the author's font. Host
713
- semantic input properties remain unless the field overrides that property.
714
- Email uses the same borderless status-background fallback as Text; explicit
715
- backgrounds, disabled controls and host CSS retain their existing precedence.
716
-
717
-
718
- #### Specialized Input Widgets
719
- - **ColorWidget** - Color picker using HTML5 color input
720
- - **EmailWidget** - Email input with validation
721
- - **URLWidget** - URL input with validation
722
- - **TelephoneWidget** - Phone number input
723
- - **NumberWidget** - Numeric input with increment/decrement controls
724
-
725
- URLWidget honors an active `ui:options.type` on its existing native input
726
- (for example, `text` or `url`), independently of `inputMode`. TextWidget
727
- honors that override only on the existing URL route, identified by
728
- `ui:options.inputType: 'url'`. Other TextWidget routes, including utility
729
- Email/Phone and direct Text, retain their published native Text behavior.
730
- Omitted, `undefined`, `null` and empty-string types preserve each route's
731
- existing default: URL for URLWidget, Text for TextWidget. The `inputType`
732
- marker alone never supplies a native type.
733
-
734
- Changing HTML type can change native browser validity; it does not change
735
- schema type, URI format, pattern, Required, answers or callbacks. Explicit
736
- schema URL constraints still apply with HTML Text. Formstudio's URL author
737
- choices remain Text/URL. URL acceptance uses HTTP/HTTPS by default, independently
738
- of the HTML type and `validateUrl`. To allow extra schemes, set
739
- `ui:options.allowedProtocols`, for example `['http://', 'https://', 'mailto']`.
740
- Scheme names are case-insensitive and accept bare names, `:` or `://` suffixes.
741
- An explicit list replaces the defaults, and `[]` rejects every nonempty URL.
742
- `javascript:`, `data:` and `vbscript:` are always blocked, even when listed.
743
- Optional empty answers still follow the schema's existing validation.
744
- This applies to ExtendedForm submission, Wizard progression/final submission and
745
- the TextWidget URL route. Raw answers are not migrated or rewritten.
746
- After adopting this SDK change, existing forms with no protocol list reject
747
- non-HTTP(S) answers. Authors must explicitly allow other safe schemes before
748
- those answers are accepted. Link preview remains restricted to HTTP/HTTPS.
749
-
750
- With the default `FieldTemplate`, ordinary `tel`/`telephone` fields recover
751
- `ui:options.style` for that field's Phone control. Changing, clearing or restoring
752
- the style leaves the current answer and sibling fields unchanged. A custom
753
- widget's explicit native `options.style`, including `{}`, takes precedence;
754
- recovery requires a matching field ID, so a host-replaced child template cannot
755
- borrow an ancestor's style.
756
-
757
- For recovered Phone styles with addons, control presentation (text, background,
758
- padding and borders) also reaches the editable input or affix control through
759
- Ant Design's supported semantic styles. Layout and effects retain native outer
760
- `Input.style` ownership. Explicit native styles are not rewritten or mirrored.
761
- Intentional outer `Form.Item` presentation remains separate; outer and control
762
- borders/padding are not a single combined-border contract.
763
-
764
- Recovered Phone typography also reaches the editable input inside an affix
765
- wrapper, where Ant Design otherwise applies its own font rule. This includes
766
- the existing Phone-via-Text route (`inputType: 'tel'`), without changing its
767
- native Text input type or unrelated Text behavior. Host input semantic styles
768
- remain unless the selected field overrides the same typography property.
769
- Padding, borders and background remain on the visible control rather than
770
- being duplicated onto its inner input. Explicit native `options.style` and
771
- intentional outer styling retain their existing ownership.
772
-
773
- Exact inside Before/After placement preserves distinct prefix/suffix content
774
- and stable wrappers through clear; omitted/outside/unsupported markers retain
775
- the native outside slot. These placement options do not enter phone answers.
776
- Phone `ui:options.mask` uses `9` for a digit, `a` for an ASCII letter and `*`
777
- for any character. Other characters are display literals. For example,
778
- `+1 (999) 999-9999` displays `+1 (202) 555-0123` and emits `+12025550123`.
779
- A national mask uses the configured country code. An international mask includes
780
- its own calling-code digits. Explicit foreign numbers are preserved rather than
781
- silently changed to a fixed mask country.
782
-
783
- Letter slots are display-only (no phoneword conversion). They survive the current
784
- edit but are absent from saved answers and cannot be reconstructed on reload.
785
- Only `+` and digit content enter answers, events and validation. Masked answers
786
- use E.164 syntax plus the existing configured phone constraints. Empty optional
787
- answers retain existing empty-value behaviour. Display separators do not spend
788
- the canonical length budget. Backspace/Delete cross inserted literals.
789
-
790
- A non-empty mask with slots takes precedence over native formatter/parser
791
- callbacks and international grouping. Clearing the mask restores those existing
792
- paths. Invalid/non-string masks without slots are ignored. The default keyboard
793
- hint remains `tel`. Loading a saved answer does not rewrite stored data.
794
-
795
- Phone country entries accept an optional `pattern` in the same mask grammar.
796
- Set `ui:options.useCountryMasks: true` to use the selected/detected country's
797
- pattern as a fallback. False or unset keeps existing behaviour, including stored
798
- country names/codes and answers. An explicit field `mask` always takes priority.
799
- For example, `countries: [{ code: '+44', name: 'UK', pattern: '+44 99 9999 9999' }]`
800
- formats `+442079460018` as `+44 20 7946 0018`. Countries also accept JSON text.
801
- Missing/non-string patterns or patterns without slots keep the unmasked route.
802
- No regex or exact-length validation is inferred from a country display pattern.
803
- Existing canonical-phone feedback and explicit schema constraints still apply.
804
- Disabling the opt-in restores the prior display path without migrating answers.
805
-
806
- #### Visual Input Widgets
807
- - **RangeWidget** / **SliderWidget** - Slider for numeric ranges
808
- - **RatingWidget** - Star rating component
809
-
810
- #### Date & Time Widgets
811
- - **DateWidget** / **EnhancedDateWidget** - Date picker with Ant Design DatePicker
812
- - **DateTimeWidget** - Combined date and time picker
813
-
814
- #### File Upload Widgets
815
- - **FileWidget** - Basic file input
816
- - **FileUploadWidget** - Enhanced upload with progress, preview, and base64 support
817
-
818
- ### Widget Configuration Examples
819
-
820
- #### Basic Text Input
821
- ```json
822
- {
823
- "schema": {
824
- "type": "string",
825
- "title": "Full Name"
826
- },
827
- "uiSchema": {
828
- "ui:widget": "text",
829
- "ui:placeholder": "Enter your full name"
830
- }
831
- }
832
- ```
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.
833
144
 
834
- #### Text Input with Variant and Auto-complete
835
- ```json
836
- {
837
- "schema": { "type": "string", "title": "Username" },
838
- "uiSchema": {
839
- "ui:widget": "text",
840
- "ui:options": {
841
- "variant": "outlined",
842
- "autoFocus": true,
843
- "autoComplete": "username",
844
- "allowClear": true
845
- }
846
- }
847
- }
848
- ```
145
+ ## Conditional logic
849
146
 
850
- #### Date Range (RangePicker)
851
- ```json
852
- {
853
- "schema": {
854
- "type": "array",
855
- "title": "Date Range",
856
- "items": { "type": "string", "format": "date" },
857
- "minItems": 2,
858
- "maxItems": 2
859
- },
860
- "uiSchema": {
861
- "ui:widget": "date",
862
- "ui:options": {
863
- "range": true,
864
- "format": "YYYY-MM-DD",
865
- "placeholder": ["Start date", "End date"],
866
- "picker": "date"
867
- }
868
- }
869
- }
870
- ```
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.
871
150
 
872
- With time selection:
873
- ```json
874
- {
875
- "schema": {
876
- "type": "array",
877
- "title": "Date Range With Time",
878
- "items": { "type": "string" },
879
- "minItems": 2,
880
- "maxItems": 2
881
- },
882
- "uiSchema": {
883
- "ui:widget": "date",
884
- "ui:options": {
885
- "range": true,
886
- "showTime": true,
887
- "format": "YYYY-MM-DD HH:mm",
888
- "placeholder": ["Start date/time", "End date/time"]
889
- }
890
- }
891
- }
892
- ```
151
+ For example, this schema rule adds a property when `accountType` is `premium`:
893
152
 
894
- Month range:
895
153
  ```json
896
154
  {
897
- "schema": {
898
- "type": "array",
899
- "title": "Month Range",
900
- "items": { "type": "string" },
901
- "minItems": 2,
902
- "maxItems": 2
155
+ "type": "object",
156
+ "properties": {
157
+ "accountType": { "type": "string", "enum": ["basic", "premium"] }
903
158
  },
904
- "uiSchema": {
905
- "ui:widget": "date",
906
- "ui:options": {
907
- "range": true,
908
- "picker": "month",
909
- "format": "YYYY-MM",
910
- "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
+ ]
911
166
  }
912
- }
913
- }
914
- ```
915
-
916
- #### Number Input with Range
917
- ```json
918
- {
919
- "schema": {
920
- "type": "number",
921
- "title": "Age",
922
- "minimum": 0,
923
- "maximum": 120
924
- },
925
- "uiSchema": {
926
- "ui:widget": "number"
927
- }
928
- }
929
- ```
930
-
931
- #### Number grouping (BUGS-S0-R202, 0.4.0)
932
-
933
- The default `number` / `updown` NumberWidget supports an explicitly configured
934
- ordinary single-character `ui:options.groupSeparator`. For example:
935
-
936
- ```json
937
- {
938
- "ui:widget": "updown",
939
- "ui:options": {
940
- "groupSeparator": "_",
941
- "precision": 2,
942
- "decimalSeparator": "."
943
- }
944
- }
945
- ```
946
-
947
- `1234567.89` displays as `1_234_567.89`; editing `7_654_321.25` emits the
948
- number `7654321.25`, not formatted text. Grouping affects the integer part only.
949
- Native precision, step-derived precision and decimal normalization delegate to
950
- the declared public `@rc-component/mini-decimal` API used by Ant's numeric input;
951
- EDF does not replace that arithmetic or rewrite restored answers on render.
952
- Partial user typing is retained until native blur/step normalization.
953
-
954
- Absent, undefined or empty-string group options retain inactive native behavior.
955
- An active (truthy) native formatter or parser retains its existing path, as does
956
- an explicitly provided input `type`. Empty formatter/parser strings are inactive,
957
- matching native hooks; they do not disable literal grouping. This option neither
958
- overrides active hooks nor executes serialized JavaScript. Multi-character
959
- groups, numeric/sign/exponent/decimal-token ambiguity and broader locale or
960
- StringMode policies are not established here; those inputs retain native behavior
961
- rather than removing numeric characters. Custom fields/widgets remain host-owned.
962
-
963
- This correction is included in 0.4.0 release preparation.
964
- Publication and downstream adoption remain separate gates; do not republish
965
- different contents under an existing published version.
966
-
967
- #### Numeric controls and exact decimal values
968
-
969
- Number, Currency and Percentage presentations use the same `number` / `updown`
970
- widget. `ui:options.min` / `max` clamp input on blur; `step` controls increments.
971
- Schema `minimum`, `maximum`, `multipleOf` and numeric draft-7
972
- `exclusiveMinimum` / `exclusiveMaximum` independently validate submissions.
973
- `controls`, `keyboard` and `changeOnWheel` independently enable their respective
974
- input interactions; wheel changes require focus.
975
-
976
- Native integrations can preserve exact decimal strings with a **string schema**
977
- and `stringMode`, for example:
978
-
979
- ```json
980
- {
981
- "schema": {
982
- "type": "string",
983
- "title": "Exact decimal",
984
- "pattern": "^-?[0-9]+(?:\\.[0-9]+)?(?![\\s\\S])"
985
- },
986
- "uiSchema": {
987
- "ui:widget": "updown",
988
- "ui:options": { "stringMode": true }
989
- }
167
+ ]
990
168
  }
991
169
  ```
992
170
 
993
- This preserves values such as `"9007199254740993.123456789123456789"` as strings
994
- through editing and submission. It is not a numeric-schema migration:
995
- RJSF's numeric field converts values to JavaScript numbers, which can lose
996
- precision even with `stringMode`. JSON Schema numeric constraints do not validate
997
- strings; hosts choosing string responses must supply decimal-aware validation.
998
- Do not change existing numeric schemas or drop their constraints implicitly.
999
-
1000
- Native callable `formatter` / `parser` hooks remain supported. JavaScript source
1001
- text in JSON is not a callable hook, and EDF does not evaluate it. Formstudio's
1002
- author-entered formatter/parser execution requires separate approval.
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.
1003
175
 
1004
- Formstudio String Mode exports this string schema for Number, Currency and
1005
- Percentage. Fields without String Mode retain numeric schemas. Existing answers
1006
- are not migrated. Clearing String Mode emits an absent value unless `emptyValue`
1007
- is explicitly configured, so optional decimal fields remain submittable.
1008
- Numeric schema bounds and multiples do not validate strings.
1009
- UI min/max, step and explicitly configured precision still apply in the widget.
1010
- String Mode preserves decimal precision, while the numeric input normalises
1011
- spelling such as leading/trailing zeroes. The generated pattern accepts signed
1012
- plain decimals and rejects exponent notation, grouping, affixes and whitespace.
176
+ ## Widgets and options
1013
177
 
1014
- #### Rating Widget
1015
- ```json
1016
- {
1017
- "schema": {
1018
- "type": "number",
1019
- "title": "Rate your experience",
1020
- "minimum": 0,
1021
- "maximum": 5
1022
- },
1023
- "uiSchema": {
1024
- "ui:widget": "rating",
1025
- "ui:options": {
1026
- "count": 5,
1027
- "allowHalf": true
1028
- }
1029
- }
1030
- }
1031
- ```
1032
-
1033
- #### Choice Widget (Dropdown)
1034
- ```json
1035
- {
1036
- "schema": {
1037
- "type": "string",
1038
- "title": "Country",
1039
- "enum": ["us", "uk", "au", "ca"],
1040
- "enumNames": ["United States", "United Kingdom", "Australia", "Canada"]
1041
- },
1042
- "uiSchema": {
1043
- "ui:widget": "choice"
1044
- }
1045
- }
1046
- ```
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.
1047
180
 
1048
- #### Choice Widget with AntD props (Dropdown)
1049
- ```json
1050
- {
1051
- "schema": { "type": "string", "title": "Country" },
1052
- "uiSchema": {
1053
- "ui:widget": "choice",
1054
- "ui:options": {
1055
- "choiceConfig": {
1056
- "presentationMode": "dropdown",
1057
- "dataSource": { "type": "static", "options": [
1058
- { "label": "USA", "value": "us" }, { "label": "UK", "value": "uk" }
1059
- ]},
1060
- "antd": { "select": { "dropdownMatchSelectWidth": 280, "placement": "bottomLeft" } }
1061
- }
1062
- }
1063
- }
1064
- }
1065
- ```
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` |
1066
191
 
1067
- #### Choice Widget (Radio Group)
1068
- ```json
1069
- {
1070
- "schema": {
1071
- "type": "string",
1072
- "title": "Gender",
1073
- "enum": ["male", "female", "other"]
1074
- },
1075
- "uiSchema": {
1076
- "ui:widget": "choice",
1077
- "ui:options": {
1078
- "presenter": "radio"
1079
- }
1080
- }
1081
- }
1082
- ```
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.
1083
196
 
1084
- #### Choice Widget (Checkbox Group - Multi-select)
1085
197
  ```json
1086
198
  {
1087
- "schema": {
1088
- "type": "array",
1089
- "title": "Interests",
1090
- "items": {
1091
- "type": "string",
1092
- "enum": ["sports", "music", "reading", "travel"]
1093
- },
1094
- "uniqueItems": true
1095
- },
1096
- "uiSchema": {
1097
- "ui:widget": "choice",
199
+ "name": {
200
+ "ui:widget": "text",
1098
201
  "ui:options": {
1099
- "presenter": "checkbox"
202
+ "allowClear": true,
203
+ "autoComplete": "name",
204
+ "variant": "outlined",
205
+ "style": { "fontSize": 18 },
206
+ "formItem": { "style": { "marginBottom": 24 } }
1100
207
  }
1101
- }
1102
- }
1103
- ```
1104
-
1105
- #### Date Picker
1106
- ```json
1107
- {
1108
- "schema": {
1109
- "type": "string",
1110
- "title": "Date of Birth",
1111
- "format": "date"
1112
208
  },
1113
- "uiSchema": {
209
+ "birthday": {
1114
210
  "ui:widget": "date",
1115
- "ui:options": {
1116
- "format": "DD/MM/YYYY",
1117
- "picker": "date"
1118
- }
1119
- }
1120
- }
1121
- ```
1122
-
1123
- #### File Upload
1124
- ```json
1125
- {
1126
- "schema": {
1127
- "type": "string",
1128
- "title": "Resume",
1129
- "format": "data-url"
1130
- },
1131
- "uiSchema": {
1132
- "ui:widget": "fileUpload",
1133
- "ui:options": {
1134
- "accept": ".pdf,.doc,.docx",
1135
- "maxSize": 5242880,
1136
- "multiple": false
1137
- }
1138
- }
1139
- }
1140
- ```
1141
-
1142
- #### File Upload (Ant Upload options)
1143
- ```json
1144
- {
1145
- "schema": { "type": "string", "title": "Profile Picture" },
1146
- "uiSchema": {
1147
- "ui:widget": "fileUpload",
1148
- "ui:options": {
1149
- "useAntUpload": true,
1150
- "listType": "picture-card",
1151
- "showUploadList": { "showPreviewIcon": true, "showRemoveIcon": true },
1152
- "maxCount": 1,
1153
- "accept": "image/*"
1154
- }
1155
- }
1156
- }
1157
- ```
1158
-
1159
- #### Color Picker
1160
- ```json
1161
- {
1162
- "schema": {
1163
- "type": "string",
1164
- "title": "Favorite Color",
1165
- "format": "color"
211
+ "ui:options": { "format": "DD/MM/YYYY", "disableFutureDates": true }
1166
212
  },
1167
- "uiSchema": {
1168
- "ui:widget": "color"
1169
- }
1170
- }
1171
- ```
1172
-
1173
- #### Range/Slider Widget
1174
- ```json
1175
- {
1176
- "schema": {
1177
- "type": "number",
1178
- "title": "Volume",
1179
- "minimum": 0,
1180
- "maximum": 100
1181
- },
1182
- "uiSchema": {
1183
- "ui:widget": "range",
1184
- "ui:options": {
1185
- "marks": {
1186
- "0": "Mute",
1187
- "50": "50%",
1188
- "100": "Max"
1189
- }
1190
- }
1191
- }
1192
- }
1193
- ```
1194
-
1195
- #### Hidden Field
1196
- ```json
1197
- {
1198
- "schema": {
1199
- "type": "string",
1200
- "title": "User ID"
1201
- },
1202
- "uiSchema": {
1203
- "ui:widget": "hidden"
213
+ "score": {
214
+ "ui:widget": "rating",
215
+ "ui:options": { "count": 5, "allowHalf": true }
1204
216
  }
1205
217
  }
1206
218
  ```
1207
219
 
1208
- ### Widget Registration (optional)
1209
-
1210
- Widgets are auto-registered. You can still override or pass a subset if needed:
1211
-
1212
- ```typescript
1213
- import { ExtendedForm, customWidgets } from 'extended-dynamic-forms';
1214
-
1215
- // Override or register specific widgets
1216
- <ExtendedForm
1217
- widgets={{
1218
- text: customWidgets.text,
1219
- date: customWidgets.date,
1220
- choice: customWidgets.choice
1221
- }}
1222
- />
1223
- ```
1224
-
1225
- ## Development
1226
-
1227
- ```bash
1228
- # Development
1229
- npm run dev # Start playground
1230
- npm run test # Run tests
1231
- npm run test:ui # Test with UI
1232
-
1233
- # Building
1234
- npm run build # Build library
1235
- npm run build:vanilla # Build standalone (currently broken - JsonLogic incompatible)
1236
- npm run preview # Preview build
1237
-
1238
- # Code Quality
1239
- npm run lint # ESLint
1240
- npm run format # Prettier
1241
- ```
1242
-
1243
- ## Project Structure
1244
-
1245
- ```
1246
- src/
1247
- ├── components/ # Core form components
1248
- ├── conditionals/ # Declarative conditional engines (internal)
1249
- │ └── v2/ # Current implementation
1250
- ├── events/ # Event orchestration
1251
- ├── layouts/ # Layout systems
1252
- │ └── wizard/ # Wizard form components
1253
- ├── widgets/ # Input widgets
1254
- └── utils/ # Utilities
1255
- ```
1256
-
1257
- ## Examples
1258
-
1259
- Interactive examples available at `/playground/src/demos/`:
1260
-
1261
- - **Wizard Forms**: Australian Company Onboarding, Employee Onboarding
1262
- - **Conditionals**: Dynamic visibility, validation, array handling
1263
- - **Events**: Webhook integration, field tracking
1264
- - **Widgets**: All available components
1265
-
1266
- Run `npm run dev` to explore examples.
1267
-
1268
- ## Known Limitations
1269
-
1270
- 1. **Dependency installation**: The legacy npm lockfile is stale; use `yarn.lock` for this checkout.
1271
- 2. **CommonJS packaging**: `require` resolves to `.umd.js` inside a `"type": "module"` package; a Node consumer returned no named SDK exports. ESM imports work. This pre-existing packaging defect is not repaired by the wizard fixes.
1272
- 3. **Vanilla JS**: JsonLogic not supported in standalone builds
1273
- 4. **Arrays**: No drag-and-drop functionality
1274
- 5. **JSON Patch**: Only supports add/remove/replace operations
1275
- 6. **Test Coverage**: Limited for UI components and events
1276
-
1277
- ## TypeScript
1278
-
1279
- Full TypeScript support with exported types:
1280
-
1281
- ```typescript
1282
- import { ExtendedForm, WizardForm } from 'extended-dynamic-forms';
1283
- // Config is JSON-only; rules are embedded via x-edf:rules
1284
- ```
1285
-
1286
- ## Contributing
1287
-
1288
- 1. Fork the repository
1289
- 2. Create your feature branch
1290
- 3. Add tests for new functionality
1291
- 4. Ensure all tests pass
1292
- 5. Submit a pull request
1293
-
1294
- ## License
1295
-
1296
- 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).