@mieweb/forms-renderer 0.1.6 → 0.1.8
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 +149 -308
- package/dist/{index.js → react.js} +243 -248
- package/dist/{index.js.map → react.js.map} +1 -1
- package/dist/standalone.js +23123 -0
- package/dist/standalone.js.map +1 -0
- package/dist/standalone.umd.cjs +272 -0
- package/dist/standalone.umd.cjs.map +1 -0
- package/dist/web-component.css +1 -0
- package/package.json +21 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# @mieweb/forms-renderer
|
|
1
|
+
# 📋 @mieweb/forms-renderer
|
|
2
2
|
|
|
3
|
-
Read-only questionnaire renderer
|
|
3
|
+
Read-only questionnaire renderer with dual distribution: React component or Standalone Web Component.
|
|
4
4
|
|
|
5
5
|
## 📦 Installation
|
|
6
6
|
|
|
@@ -8,356 +8,197 @@ Read-only questionnaire renderer for displaying and filling out forms. Produces
|
|
|
8
8
|
npm install @mieweb/forms-renderer
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## 🚀 Examples
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
See the complete working examples in this package:
|
|
14
|
+
- [`example-react.jsx`](./example-react.jsx) - ⚛️ React component usage
|
|
15
|
+
- [`example-standalone.html`](./example-standalone.html) - 🌐 Web Component usage
|
|
14
16
|
|
|
17
|
+
## 💻 Usage
|
|
18
|
+
|
|
19
|
+
### ⚛️ React Component (Recommended for React Projects)
|
|
20
|
+
|
|
21
|
+
Requires React peer dependencies:
|
|
15
22
|
```bash
|
|
16
23
|
npm install react react-dom
|
|
17
24
|
```
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
The following is installed automatically:
|
|
22
|
-
|
|
23
|
-
- `@mieweb/forms-engine` - Core form state and field components
|
|
24
|
-
|
|
25
|
-
## 🚀 Quick Start
|
|
26
|
-
|
|
27
|
-
### Basic Usage
|
|
28
|
-
|
|
26
|
+
From [`example-react.jsx`](./example-react.jsx):
|
|
29
27
|
```jsx
|
|
30
28
|
import { QuestionnaireRenderer } from '@mieweb/forms-renderer';
|
|
31
|
-
import { createRoot } from 'react-dom/client';
|
|
32
|
-
import './index.css';
|
|
33
29
|
|
|
34
30
|
function App() {
|
|
35
31
|
const [fields] = React.useState([
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
{
|
|
33
|
+
id: 'sec-1',
|
|
34
|
+
fieldType: 'section',
|
|
35
|
+
title: 'Personal Information',
|
|
36
|
+
fields: [
|
|
37
|
+
{
|
|
38
|
+
id: 'q-name',
|
|
39
|
+
fieldType: 'input',
|
|
40
|
+
question: 'What is your full name?',
|
|
41
|
+
answer: ''
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'q-gender',
|
|
45
|
+
fieldType: 'radio',
|
|
46
|
+
question: 'Biological sex',
|
|
47
|
+
options: [
|
|
48
|
+
{ id: 'gender-male', value: 'Male' },
|
|
49
|
+
{ id: 'gender-female', value: 'Female' }
|
|
50
|
+
],
|
|
51
|
+
selected: null
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
39
55
|
]);
|
|
40
|
-
const [submitted, setSubmitted] = React.useState(null);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="w-full h-dvh bg-slate-100">
|
|
44
|
-
<div className="absolute inset-0 overflow-auto p-4 max-w-4xl mx-auto w-full">
|
|
45
|
-
<QuestionnaireRenderer
|
|
46
|
-
questionnaireId="demo-1"
|
|
47
|
-
fields={fields}
|
|
48
|
-
onSubmit={(qr) => setSubmitted(qr)}
|
|
49
|
-
/>
|
|
50
|
-
{submitted && (
|
|
51
|
-
<pre className="mt-4 bg-neutral-100 p-4">{JSON.stringify(submitted, null, 2)}</pre>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
createRoot(document.getElementById('root')).render(<App />);
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Loading from API
|
|
62
|
-
|
|
63
|
-
```jsx
|
|
64
|
-
function App() {
|
|
65
|
-
const [fields, setFields] = React.useState([]);
|
|
66
|
-
const [loading, setLoading] = React.useState(true);
|
|
67
|
-
|
|
68
|
-
React.useEffect(() => {
|
|
69
|
-
fetch('/api/questionnaire/123')
|
|
70
|
-
.then(res => res.json())
|
|
71
|
-
.then(data => {
|
|
72
|
-
setFields(data.fields);
|
|
73
|
-
setLoading(false);
|
|
74
|
-
});
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
if (loading) return <div>Loading...</div>;
|
|
78
56
|
|
|
79
57
|
return (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await fetch('/api/responses', {
|
|
87
|
-
method: 'POST',
|
|
88
|
-
headers: { 'Content-Type': 'application/json' },
|
|
89
|
-
body: JSON.stringify(fhirResponse)
|
|
90
|
-
});
|
|
91
|
-
alert('Submitted!');
|
|
92
|
-
}}
|
|
93
|
-
/>
|
|
94
|
-
</div>
|
|
58
|
+
<QuestionnaireRenderer
|
|
59
|
+
questionnaireId="demo-questionnaire"
|
|
60
|
+
fields={fields}
|
|
61
|
+
onChange={(updatedFields) => console.log('Changed:', updatedFields)}
|
|
62
|
+
onSubmit={(fhirResponse) => console.log('Submitted:', fhirResponse)}
|
|
63
|
+
/>
|
|
95
64
|
);
|
|
96
65
|
}
|
|
97
66
|
```
|
|
98
67
|
|
|
99
|
-
|
|
68
|
+
### 🌐 Standalone Web Component (Framework-Agnostic)
|
|
100
69
|
|
|
101
|
-
|
|
102
|
-
// Example: Simple questionnaire data
|
|
103
|
-
[
|
|
104
|
-
{
|
|
105
|
-
id: '1',
|
|
106
|
-
fieldType: 'input',
|
|
107
|
-
question: 'What is your name?',
|
|
108
|
-
answer: ''
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
id: '2',
|
|
112
|
-
fieldType: 'radio',
|
|
113
|
-
question: 'Select your role',
|
|
114
|
-
options: ['Developer', 'Designer', 'Manager'],
|
|
115
|
-
selected: null
|
|
116
|
-
}
|
|
117
|
-
]
|
|
70
|
+
✨ Zero dependencies - works with any framework or vanilla JS.
|
|
118
71
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
{ targetId: "pi-sex", operator: "equals", value: "pi-sex-f" }
|
|
72
|
+
From [`example-standalone.html`](./example-standalone.html):
|
|
73
|
+
```html
|
|
74
|
+
<script type="module">
|
|
75
|
+
import './package/dist/standalone.js';
|
|
76
|
+
|
|
77
|
+
const renderer = document.querySelector('questionnaire-renderer');
|
|
78
|
+
renderer.fields = [
|
|
79
|
+
{
|
|
80
|
+
id: 'sec-1',
|
|
81
|
+
fieldType: 'section',
|
|
82
|
+
title: 'Patient Information',
|
|
83
|
+
fields: [
|
|
84
|
+
{
|
|
85
|
+
id: 'q-name',
|
|
86
|
+
fieldType: 'input',
|
|
87
|
+
question: 'Full Name',
|
|
88
|
+
answer: ''
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'q-gender',
|
|
92
|
+
fieldType: 'radio',
|
|
93
|
+
question: 'Biological sex',
|
|
94
|
+
options: [
|
|
95
|
+
{ id: 'gender-male', value: 'Male' },
|
|
96
|
+
{ id: 'gender-female', value: 'Female' }
|
|
97
|
+
],
|
|
98
|
+
selected: null
|
|
99
|
+
}
|
|
148
100
|
]
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{ id: "preg-no", value: "No" }
|
|
157
|
-
],
|
|
158
|
-
selected: null,
|
|
159
|
-
id: "preg-status"
|
|
160
|
-
}
|
|
161
|
-
]
|
|
162
|
-
}
|
|
163
|
-
]
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Any JSON object matching this structure works** - whether from your backend API, a database query, local storage, or a CMS.
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## 📖 Props
|
|
171
|
-
|
|
172
|
-
### `QuestionnaireRenderer`
|
|
173
|
-
|
|
174
|
-
| Prop | Type | Default | Description |
|
|
175
|
-
|------|------|---------|-------------|
|
|
176
|
-
| `fields` | `Array` | **Required** | Questionnaire definition from your data source (API, database, etc.) |
|
|
177
|
-
| `onChange` | `Function` | `undefined` | Callback when answers change: `(fields) => void` |
|
|
178
|
-
| `onSubmit` | `Function` | `undefined` | Callback on submit: `(fhirResponse) => void` |
|
|
179
|
-
| `questionnaireId` | `String` | `'questionnaire-1'` | ID for FHIR Questionnaire reference |
|
|
180
|
-
| `subjectId` | `String` | `undefined` | Patient/subject ID for FHIR response |
|
|
181
|
-
| `className` | `String` | `''` | Additional CSS classes |
|
|
182
|
-
| `fullHeight` | `Boolean` | `false` | Use full viewport height |
|
|
183
|
-
|
|
184
|
-
## ✨ Features
|
|
185
|
-
|
|
186
|
-
### ✅ Read-Only Display
|
|
187
|
-
|
|
188
|
-
- Displays questionnaire fields without editing controls
|
|
189
|
-
- Users can **fill out** the form but **cannot add/remove/reorder** fields
|
|
101
|
+
}
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
renderer.onSubmit = (fhirResponse) => {
|
|
105
|
+
console.log('Form submitted:', fhirResponse);
|
|
106
|
+
};
|
|
107
|
+
</script>
|
|
190
108
|
|
|
191
|
-
|
|
109
|
+
<questionnaire-renderer
|
|
110
|
+
questionnaire-id="standalone-demo"
|
|
111
|
+
full-height>
|
|
112
|
+
</questionnaire-renderer>
|
|
113
|
+
```
|
|
192
114
|
|
|
193
|
-
|
|
194
|
-
- **Radio Buttons** - Single choice selection
|
|
195
|
-
- **Checkboxes** - Multiple choice selection
|
|
196
|
-
- **Dropdown** - Select menu
|
|
197
|
-
- **Section** - Grouped fields with collapse/expand
|
|
115
|
+
## ⚙️ Props/Attributes
|
|
198
116
|
|
|
199
|
-
###
|
|
117
|
+
### ⚛️ React Component
|
|
118
|
+
- `fields` - Questionnaire definition array
|
|
119
|
+
- `onChange` - Callback when answers change
|
|
120
|
+
- `onSubmit` - Callback on form submit
|
|
121
|
+
- `questionnaireId` - FHIR Questionnaire ID
|
|
122
|
+
- `subjectId` - Patient/subject ID
|
|
123
|
+
- `className` - CSS classes
|
|
124
|
+
- `fullHeight` - Full viewport height
|
|
200
125
|
|
|
201
|
-
|
|
126
|
+
### 🌐 Web Component
|
|
127
|
+
- `questionnaire-id` - FHIR Questionnaire ID (attribute)
|
|
128
|
+
- `full-height` - Full viewport height (attribute)
|
|
129
|
+
- `fields` - Questionnaire definition (property)
|
|
130
|
+
- `onChange` - Change callback (property)
|
|
131
|
+
- `onSubmit` - Submit callback (property)
|
|
202
132
|
|
|
203
|
-
|
|
204
|
-
const fields = [
|
|
205
|
-
{
|
|
206
|
-
id: '1',
|
|
207
|
-
fieldType: 'radio',
|
|
208
|
-
question: 'Do you have symptoms?',
|
|
209
|
-
options: ['Yes', 'No'],
|
|
210
|
-
selected: null
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
id: '2',
|
|
214
|
-
fieldType: 'input',
|
|
215
|
-
question: 'Describe your symptoms',
|
|
216
|
-
answer: '',
|
|
217
|
-
enableWhen: [
|
|
218
|
-
{
|
|
219
|
-
question: '1', // ID of field to check
|
|
220
|
-
operator: 'equals',
|
|
221
|
-
answer: 'Yes'
|
|
222
|
-
}
|
|
223
|
-
]
|
|
224
|
-
}
|
|
225
|
-
];
|
|
226
|
-
```
|
|
133
|
+
## 🔧 Field Types
|
|
227
134
|
|
|
228
|
-
|
|
135
|
+
- `input` - 📝 Text input field
|
|
136
|
+
- `radio` - 🔘 Single selection radio buttons
|
|
137
|
+
- `check` - ☑️ Multiple selection checkboxes
|
|
138
|
+
- `selection` - 📋 Dropdown selection
|
|
139
|
+
- `section` - 📂 Container for grouping fields
|
|
229
140
|
|
|
230
|
-
|
|
141
|
+
## 🔀 Conditional Logic (enableWhen)
|
|
231
142
|
|
|
232
|
-
|
|
143
|
+
Fields can be shown/hidden based on other field values. Both examples include conditional logic:
|
|
233
144
|
|
|
234
|
-
|
|
145
|
+
From [`example-react.jsx`](./example-react.jsx):
|
|
146
|
+
```javascript
|
|
235
147
|
{
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
148
|
+
id: 'sec-pregnancy',
|
|
149
|
+
fieldType: 'section',
|
|
150
|
+
title: 'Pregnancy Information',
|
|
151
|
+
enableWhen: {
|
|
152
|
+
logic: 'AND',
|
|
153
|
+
conditions: [
|
|
154
|
+
{ targetId: 'q-gender', operator: 'equals', value: 'gender-female' }
|
|
155
|
+
]
|
|
243
156
|
},
|
|
244
|
-
|
|
157
|
+
fields: [
|
|
245
158
|
{
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
159
|
+
id: 'q-weeks',
|
|
160
|
+
fieldType: 'input',
|
|
161
|
+
question: 'Weeks gestation (if known)',
|
|
162
|
+
answer: '',
|
|
163
|
+
enableWhen: {
|
|
164
|
+
logic: 'AND',
|
|
165
|
+
conditions: [
|
|
166
|
+
{ targetId: 'q-pregnant', operator: 'equals', value: 'preg-yes' }
|
|
167
|
+
]
|
|
168
|
+
}
|
|
253
169
|
}
|
|
254
|
-
// ... more items
|
|
255
170
|
]
|
|
256
171
|
}
|
|
257
172
|
```
|
|
258
173
|
|
|
259
|
-
##
|
|
260
|
-
|
|
261
|
-
### Pre-filled Responses
|
|
262
|
-
|
|
263
|
-
```jsx
|
|
264
|
-
const prefilledFields = [
|
|
265
|
-
{
|
|
266
|
-
id: '1',
|
|
267
|
-
fieldType: 'input',
|
|
268
|
-
question: 'Full Name',
|
|
269
|
-
answer: 'Jane Doe' // Pre-filled
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
id: '2',
|
|
273
|
-
fieldType: 'radio',
|
|
274
|
-
question: 'Gender',
|
|
275
|
-
options: [{ value: 'Male' }, { value: 'Female' }, { value: 'Other' }],
|
|
276
|
-
selected: { value: 'Female' } // Pre-filled
|
|
277
|
-
}
|
|
278
|
-
];
|
|
279
|
-
|
|
280
|
-
<QuestionnaireRenderer
|
|
281
|
-
fields={prefilledFields}
|
|
282
|
-
questionnaireId="follow-up-visit"
|
|
283
|
-
subjectId="patient-67890"
|
|
284
|
-
/>
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Track Form Changes
|
|
174
|
+
## 🏥 FHIR Output
|
|
288
175
|
|
|
289
|
-
|
|
290
|
-
function App() {
|
|
291
|
-
const [fields, setFields] = React.useState(initialFields);
|
|
292
|
-
|
|
293
|
-
const handleChange = (updatedFields) => {
|
|
294
|
-
setFields(updatedFields);
|
|
295
|
-
console.log('User updated:', updatedFields);
|
|
296
|
-
};
|
|
176
|
+
The `onSubmit` callback receives a FHIR QuestionnaireResponse:
|
|
297
177
|
|
|
298
|
-
|
|
299
|
-
<QuestionnaireRenderer
|
|
300
|
-
fields={fields}
|
|
301
|
-
onChange={handleChange}
|
|
302
|
-
onSubmit={(fhirResponse) => {
|
|
303
|
-
console.log('Submitting:', fhirResponse);
|
|
304
|
-
}}
|
|
305
|
-
/>
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## 🔧 Field Structure
|
|
311
|
-
|
|
312
|
-
Fields use the same structure as `@mieweb/forms-editor`:
|
|
313
|
-
|
|
314
|
-
```js
|
|
178
|
+
```javascript
|
|
315
179
|
{
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
## 📦 Bundle Size
|
|
329
|
-
|
|
330
|
-
- **ESM format** with tree-shaking support
|
|
331
|
-
- **TypeScript definitions** included
|
|
332
|
-
- **Very lightweight** - perfect for embedding in patient portals
|
|
333
|
-
- **CSS automatically injected** via `@mieweb/forms-engine` dependency
|
|
334
|
-
- Dependencies: `@mieweb/forms-engine` (auto-installed)
|
|
335
|
-
- Peer dependencies: React 18+
|
|
336
|
-
|
|
337
|
-
## 🎨 Styling
|
|
338
|
-
|
|
339
|
-
**CSS is automatically included** when you import the package! The styles come bundled via the `@mieweb/forms-engine` dependency.
|
|
340
|
-
|
|
341
|
-
Override with custom CSS:
|
|
342
|
-
|
|
343
|
-
```css
|
|
344
|
-
.qr-renderer-root {
|
|
345
|
-
max-width: 800px;
|
|
346
|
-
margin: 0 auto;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.qr-submit-btn {
|
|
350
|
-
background: #10b981;
|
|
351
|
-
color: white;
|
|
352
|
-
padding: 0.75rem 2rem;
|
|
180
|
+
resourceType: 'QuestionnaireResponse',
|
|
181
|
+
questionnaire: 'demo-1',
|
|
182
|
+
status: 'completed',
|
|
183
|
+
authored: '2023-01-01T12:00:00Z',
|
|
184
|
+
item: [
|
|
185
|
+
{
|
|
186
|
+
linkId: 'q1',
|
|
187
|
+
text: 'What is your name?',
|
|
188
|
+
answer: [{ valueString: 'John Doe' }]
|
|
189
|
+
}
|
|
190
|
+
]
|
|
353
191
|
}
|
|
354
192
|
```
|
|
355
193
|
|
|
356
|
-
##
|
|
194
|
+
## 📊 Bundle Sizes
|
|
357
195
|
|
|
358
|
-
-
|
|
359
|
-
-
|
|
196
|
+
- **⚛️ React version**: ~24 KB (requires peer deps)
|
|
197
|
+
- **🌐 Standalone version**: ~819 KB (zero dependencies)
|
|
360
198
|
|
|
361
|
-
##
|
|
199
|
+
## 📚 Documentation
|
|
362
200
|
|
|
363
|
-
|
|
201
|
+
- [📖 Migration Guide](./docs/MIGRATION.md)
|
|
202
|
+
- [🚀 Meteor/Blaze Guide](./docs/METEOR-BLAZE-GUIDE.md)
|
|
203
|
+
- [🌐 Web Component Summary](./docs/WEB-COMPONENT-SUMMARY.md)
|
|
204
|
+
- [🏗️ Architecture](./docs/ARCHITECTURE.md)
|