dynamic-mui 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -37
- package/dist/accordion-DqBdGRim.mjs +23 -0
- package/dist/accordion-lvbD_BpX.js +1 -0
- package/dist/addressfield-CGX3UppE.mjs +152 -0
- package/dist/addressfield-DDG15fqJ.js +1 -0
- package/dist/asyncautocomplete-BAfBR9P7.mjs +89 -0
- package/dist/asyncautocomplete-w9q_1DWt.js +1 -0
- package/dist/bar-BzO7tvYJ.mjs +20 -0
- package/dist/bar-DEVxyUVS.js +1 -0
- package/dist/cascadeselect-C3QVfoig.mjs +64 -0
- package/dist/cascadeselect-DzbhK9en.js +1 -0
- package/dist/colorpicker-5qmEq7ZI.js +1 -0
- package/dist/colorpicker-DhNZulJY.mjs +98 -0
- package/dist/components/DynamicComponent.d.ts +2 -0
- package/dist/components/FormGenerator.d.ts +123 -2
- package/dist/components/controls/AsyncAutocomplete/asyncautocomplete.d.ts +9 -0
- package/dist/components/controls/CheckBox/checkbox.d.ts +1 -1
- package/dist/components/controls/ChipSelect/chipselect.d.ts +1 -1
- package/dist/components/controls/EditableTable/editabletable.d.ts +2 -0
- package/dist/components/controls/IntlPhone/intlphone.d.ts +2 -0
- package/dist/components/controls/LocationField/locationfield.d.ts +1 -1
- package/dist/components/controls/MultiTextbox/multitextbox.d.ts +1 -1
- package/dist/components/controls/NpsScale/npsscale.d.ts +3 -0
- package/dist/components/controls/NumberField/numberfield.d.ts +1 -1
- package/dist/components/controls/PasswordField/passwordfield.d.ts +1 -1
- package/dist/components/controls/Radio/radio.d.ts +1 -1
- package/dist/components/controls/RichTextEditor/richtexteditor.d.ts +3 -0
- package/dist/components/controls/Select/select.d.ts +1 -1
- package/dist/components/controls/Switch/switch.d.ts +1 -1
- package/dist/components/controls/TextField/textfield.d.ts +1 -1
- package/dist/components/controls/index.d.ts +45 -69
- package/dist/consentfield-B7Rm8d3U.js +1 -0
- package/dist/consentfield-CBQUR4J5.mjs +78 -0
- package/dist/datatable-BIQznwQe.mjs +20 -0
- package/dist/datatable-BWlcUP0M.js +1 -0
- package/dist/dynamic-mui.cjs +1 -0
- package/dist/dynamic-mui.mjs +32 -0
- package/dist/editabletable-DCoRmsEO.js +1 -0
- package/dist/editabletable-DRlBV8lk.mjs +126 -0
- package/dist/fileupload-ByBv1UjT.mjs +172 -0
- package/dist/fileupload-Cze3Oc_2.js +1 -0
- package/dist/formrepeater-BBkKGLFJ.mjs +71 -0
- package/dist/formrepeater-BxJzXoHx.js +1 -0
- package/dist/formwizard-BDZE1UDD.js +1 -0
- package/dist/formwizard-D2u1bMvK.mjs +83 -0
- package/dist/geofield-8WTeZk0Z.mjs +104 -0
- package/dist/geofield-Bw3p4NLJ.js +1 -0
- package/dist/group-B2mSylHd.mjs +23 -0
- package/dist/group-BlfO3ZZf.js +1 -0
- package/dist/imagelist-Bn9QhIv7.js +1 -0
- package/dist/imagelist-rqmJajHt.mjs +18 -0
- package/dist/index-CddjLTWF.mjs +12499 -0
- package/dist/index-D08GIfqF.js +43 -0
- package/dist/index.d.ts +11 -1
- package/dist/intlphone-B_Xn98pU.js +1 -0
- package/dist/intlphone-DtQZ12xk.mjs +140 -0
- package/dist/keyvaluefield-B_1NeGuP.mjs +125 -0
- package/dist/keyvaluefield-DyKCGd32.js +1 -0
- package/dist/line-B-DwR1hm.mjs +20 -0
- package/dist/line-BkymZu31.js +1 -0
- package/dist/lineitemlist-DGo8UOZC.js +1 -0
- package/dist/lineitemlist-vGKzJlQx.mjs +166 -0
- package/dist/list-7uNWrepS.mjs +20 -0
- package/dist/list-C0FgmtQw.js +1 -0
- package/dist/locationfield-CeUgxLkQ.mjs +119 -0
- package/dist/locationfield-Duyw1ZEz.js +1 -0
- package/dist/markdowneditor-B1y-hfny.mjs +90 -0
- package/dist/markdowneditor-Db0Un1_Z.js +2 -0
- package/dist/matrixfield-C7Sc-YpI.mjs +117 -0
- package/dist/matrixfield-CckODuPn.js +1 -0
- package/dist/mixChart-C3o184UT.js +1 -0
- package/dist/mixChart-ISf3Kbr-.mjs +75 -0
- package/dist/multitextbox-BYazIAeG.js +1 -0
- package/dist/multitextbox-ide2lvJ6.mjs +78 -0
- package/dist/npsscale-C2e88qk_.js +1 -0
- package/dist/npsscale-lndz7GEq.mjs +87 -0
- package/dist/numberstepper-DitbcvV9.mjs +128 -0
- package/dist/numberstepper-DyRjQM_K.js +1 -0
- package/dist/pie-CJSL8Sv5.js +1 -0
- package/dist/pie-CL29dlh5.mjs +20 -0
- package/dist/richtexteditor-CVg-MnKp.js +1 -0
- package/dist/richtexteditor-DsC1XC7h.mjs +141 -0
- package/dist/s3Upload-B0JeoLkS.js +1 -0
- package/dist/s3Upload-dpioZ2c8.mjs +51 -0
- package/dist/schema.d.ts +404 -0
- package/dist/signature-14OrZriO.mjs +1225 -0
- package/dist/signature-D582X6tB.js +2 -0
- package/dist/stepper-B-FpcZvT.js +1 -0
- package/dist/stepper-CDfEy_C6.mjs +140 -0
- package/dist/summaryfield-B08ZmVHh.js +1 -0
- package/dist/summaryfield-CTpPlZBG.mjs +96 -0
- package/dist/tabs-BIliqFOs.js +1 -0
- package/dist/tabs-DDeJCFt8.mjs +39 -0
- package/dist/tagsinput-D-q6rRZS.mjs +58 -0
- package/dist/tagsinput-qh_lhdFb.js +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/util/registry.d.ts +29 -0
- package/dist/util/resolvers.d.ts +45 -0
- package/dist/util/useFormEngine.d.ts +74 -0
- package/dist/util/validateSchema.d.ts +23 -0
- package/dist/v4-BysszJq8.mjs +30 -0
- package/dist/v4-CqdK_b8Y.js +1 -0
- package/package.json +36 -11
- package/dist/XmlShapeDeserializer-3yQdm0b0.mjs +0 -237
- package/dist/XmlShapeDeserializer-XKJAoqLc.js +0 -1
- package/dist/date-utils-CFUUt6NL.js +0 -4
- package/dist/date-utils-pGzaKb0p.mjs +0 -477
- package/dist/dynamic-mui.cjs.js +0 -469
- package/dist/dynamic-mui.es.js +0 -102550
- package/dist/index-BW8_rRff.js +0 -7
- package/dist/index-BXFVK_GX.js +0 -1
- package/dist/index-CVqLvm8B.js +0 -1
- package/dist/index-Cr6Wp_S-.mjs +0 -3141
- package/dist/index-DA0XouZs.mjs +0 -153
- package/dist/index-DKS5Sz9e.js +0 -14
- package/dist/index-DcWfyXJp.mjs +0 -92
- package/dist/index-MllWgTJ5.mjs +0 -9372
- package/dist/index.browser-BSu11T_q.mjs +0 -363
- package/dist/index.browser-By7-GABP.js +0 -4
- package/dist/loadCognitoIdentity-DtYTy-DW.js +0 -1
- package/dist/loadCognitoIdentity-DzdR91GB.mjs +0 -872
- package/dist/loadSts-Cic6UToN.mjs +0 -593
- package/dist/loadSts-csbFP6zX.js +0 -1
- package/dist/main.d.ts +0 -1
- package/dist/noAuth-B8ui2kBg.mjs +0 -71
- package/dist/noAuth-JPA2o4dt.js +0 -1
- package/dist/playground/AIContext.d.ts +0 -18
- package/dist/playground/AIPanel.d.ts +0 -10
- package/dist/playground/Builder.d.ts +0 -1
- package/dist/playground/Canvas.d.ts +0 -9
- package/dist/playground/PropertiesEditor.d.ts +0 -10
- package/dist/playground/Sidebar.d.ts +0 -1
- package/dist/playground/documentation.d.ts +0 -6
- package/dist/playground/templates.d.ts +0 -7
- package/dist/playground/testData.d.ts +0 -2
- package/dist/test/setup.d.ts +0 -0
- package/dist/theme/theme.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,67 +1,385 @@
|
|
|
1
1
|
# dynamic-mui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Build rich, dynamic forms in React from a JSON schema — powered by Material UI.**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **New Documentation Mode**: Integrated documentation sidebar with component details.
|
|
10
|
-
- **Advanced Validation**: Mandatory field validation with visual indicators (`*` and red error states).
|
|
11
|
-
- **Rich Component Library**: Includes TextField, Select, Date/Time Pickers, Checkbox, Radio, Switch, Signature, and more.
|
|
12
|
-
- **JSON Editing**: Edit form schema directly or use the visual builder.
|
|
13
|
-
- **Data Preview**: View current form data in real-time.
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
5
|
+
Render complex, reactive forms without writing form code. Fields can show/hide,
|
|
6
|
+
enable/disable, become required, compute their value, and drive each other's
|
|
7
|
+
options — all declaratively from data. Comes with 45+ controls, a typed schema
|
|
8
|
+
helper, an imperative API, i18n, and an optional visual builder / AI module.
|
|
16
9
|
|
|
17
10
|
```bash
|
|
11
|
+
npm install dynamic-mui
|
|
12
|
+
# peer dependencies (if you don't already have them)
|
|
18
13
|
npm install @mui/material @emotion/react @emotion/styled
|
|
19
|
-
npm install dynamic-mui --save
|
|
20
14
|
```
|
|
21
15
|
|
|
22
|
-
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
23
19
|
|
|
24
20
|
```tsx
|
|
25
|
-
import { FormGenerator } from 'dynamic-mui';
|
|
21
|
+
import { FormGenerator, FormData } from 'dynamic-mui';
|
|
26
22
|
|
|
27
|
-
const
|
|
23
|
+
const schema = [
|
|
28
24
|
{
|
|
29
25
|
type: 'textfield',
|
|
26
|
+
props: { id: 'name', MuiAttributes: { label: 'Full name', fullWidth: true } },
|
|
27
|
+
rules: { validation: [{ rule: 'mandatory', message: 'Name is required' }] },
|
|
28
|
+
layout: { row: 1, xs: 12, md: 6 },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'select',
|
|
30
32
|
props: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
rules: {
|
|
35
|
-
validation: [{ rule: 'mandatory', message: 'Name is required' }]
|
|
36
|
-
}
|
|
33
|
+
id: 'role',
|
|
34
|
+
options: [{ value: 'dev', label: 'Developer' }, { value: 'design', label: 'Designer' }],
|
|
35
|
+
MuiBoxAttributes: { label: 'Role' },
|
|
37
36
|
},
|
|
38
|
-
layout: { row: 1, xs: 12, md: 6 }
|
|
39
|
-
}
|
|
37
|
+
layout: { row: 1, xs: 12, md: 6 },
|
|
38
|
+
},
|
|
40
39
|
];
|
|
41
40
|
|
|
42
|
-
export default function
|
|
43
|
-
const handleSubmit = (data) => {
|
|
44
|
-
console.log('Form Data:', data);
|
|
45
|
-
};
|
|
46
|
-
|
|
41
|
+
export default function MyForm() {
|
|
47
42
|
return (
|
|
48
43
|
<FormGenerator
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
guid="my-form"
|
|
45
|
+
data={schema}
|
|
46
|
+
onSubmit={(values, errors) => {
|
|
47
|
+
if (errors.length === 0) console.log(FormData('my-form'));
|
|
48
|
+
}}
|
|
51
49
|
/>
|
|
52
50
|
);
|
|
53
51
|
}
|
|
54
52
|
```
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
Every field is an object of `{ type, props, layout, rules?, ...engine }`.
|
|
55
|
+
`layout` uses MUI Grid units (`row`, `xs`, `sm`, `md`, `lg`).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Reading & writing form data
|
|
60
|
+
|
|
61
|
+
Data lives in a store keyed by the form's `guid`:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { FormData, ClearFormData, useForm } from 'dynamic-mui';
|
|
65
|
+
|
|
66
|
+
FormData('my-form'); // { name: 'Ada', role: 'dev' }
|
|
67
|
+
ClearFormData('my-form'); // reset one form (or all forms if called with no guid)
|
|
68
|
+
|
|
69
|
+
const values = useForm('my-form'); // reactive hook inside a component
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Prefill a form with `patch`:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<FormGenerator guid="my-form" data={schema} patch={{ name: 'Ada', role: 'dev' }} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Conditional fields, subforms, computed values, and `requiredWhen` all evaluate
|
|
79
|
+
correctly from the **initial** `patch` — not just after the first edit.
|
|
80
|
+
|
|
81
|
+
### Imperative API (`apiRef`)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useRef } from 'react';
|
|
85
|
+
import { FormGenerator, FormApi } from 'dynamic-mui';
|
|
86
|
+
|
|
87
|
+
const api = useRef<FormApi>(null);
|
|
88
|
+
|
|
89
|
+
<FormGenerator guid="my-form" data={schema} apiRef={api} />;
|
|
90
|
+
|
|
91
|
+
api.current?.getValues(); // current values
|
|
92
|
+
api.current?.setValues({ ... }); // patch values in
|
|
93
|
+
api.current?.validate(); // boolean — runs validation, shows errors
|
|
94
|
+
api.current?.getErrors(); // array of { id, rule, message }
|
|
95
|
+
api.current?.reset(); // clear values
|
|
96
|
+
api.current?.submit(); // trigger the submit flow
|
|
97
|
+
|
|
98
|
+
// Dirty tracking
|
|
99
|
+
api.current?.isDirty(); // changed since it loaded?
|
|
100
|
+
api.current?.getInitialValues(); // the starting snapshot
|
|
101
|
+
api.current?.resetToInitial(); // undo all edits
|
|
102
|
+
api.current?.markPristine(); // adopt current values as the new baseline (after save)
|
|
103
|
+
|
|
104
|
+
// Print & PDF (see below)
|
|
105
|
+
api.current?.print({ title: 'Report' });
|
|
106
|
+
await api.current?.exportPdf({ title: 'Report', filename: 'report.pdf' });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Set `warnOnUnsavedChanges` to prompt the user before they leave the page while
|
|
110
|
+
the form is dirty.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Review, read-only, print & PDF
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// Read-only: full layout, every control disabled
|
|
118
|
+
<FormGenerator guid="f" data={schema} patch={values} readOnly />
|
|
119
|
+
|
|
120
|
+
// Review: a clean label → value summary instead of inputs
|
|
121
|
+
<FormGenerator guid="f" data={schema} patch={values} reviewMode />
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The review summary resolves option labels (not raw values), Yes/No for booleans,
|
|
125
|
+
joins multi-values, skips hidden and display-only fields, and follows matching
|
|
126
|
+
subforms — ideal for a "confirm before submit" step.
|
|
127
|
+
|
|
128
|
+
`print()` opens the browser dialog for that same summary (users can Save as PDF)
|
|
129
|
+
— **no dependencies**. `exportPdf()` downloads a real PDF and needs the optional
|
|
130
|
+
`pdfmake` package (lazy-loaded, never bundled into the core):
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm i pdfmake # only if you use exportPdf()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Submit bar
|
|
139
|
+
|
|
140
|
+
Render a visible (optionally sticky) submit button, fully configurable:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<FormGenerator
|
|
144
|
+
guid="wo" data={schema}
|
|
145
|
+
stickySubmit // pins to the bottom of the scroll area
|
|
146
|
+
cancelLabel="Cancel" onCancel={...}
|
|
147
|
+
submitButton={{
|
|
148
|
+
label: 'Dispatch work order',
|
|
149
|
+
color: 'success', // MUI palette name or any CSS color
|
|
150
|
+
gradient: ['#f59e0b', '#ef4444'],// optional gradient fill (overrides color)
|
|
151
|
+
icon: 'engineering', // leading Material icon; also `endIcon`
|
|
152
|
+
variant: 'contained', // contained | outlined | text
|
|
153
|
+
loadingLabel: 'Dispatching…',
|
|
154
|
+
}}
|
|
155
|
+
onSubmit={async (values, errors) => { if (!errors.length) await save(values); }}
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If `onSubmit` returns a Promise, the button shows a **spinner automatically**
|
|
160
|
+
until it settles (or drive it yourself with `submitButton.loading`).
|
|
161
|
+
`submitLabel="Save"` is a shortcut for `submitButton={{ label: 'Save' }}`.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Dynamic rule engine
|
|
166
|
+
|
|
167
|
+
Add these keys to any field to make the form reactive. All conditions run
|
|
168
|
+
against the live form values.
|
|
169
|
+
|
|
170
|
+
| Key | What it does |
|
|
171
|
+
| ------------- | --------------------------------------------------------- |
|
|
172
|
+
| `visibleWhen` | Show the field only when a condition holds |
|
|
173
|
+
| `disabledWhen`| Disable the field when a condition holds |
|
|
174
|
+
| `requiredWhen`| Make the field required when a condition holds |
|
|
175
|
+
| `formula` | Compute the field's value from other fields |
|
|
176
|
+
| `dependsOn` + `optionsMap` | Swap a field's options based on another field |
|
|
177
|
+
| `subforms` | Reveal a group of fields for a matching parent value |
|
|
178
|
+
|
|
179
|
+
A condition is `{ field, op, value }` with operators `eq`, `neq`, `gt`, `gte`,
|
|
180
|
+
`lt`, `lte`, `in`, `nin`, `contains`, `startsWith`, `endsWith`, `empty`,
|
|
181
|
+
`notEmpty`, `truthy`, `falsy`, plus `all` / `any` / `none` groups.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
// Show + require "reason" only when status is "other"
|
|
185
|
+
{
|
|
186
|
+
type: 'textfield',
|
|
187
|
+
props: { id: 'reason', MuiAttributes: { label: 'Reason' } },
|
|
188
|
+
visibleWhen: { field: 'status', op: 'eq', value: 'other' },
|
|
189
|
+
requiredWhen: { field: 'status', op: 'eq', value: 'other' },
|
|
190
|
+
requiredMessage: 'Please tell us why',
|
|
191
|
+
layout: { row: 2, xs: 12 },
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Live total = qty * price
|
|
195
|
+
{
|
|
196
|
+
type: 'computed',
|
|
197
|
+
props: { id: 'total', format: '0,0.00', MuiAttributes: { label: 'Total' } },
|
|
198
|
+
formula: 'qty * price',
|
|
199
|
+
layout: { row: 3, xs: 12 },
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// State options depend on the selected country (one control, not many)
|
|
203
|
+
{
|
|
204
|
+
type: 'select',
|
|
205
|
+
props: { id: 'state', MuiBoxAttributes: { label: 'State' } },
|
|
206
|
+
dependsOn: 'country',
|
|
207
|
+
optionsMap: {
|
|
208
|
+
us: [{ value: 'ca', label: 'California' }],
|
|
209
|
+
in: [{ value: 'ka', label: 'Karnataka' }],
|
|
210
|
+
},
|
|
211
|
+
layout: { row: 4, xs: 12 },
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`formula` supports `+ - * / %`, parentheses, unary minus, and
|
|
216
|
+
`SUM/AVG/MIN/MAX/ROUND/ABS/FLOOR/CEIL`.
|
|
217
|
+
|
|
218
|
+
---
|
|
57
219
|
|
|
58
|
-
|
|
220
|
+
## Validation
|
|
221
|
+
|
|
222
|
+
Per-field rules live under `rules.validation`:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
rules: { validation: [{ rule: 'mandatory', message: 'Required' }] }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Cross-field validators compare against another field:
|
|
229
|
+
`equalsField`, `notEqualsField`, `gtField`, `gteField`, `ltField`, `lteField`.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// "confirm" must equal "password"
|
|
233
|
+
rules: { validation: [{ rule: 'equalsField', field: 'password', message: 'Passwords must match' }] }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Turn on an accessible summary and auto-focus of the first error with
|
|
237
|
+
`validationSummary`. Hidden (`visibleWhen`) fields are excluded from validation.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Typed schema (`defineForm`)
|
|
242
|
+
|
|
243
|
+
Get full TypeScript autocompletion and compile-time checks for your schema:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { defineForm, FormGenerator } from 'dynamic-mui';
|
|
247
|
+
|
|
248
|
+
const schema = defineForm([
|
|
249
|
+
{ type: 'textfield', props: { id: 'email', MuiAttributes: { label: 'Email' } }, layout: { row: 1, xs: 12 } },
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
<FormGenerator guid="typed" data={schema} />;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Extensibility & DX
|
|
258
|
+
|
|
259
|
+
- **Custom controls** — register your own field types; they get the same
|
|
260
|
+
`ControlProps` as built-ins and participate fully in the engine:
|
|
261
|
+
```ts
|
|
262
|
+
import { registerControl } from 'dynamic-mui';
|
|
263
|
+
registerControl('color-swatch', ({ attributes, onChange }) => /* your component */);
|
|
264
|
+
// then: { type: 'color-swatch', props: { id: 'brand' } }
|
|
265
|
+
```
|
|
266
|
+
- **Zod / Yup validation** — validate the whole form against an existing schema
|
|
267
|
+
(`zod`/`yup` are never bundled — the resolvers duck-type the object you pass):
|
|
268
|
+
```ts
|
|
269
|
+
import { zodResolver } from 'dynamic-mui';
|
|
270
|
+
<FormGenerator data={fields} resolver={zodResolver(z.object({ email: z.string().email() }))} />
|
|
271
|
+
```
|
|
272
|
+
- **Async / remote validation** — per-field server checks (debounced, race-safe,
|
|
273
|
+
gates submit): `asyncValidators={{ username: async (v) => taken(v) ? 'Taken' : null }}`.
|
|
274
|
+
- **Typed values** — `FormApi<T>`, `FormData<T>(guid)`, `useForm<T>(guid)` give
|
|
275
|
+
autocompleted, type-checked values.
|
|
276
|
+
|
|
277
|
+
### Headless engine — `useFormEngine`
|
|
278
|
+
|
|
279
|
+
The whole dynamic engine (rules, validation, dynamic options, subforms) with **no
|
|
280
|
+
UI** — render your own components:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
import { useFormEngine, zodResolver } from 'dynamic-mui';
|
|
284
|
+
|
|
285
|
+
function MyForm() {
|
|
286
|
+
const form = useFormEngine(schema, { resolver: zodResolver(mySchema) });
|
|
287
|
+
return (
|
|
288
|
+
<form onSubmit={(e) => { e.preventDefault(); form.submit(save); }}>
|
|
289
|
+
{form.visibleFields.map((f) => (
|
|
290
|
+
<MyInput key={f.id} value={f.value} required={f.required}
|
|
291
|
+
disabled={f.disabled} options={f.options} error={f.error}
|
|
292
|
+
onChange={(v) => form.setValue(f.id, v)} />
|
|
293
|
+
))}
|
|
294
|
+
</form>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Exposes `values`, `errors`, `fields`/`visibleFields`, `setValue`/`setValues`,
|
|
300
|
+
`validate()`, `submit(onValid)`, `isValid`, `isDirty`, `reset()`, `getFieldState(id)`.
|
|
301
|
+
|
|
302
|
+
### Schema linter — `validateSchema`
|
|
303
|
+
|
|
304
|
+
Catch config mistakes before runtime (great in a test or build step):
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { validateSchema } from 'dynamic-mui';
|
|
308
|
+
|
|
309
|
+
const issues = validateSchema(mySchema); // SchemaIssue[]; [] = clean
|
|
310
|
+
// flags duplicate ids, dependsOn → missing field, orphan optionsMap,
|
|
311
|
+
// rule/cross-field references to unknown fields, subform conditions
|
|
312
|
+
// that aren't real options, …
|
|
313
|
+
if (issues.some((i) => i.level === 'error')) throw new Error('Bad form schema');
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Internationalisation
|
|
319
|
+
|
|
320
|
+
Override the built-in strings:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
<FormGenerator guid="fr" data={schema}
|
|
324
|
+
messages={{ required: 'Champ obligatoire', errorSummary: '{n} champ(s) à corriger' }} />
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Or plug in **any** i18n system with a `translate` function — it localizes every
|
|
328
|
+
user-facing string (labels, placeholders, helper text, option labels, validation
|
|
329
|
+
messages, typography, submit/cancel labels), keeping the schema in one language:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
// works with i18next, react-intl, or a plain dictionary
|
|
333
|
+
const t = (s) => dictionary[s] ?? s;
|
|
334
|
+
|
|
335
|
+
<FormGenerator guid="form" data={schema} translate={t} />
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Switching the language re-renders the form translated while preserving values.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Controls
|
|
343
|
+
|
|
344
|
+
Text & numbers · password · OTP · phone / international phone · currency ·
|
|
345
|
+
select · async autocomplete · autocomplete · cascade select · chip select ·
|
|
346
|
+
radio · checkbox · switch · toggle buttons · slider · rating · NPS · stepper ·
|
|
347
|
+
date / time / date-time / date-range pickers · file upload · signature ·
|
|
348
|
+
color picker · markdown & rich-text editors · tags · key-value · matrix ·
|
|
349
|
+
editable & data tables · line items · address · geo / location · consent ·
|
|
350
|
+
alert · divider · typography · charts (bar / line / pie / mixed), and more.
|
|
351
|
+
|
|
352
|
+
Each control is configured through `props` (its `MuiAttributes` /
|
|
353
|
+
`MuiBoxAttributes` map straight onto the underlying MUI component).
|
|
354
|
+
|
|
355
|
+
### Theming
|
|
356
|
+
|
|
357
|
+
Controls render under **your** MUI theme. Date/time pickers automatically match
|
|
358
|
+
the size and variant of your text fields, so a picker lines up with the fields
|
|
359
|
+
beside it.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## AI module (optional)
|
|
364
|
+
|
|
365
|
+
`dynamic-mui/ai` provides AI-assisted building blocks (generate a form from a
|
|
366
|
+
prompt, autofill, text assist, schema review) that talk to **your** proxy/relay
|
|
367
|
+
so your provider key stays server-side. See [`examples/`](./examples) for a
|
|
368
|
+
ready-to-deploy proxy (including an AWS Lambda BYOK relay).
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Playground
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npm install
|
|
376
|
+
npm run dev # visual builder + live demo gallery
|
|
377
|
+
```
|
|
59
378
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
3. Open `http://localhost:5173` (or the port shown in terminal).
|
|
379
|
+
Build forms visually, browse every control in multiple configurations, explore
|
|
380
|
+
real-world example forms, view the generated JSON, and copy it into your app.
|
|
63
381
|
|
|
64
|
-
|
|
382
|
+
---
|
|
65
383
|
|
|
66
384
|
## License
|
|
67
385
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsxs as a, jsx as o } from "react/jsx-runtime";
|
|
2
|
+
import m from "react";
|
|
3
|
+
import { Accordion as s, AccordionSummary as l, Typography as p, Icon as u, AccordionDetails as f } from "@mui/material";
|
|
4
|
+
import { F as A } from "./index-CddjLTWF.mjs";
|
|
5
|
+
import { v as h } from "./v4-BysszJq8.mjs";
|
|
6
|
+
function j({ attributes: r = {}, patch: c, onChange: i }) {
|
|
7
|
+
const { MuiAttributes: n = {}, label: e = "Accordion", subFields: t = [] } = r, d = m.useMemo(() => h(), []);
|
|
8
|
+
return /* @__PURE__ */ a(s, { ...n, children: [
|
|
9
|
+
/* @__PURE__ */ o(l, { expandIcon: /* @__PURE__ */ o(u, { children: "expand_more" }), children: /* @__PURE__ */ o(p, { children: e }) }),
|
|
10
|
+
/* @__PURE__ */ o(f, { children: /* @__PURE__ */ o(
|
|
11
|
+
A,
|
|
12
|
+
{
|
|
13
|
+
guid: d,
|
|
14
|
+
data: t,
|
|
15
|
+
patch: c,
|
|
16
|
+
onChange: i
|
|
17
|
+
}
|
|
18
|
+
) })
|
|
19
|
+
] });
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
j as default
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),u=require("react"),r=require("@mui/material"),a=require("./index-D08GIfqF.js"),l=require("./v4-CqdK_b8Y.js");function x({attributes:n={},patch:o,onChange:c}){const{MuiAttributes:i={},label:t="Accordion",subFields:s=[]}=n,d=u.useMemo(()=>l.v4(),[]);return e.jsxs(r.Accordion,{...i,children:[e.jsx(r.AccordionSummary,{expandIcon:e.jsx(r.Icon,{children:"expand_more"}),children:e.jsx(r.Typography,{children:t})}),e.jsx(r.AccordionDetails,{children:e.jsx(a.FormGenerator,{guid:d,data:s,patch:o,onChange:c})})]})}exports.default=x;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { jsxs as f, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import S, { useCallback as A } from "react";
|
|
3
|
+
import r from "@mui/material/Grid";
|
|
4
|
+
import i from "@mui/material/TextField";
|
|
5
|
+
import g from "@mui/material/MenuItem";
|
|
6
|
+
import { u as E, m as P, a as I } from "./index-CddjLTWF.mjs";
|
|
7
|
+
const N = [
|
|
8
|
+
{ value: "US", label: "United States" },
|
|
9
|
+
{ value: "CA", label: "Canada" },
|
|
10
|
+
{ value: "MX", label: "Mexico" },
|
|
11
|
+
{ value: "GB", label: "United Kingdom" },
|
|
12
|
+
{ value: "IE", label: "Ireland" },
|
|
13
|
+
{ value: "FR", label: "France" },
|
|
14
|
+
{ value: "DE", label: "Germany" },
|
|
15
|
+
{ value: "ES", label: "Spain" },
|
|
16
|
+
{ value: "IT", label: "Italy" },
|
|
17
|
+
{ value: "NL", label: "Netherlands" },
|
|
18
|
+
{ value: "BE", label: "Belgium" },
|
|
19
|
+
{ value: "CH", label: "Switzerland" },
|
|
20
|
+
{ value: "SE", label: "Sweden" },
|
|
21
|
+
{ value: "NO", label: "Norway" },
|
|
22
|
+
{ value: "DK", label: "Denmark" },
|
|
23
|
+
{ value: "PT", label: "Portugal" },
|
|
24
|
+
{ value: "AU", label: "Australia" },
|
|
25
|
+
{ value: "NZ", label: "New Zealand" },
|
|
26
|
+
{ value: "IN", label: "India" },
|
|
27
|
+
{ value: "CN", label: "China" },
|
|
28
|
+
{ value: "JP", label: "Japan" },
|
|
29
|
+
{ value: "SG", label: "Singapore" },
|
|
30
|
+
{ value: "AE", label: "United Arab Emirates" },
|
|
31
|
+
{ value: "BR", label: "Brazil" },
|
|
32
|
+
{ value: "ZA", label: "South Africa" }
|
|
33
|
+
], U = {
|
|
34
|
+
street1: "Street address",
|
|
35
|
+
street2: "Apt, suite, etc. (optional)",
|
|
36
|
+
city: "City",
|
|
37
|
+
state: "State / Province",
|
|
38
|
+
postalCode: "Postal code",
|
|
39
|
+
country: "Country"
|
|
40
|
+
};
|
|
41
|
+
function D({ attributes: n = {}, rules: h = {}, onChange: c }) {
|
|
42
|
+
const {
|
|
43
|
+
id: b = "",
|
|
44
|
+
MuiAttributes: v = {},
|
|
45
|
+
countries: o,
|
|
46
|
+
labels: y = {}
|
|
47
|
+
} = n, x = Array.isArray(o) && o.length ? o : N, l = { ...U, ...y }, d = h?.validation?.some(
|
|
48
|
+
(t) => t.rule === "mandatory" || t.rule === "mandatoryselect"
|
|
49
|
+
) || !1, [a, p] = S.useState(() => ({
|
|
50
|
+
street1: "",
|
|
51
|
+
street2: "",
|
|
52
|
+
city: "",
|
|
53
|
+
state: "",
|
|
54
|
+
postalCode: "",
|
|
55
|
+
country: "",
|
|
56
|
+
...n?.value || {}
|
|
57
|
+
}));
|
|
58
|
+
E(() => {
|
|
59
|
+
p({
|
|
60
|
+
street1: "",
|
|
61
|
+
street2: "",
|
|
62
|
+
city: "",
|
|
63
|
+
state: "",
|
|
64
|
+
postalCode: "",
|
|
65
|
+
country: "",
|
|
66
|
+
...n?.value || {}
|
|
67
|
+
});
|
|
68
|
+
}, [n?.value]);
|
|
69
|
+
const s = A(
|
|
70
|
+
(t) => (C) => {
|
|
71
|
+
const m = { ...a, [t]: C.target.value };
|
|
72
|
+
p(m), c?.({ id: b, value: m });
|
|
73
|
+
},
|
|
74
|
+
[a, b, c]
|
|
75
|
+
), u = P(I, v?.sx);
|
|
76
|
+
return /* @__PURE__ */ f(r, { container: !0, spacing: 2, ...v, children: [
|
|
77
|
+
/* @__PURE__ */ e(r, { size: { xs: 12 }, children: /* @__PURE__ */ e(
|
|
78
|
+
i,
|
|
79
|
+
{
|
|
80
|
+
fullWidth: !0,
|
|
81
|
+
label: l.street1,
|
|
82
|
+
required: d,
|
|
83
|
+
value: a.street1 || "",
|
|
84
|
+
onChange: s("street1"),
|
|
85
|
+
inputProps: { "aria-label": l.street1 },
|
|
86
|
+
sx: u
|
|
87
|
+
}
|
|
88
|
+
) }),
|
|
89
|
+
/* @__PURE__ */ e(r, { size: { xs: 12 }, children: /* @__PURE__ */ e(
|
|
90
|
+
i,
|
|
91
|
+
{
|
|
92
|
+
fullWidth: !0,
|
|
93
|
+
label: l.street2,
|
|
94
|
+
value: a.street2 || "",
|
|
95
|
+
onChange: s("street2"),
|
|
96
|
+
inputProps: { "aria-label": l.street2 },
|
|
97
|
+
sx: u
|
|
98
|
+
}
|
|
99
|
+
) }),
|
|
100
|
+
/* @__PURE__ */ e(r, { size: { xs: 12, sm: 6, md: 5 }, children: /* @__PURE__ */ e(
|
|
101
|
+
i,
|
|
102
|
+
{
|
|
103
|
+
fullWidth: !0,
|
|
104
|
+
label: l.city,
|
|
105
|
+
required: d,
|
|
106
|
+
value: a.city || "",
|
|
107
|
+
onChange: s("city"),
|
|
108
|
+
inputProps: { "aria-label": l.city },
|
|
109
|
+
sx: u
|
|
110
|
+
}
|
|
111
|
+
) }),
|
|
112
|
+
/* @__PURE__ */ e(r, { size: { xs: 12, sm: 6, md: 4 }, children: /* @__PURE__ */ e(
|
|
113
|
+
i,
|
|
114
|
+
{
|
|
115
|
+
fullWidth: !0,
|
|
116
|
+
label: l.state,
|
|
117
|
+
value: a.state || "",
|
|
118
|
+
onChange: s("state"),
|
|
119
|
+
inputProps: { "aria-label": l.state },
|
|
120
|
+
sx: u
|
|
121
|
+
}
|
|
122
|
+
) }),
|
|
123
|
+
/* @__PURE__ */ e(r, { size: { xs: 12, sm: 6, md: 3 }, children: /* @__PURE__ */ e(
|
|
124
|
+
i,
|
|
125
|
+
{
|
|
126
|
+
fullWidth: !0,
|
|
127
|
+
label: l.postalCode,
|
|
128
|
+
value: a.postalCode || "",
|
|
129
|
+
onChange: s("postalCode"),
|
|
130
|
+
inputProps: { "aria-label": l.postalCode },
|
|
131
|
+
sx: u
|
|
132
|
+
}
|
|
133
|
+
) }),
|
|
134
|
+
/* @__PURE__ */ e(r, { size: { xs: 12, sm: 6 }, children: /* @__PURE__ */ e(
|
|
135
|
+
i,
|
|
136
|
+
{
|
|
137
|
+
select: !0,
|
|
138
|
+
fullWidth: !0,
|
|
139
|
+
label: l.country,
|
|
140
|
+
required: d,
|
|
141
|
+
value: a.country || "",
|
|
142
|
+
onChange: s("country"),
|
|
143
|
+
SelectProps: { inputProps: { "aria-label": l.country } },
|
|
144
|
+
sx: u,
|
|
145
|
+
children: x.map((t) => /* @__PURE__ */ e(g, { value: t.value, children: t.label }, t.value))
|
|
146
|
+
}
|
|
147
|
+
) })
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
D as default
|
|
152
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),h=require("react"),s=require("@mui/material/Grid"),n=require("@mui/material/TextField"),A=require("@mui/material/MenuItem"),c=require("./index-D08GIfqF.js"),g=[{value:"US",label:"United States"},{value:"CA",label:"Canada"},{value:"MX",label:"Mexico"},{value:"GB",label:"United Kingdom"},{value:"IE",label:"Ireland"},{value:"FR",label:"France"},{value:"DE",label:"Germany"},{value:"ES",label:"Spain"},{value:"IT",label:"Italy"},{value:"NL",label:"Netherlands"},{value:"BE",label:"Belgium"},{value:"CH",label:"Switzerland"},{value:"SE",label:"Sweden"},{value:"NO",label:"Norway"},{value:"DK",label:"Denmark"},{value:"PT",label:"Portugal"},{value:"AU",label:"Australia"},{value:"NZ",label:"New Zealand"},{value:"IN",label:"India"},{value:"CN",label:"China"},{value:"JP",label:"Japan"},{value:"SG",label:"Singapore"},{value:"AE",label:"United Arab Emirates"},{value:"BR",label:"Brazil"},{value:"ZA",label:"South Africa"}],f={street1:"Street address",street2:"Apt, suite, etc. (optional)",city:"City",state:"State / Province",postalCode:"Postal code",country:"Country"};function P({attributes:i={},rules:m={},onChange:b}){const{id:v="",MuiAttributes:x={},countries:o,labels:C={}}=i,S=Array.isArray(o)&&o.length?o:g,l={...f,...C},d=m?.validation?.some(t=>t.rule==="mandatory"||t.rule==="mandatoryselect")||!1,[a,p]=h.useState(()=>({street1:"",street2:"",city:"",state:"",postalCode:"",country:"",...i?.value||{}}));c.useUpdateEffect(()=>{p({street1:"",street2:"",city:"",state:"",postalCode:"",country:"",...i?.value||{}})},[i?.value]);const r=h.useCallback(t=>j=>{const y={...a,[t]:j.target.value};p(y),b?.({id:v,value:y})},[a,v,b]),u=c.mergeSx(c.premiumInputSx,x?.sx);return e.jsxs(s,{container:!0,spacing:2,...x,children:[e.jsx(s,{size:{xs:12},children:e.jsx(n,{fullWidth:!0,label:l.street1,required:d,value:a.street1||"",onChange:r("street1"),inputProps:{"aria-label":l.street1},sx:u})}),e.jsx(s,{size:{xs:12},children:e.jsx(n,{fullWidth:!0,label:l.street2,value:a.street2||"",onChange:r("street2"),inputProps:{"aria-label":l.street2},sx:u})}),e.jsx(s,{size:{xs:12,sm:6,md:5},children:e.jsx(n,{fullWidth:!0,label:l.city,required:d,value:a.city||"",onChange:r("city"),inputProps:{"aria-label":l.city},sx:u})}),e.jsx(s,{size:{xs:12,sm:6,md:4},children:e.jsx(n,{fullWidth:!0,label:l.state,value:a.state||"",onChange:r("state"),inputProps:{"aria-label":l.state},sx:u})}),e.jsx(s,{size:{xs:12,sm:6,md:3},children:e.jsx(n,{fullWidth:!0,label:l.postalCode,value:a.postalCode||"",onChange:r("postalCode"),inputProps:{"aria-label":l.postalCode},sx:u})}),e.jsx(s,{size:{xs:12,sm:6},children:e.jsx(n,{select:!0,fullWidth:!0,label:l.country,required:d,value:a.country||"",onChange:r("country"),SelectProps:{inputProps:{"aria-label":l.country}},sx:u,children:S.map(t=>e.jsx(A,{value:t.value,children:t.label},t.value))})})]})}exports.default=P;
|