@mieweb/forms-renderer 0.1.4 → 0.1.7
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 +148 -295
- package/dist/react.js +748 -0
- package/dist/react.js.map +1 -0
- package/dist/standalone.js +23137 -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 +22 -9
- package/dist/index.js +0 -734
- package/dist/index.js.map +0 -1
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,344 +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
|
-
### 1. Basic Usage
|
|
28
|
-
|
|
26
|
+
From [`example-react.jsx`](./example-react.jsx):
|
|
29
27
|
```jsx
|
|
30
28
|
import { QuestionnaireRenderer } from '@mieweb/forms-renderer';
|
|
31
29
|
|
|
32
|
-
function App(
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
function App() {
|
|
31
|
+
const [fields] = React.useState([
|
|
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
|
+
}
|
|
55
|
+
]);
|
|
41
56
|
|
|
42
57
|
return (
|
|
43
58
|
<QuestionnaireRenderer
|
|
59
|
+
questionnaireId="demo-questionnaire"
|
|
44
60
|
fields={fields}
|
|
45
|
-
onChange={
|
|
46
|
-
onSubmit={
|
|
47
|
-
questionnaireId="patient-intake-v1"
|
|
48
|
-
subjectId="patient-12345"
|
|
61
|
+
onChange={(updatedFields) => console.log('Changed:', updatedFields)}
|
|
62
|
+
onSubmit={(fhirResponse) => console.log('Submitted:', fhirResponse)}
|
|
49
63
|
/>
|
|
50
64
|
);
|
|
51
65
|
}
|
|
52
66
|
```
|
|
53
67
|
|
|
54
|
-
###
|
|
55
|
-
|
|
56
|
-
The `fields` prop accepts any data source (API, database, local storage) that matches this JSON structure:
|
|
57
|
-
|
|
58
|
-
```js
|
|
59
|
-
// Example: Simple questionnaire data
|
|
60
|
-
[
|
|
61
|
-
{
|
|
62
|
-
id: '1',
|
|
63
|
-
fieldType: 'input',
|
|
64
|
-
question: 'What is your name?',
|
|
65
|
-
answer: ''
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: '2',
|
|
69
|
-
fieldType: 'radio',
|
|
70
|
-
question: 'Select your role',
|
|
71
|
-
options: ['Developer', 'Designer', 'Manager'],
|
|
72
|
-
selected: null
|
|
73
|
-
}
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
// Example: Complex medical screening with sections and conditional logic
|
|
77
|
-
[
|
|
78
|
-
{
|
|
79
|
-
fieldType: "section",
|
|
80
|
-
title: "Patient Information",
|
|
81
|
-
id: "sec-patient-info",
|
|
82
|
-
fields: [
|
|
83
|
-
{ fieldType: "input", question: "First name", answer: "", id: "pi-first-name" },
|
|
84
|
-
{ fieldType: "input", question: "Last name", answer: "", id: "pi-last-name" },
|
|
85
|
-
{
|
|
86
|
-
fieldType: "selection",
|
|
87
|
-
question: "Biological sex",
|
|
88
|
-
options: [
|
|
89
|
-
{ id: "pi-sex-m", value: "Male" },
|
|
90
|
-
{ id: "pi-sex-f", value: "Female" }
|
|
91
|
-
],
|
|
92
|
-
selected: null,
|
|
93
|
-
id: "pi-sex"
|
|
94
|
-
}
|
|
95
|
-
]
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
fieldType: "section",
|
|
99
|
-
title: "Pregnancy & OB",
|
|
100
|
-
id: "sec-pregnancy",
|
|
101
|
-
enableWhen: {
|
|
102
|
-
logic: "AND",
|
|
103
|
-
conditions: [
|
|
104
|
-
{ targetId: "pi-sex", operator: "equals", value: "pi-sex-f" }
|
|
105
|
-
]
|
|
106
|
-
},
|
|
107
|
-
fields: [
|
|
108
|
-
{
|
|
109
|
-
fieldType: "radio",
|
|
110
|
-
question: "Are you currently pregnant?",
|
|
111
|
-
options: [
|
|
112
|
-
{ id: "preg-yes", value: "Yes" },
|
|
113
|
-
{ id: "preg-no", value: "No" }
|
|
114
|
-
],
|
|
115
|
-
selected: null,
|
|
116
|
-
id: "preg-status"
|
|
117
|
-
}
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
]
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Any JSON object matching this structure works** - whether from your backend API, a database query, local storage, or a CMS.
|
|
124
|
-
|
|
125
|
-
## 📖 Props
|
|
126
|
-
|
|
127
|
-
### `QuestionnaireRenderer`
|
|
128
|
-
|
|
129
|
-
| Prop | Type | Default | Description |
|
|
130
|
-
|------|------|---------|-------------|
|
|
131
|
-
| `fields` | `Array` | **Required** | Questionnaire definition from your data source (API, database, etc.) |
|
|
132
|
-
| `onChange` | `Function` | `undefined` | Callback when answers change: `(fields) => void` |
|
|
133
|
-
| `onSubmit` | `Function` | `undefined` | Callback on submit: `(fhirResponse) => void` |
|
|
134
|
-
| `questionnaireId` | `String` | `'questionnaire-1'` | ID for FHIR Questionnaire reference |
|
|
135
|
-
| `subjectId` | `String` | `undefined` | Patient/subject ID for FHIR response |
|
|
136
|
-
| `className` | `String` | `''` | Additional CSS classes |
|
|
137
|
-
| `fullHeight` | `Boolean` | `false` | Use full viewport height |
|
|
138
|
-
|
|
139
|
-
## ✨ Features
|
|
140
|
-
|
|
141
|
-
### ✅ Read-Only Display
|
|
142
|
-
|
|
143
|
-
- Displays questionnaire fields without editing controls
|
|
144
|
-
- Users can **fill out** the form but **cannot add/remove/reorder** fields
|
|
145
|
-
|
|
146
|
-
### 📋 Supported Field Types
|
|
147
|
-
|
|
148
|
-
- **Text Input** - Single-line text entry
|
|
149
|
-
- **Radio Buttons** - Single choice selection
|
|
150
|
-
- **Checkboxes** - Multiple choice selection
|
|
151
|
-
- **Dropdown** - Select menu
|
|
152
|
-
- **Section** - Grouped fields with collapse/expand
|
|
153
|
-
|
|
154
|
-
### 🔀 Conditional Logic (enableWhen)
|
|
155
|
-
|
|
156
|
-
Automatically shows/hides fields based on answers:
|
|
157
|
-
|
|
158
|
-
```jsx
|
|
159
|
-
const fields = [
|
|
160
|
-
{
|
|
161
|
-
id: '1',
|
|
162
|
-
fieldType: 'radio',
|
|
163
|
-
question: 'Do you have symptoms?',
|
|
164
|
-
options: ['Yes', 'No'],
|
|
165
|
-
selected: null
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: '2',
|
|
169
|
-
fieldType: 'input',
|
|
170
|
-
question: 'Describe your symptoms',
|
|
171
|
-
answer: '',
|
|
172
|
-
enableWhen: [
|
|
173
|
-
{
|
|
174
|
-
question: '1', // ID of field to check
|
|
175
|
-
operator: 'equals',
|
|
176
|
-
answer: 'Yes'
|
|
177
|
-
}
|
|
178
|
-
]
|
|
179
|
-
}
|
|
180
|
-
];
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Field `2` only appears when field `1` is answered with "Yes".
|
|
68
|
+
### 🌐 Standalone Web Component (Framework-Agnostic)
|
|
184
69
|
|
|
185
|
-
|
|
70
|
+
✨ Zero dependencies - works with any framework or vanilla JS.
|
|
186
71
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
status: "completed",
|
|
195
|
-
authored: "2025-10-02T10:30:00Z",
|
|
196
|
-
subject: {
|
|
197
|
-
reference: "Patient/patient-12345"
|
|
198
|
-
},
|
|
199
|
-
item: [
|
|
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 = [
|
|
200
79
|
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
},
|
|
204
90
|
{
|
|
205
|
-
|
|
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
|
|
206
99
|
}
|
|
207
100
|
]
|
|
208
101
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
## 🎯 Usage Examples
|
|
215
|
-
|
|
216
|
-
### Saving Responses
|
|
217
|
-
|
|
218
|
-
```jsx
|
|
219
|
-
import { QuestionnaireRenderer } from '@mieweb/forms-renderer';
|
|
220
|
-
import { useState } from 'react';
|
|
221
|
-
|
|
222
|
-
function FormPage() {
|
|
223
|
-
const [responses, setResponses] = useState([]);
|
|
224
|
-
|
|
225
|
-
const handleSubmit = async (fhirResponse) => {
|
|
226
|
-
// Save to backend
|
|
227
|
-
await fetch('/api/responses', {
|
|
228
|
-
method: 'POST',
|
|
229
|
-
headers: { 'Content-Type': 'application/json' },
|
|
230
|
-
body: JSON.stringify(fhirResponse)
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
setResponses([...responses, fhirResponse]);
|
|
234
|
-
alert('Form submitted successfully!');
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
renderer.onSubmit = (fhirResponse) => {
|
|
105
|
+
console.log('Form submitted:', fhirResponse);
|
|
235
106
|
};
|
|
107
|
+
</script>
|
|
236
108
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
questionnaireId="patient-intake"
|
|
242
|
-
subjectId="patient-67890"
|
|
243
|
-
/>
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Pre-filled Form
|
|
249
|
-
|
|
250
|
-
```jsx
|
|
251
|
-
const prefilledFields = [
|
|
252
|
-
{
|
|
253
|
-
id: '1',
|
|
254
|
-
fieldType: 'input',
|
|
255
|
-
question: 'Full Name',
|
|
256
|
-
answer: 'Jane Doe' // Pre-filled
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
id: '2',
|
|
260
|
-
fieldType: 'radio',
|
|
261
|
-
question: 'Gender',
|
|
262
|
-
options: ['Male', 'Female', 'Other'],
|
|
263
|
-
selected: 'Female' // Pre-filled
|
|
264
|
-
}
|
|
265
|
-
];
|
|
266
|
-
|
|
267
|
-
<QuestionnaireRenderer fields={prefilledFields} />
|
|
109
|
+
<questionnaire-renderer
|
|
110
|
+
questionnaire-id="standalone-demo"
|
|
111
|
+
full-height>
|
|
112
|
+
</questionnaire-renderer>
|
|
268
113
|
```
|
|
269
114
|
|
|
270
|
-
|
|
115
|
+
## ⚙️ Props/Attributes
|
|
271
116
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
275
125
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
});
|
|
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)
|
|
284
132
|
|
|
285
|
-
|
|
286
|
-
};
|
|
133
|
+
## 🔧 Field Types
|
|
287
134
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
/>
|
|
294
|
-
{Object.values(errors).map(err => (
|
|
295
|
-
<p className="text-red-500">{err}</p>
|
|
296
|
-
))}
|
|
297
|
-
</div>
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
```
|
|
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
|
|
301
140
|
|
|
302
|
-
##
|
|
141
|
+
## 🔀 Conditional Logic (enableWhen)
|
|
303
142
|
|
|
304
|
-
Fields
|
|
143
|
+
Fields can be shown/hidden based on other field values. Both examples include conditional logic:
|
|
305
144
|
|
|
306
|
-
|
|
145
|
+
From [`example-react.jsx`](./example-react.jsx):
|
|
146
|
+
```javascript
|
|
307
147
|
{
|
|
308
|
-
id: '
|
|
309
|
-
fieldType: '
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
+
]
|
|
156
|
+
},
|
|
157
|
+
fields: [
|
|
158
|
+
{
|
|
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
|
+
}
|
|
169
|
+
}
|
|
170
|
+
]
|
|
317
171
|
}
|
|
318
172
|
```
|
|
319
173
|
|
|
320
|
-
##
|
|
321
|
-
|
|
322
|
-
- **4.85 KB** (ESM, uncompressed)
|
|
323
|
-
- Very lightweight - perfect for embedding in patient portals
|
|
324
|
-
|
|
325
|
-
## 🎨 Styling
|
|
326
|
-
|
|
327
|
-
**CSS is automatically included** when you import the package! The styles come bundled via the `@mieweb/forms-engine` dependency.
|
|
328
|
-
|
|
329
|
-
Override with custom CSS:
|
|
174
|
+
## 🏥 FHIR Output
|
|
330
175
|
|
|
331
|
-
|
|
332
|
-
.qr-renderer-root {
|
|
333
|
-
max-width: 800px;
|
|
334
|
-
margin: 0 auto;
|
|
335
|
-
}
|
|
176
|
+
The `onSubmit` callback receives a FHIR QuestionnaireResponse:
|
|
336
177
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
178
|
+
```javascript
|
|
179
|
+
{
|
|
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
|
+
]
|
|
341
191
|
}
|
|
342
192
|
```
|
|
343
193
|
|
|
344
|
-
##
|
|
194
|
+
## 📊 Bundle Sizes
|
|
345
195
|
|
|
346
|
-
-
|
|
347
|
-
-
|
|
196
|
+
- **⚛️ React version**: ~24 KB (requires peer deps)
|
|
197
|
+
- **🌐 Standalone version**: ~819 KB (zero dependencies)
|
|
348
198
|
|
|
349
|
-
##
|
|
199
|
+
## 📚 Documentation
|
|
350
200
|
|
|
351
|
-
|
|
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)
|