@welshare/questionnaire 0.1.2 → 0.2.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.
Files changed (96) hide show
  1. package/README.md +151 -0
  2. package/dist/esm/components/bmi-form.d.ts +68 -0
  3. package/dist/esm/components/bmi-form.d.ts.map +1 -0
  4. package/dist/esm/components/bmi-form.js +138 -0
  5. package/dist/esm/components/question-renderer.d.ts +6 -1
  6. package/dist/esm/components/question-renderer.d.ts.map +1 -1
  7. package/dist/esm/components/question-renderer.js +25 -14
  8. package/dist/esm/components/questions/decimal-question.d.ts +8 -1
  9. package/dist/esm/components/questions/decimal-question.d.ts.map +1 -1
  10. package/dist/esm/components/questions/decimal-question.js +19 -1
  11. package/dist/esm/components/questions/multiple-choice-question.d.ts.map +1 -1
  12. package/dist/esm/components/questions/multiple-choice-question.js +2 -2
  13. package/dist/esm/contexts/questionnaire-context.d.ts.map +1 -1
  14. package/dist/esm/contexts/questionnaire-context.js +3 -2
  15. package/dist/esm/index.d.ts +6 -2
  16. package/dist/esm/index.d.ts.map +1 -1
  17. package/dist/esm/index.js +5 -1
  18. package/dist/esm/lib/bmi-helpers.d.ts +50 -0
  19. package/dist/esm/lib/bmi-helpers.d.ts.map +1 -0
  20. package/dist/esm/lib/bmi-helpers.js +69 -0
  21. package/dist/esm/lib/constants.d.ts +94 -0
  22. package/dist/esm/lib/constants.d.ts.map +1 -0
  23. package/dist/esm/lib/constants.js +93 -0
  24. package/dist/esm/lib/questionnaire-utils.d.ts +21 -1
  25. package/dist/esm/lib/questionnaire-utils.d.ts.map +1 -1
  26. package/dist/esm/lib/questionnaire-utils.js +85 -4
  27. package/dist/esm/types/fhir.d.ts +1 -0
  28. package/dist/esm/types/fhir.d.ts.map +1 -1
  29. package/dist/esm/types/index.d.ts +25 -0
  30. package/dist/esm/types/index.d.ts.map +1 -1
  31. package/dist/styles.css +108 -0
  32. package/package.json +17 -6
  33. package/dist/node_modules/@welshare/questionnaire/.tshy/build.json +0 -8
  34. package/dist/node_modules/@welshare/questionnaire/.tshy/esm.json +0 -16
  35. package/dist/node_modules/@welshare/questionnaire/LICENSE +0 -7
  36. package/dist/node_modules/@welshare/questionnaire/README.md +0 -173
  37. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.d.ts +0 -44
  38. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.d.ts.map +0 -1
  39. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.js +0 -28
  40. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.d.ts +0 -80
  41. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.d.ts.map +0 -1
  42. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.js +0 -183
  43. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.d.ts +0 -15
  44. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.d.ts.map +0 -1
  45. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.js +0 -19
  46. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.d.ts +0 -19
  47. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.d.ts.map +0 -1
  48. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.js +0 -23
  49. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.d.ts +0 -12
  50. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.d.ts.map +0 -1
  51. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.js +0 -7
  52. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.d.ts +0 -18
  53. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.d.ts.map +0 -1
  54. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.js +0 -24
  55. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.d.ts +0 -20
  56. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.d.ts.map +0 -1
  57. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.js +0 -39
  58. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.d.ts +0 -12
  59. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.d.ts.map +0 -1
  60. package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.js +0 -7
  61. package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.d.ts +0 -41
  62. package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.d.ts.map +0 -1
  63. package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.js +0 -350
  64. package/dist/node_modules/@welshare/questionnaire/dist/esm/index.d.ts +0 -7
  65. package/dist/node_modules/@welshare/questionnaire/dist/esm/index.d.ts.map +0 -1
  66. package/dist/node_modules/@welshare/questionnaire/dist/esm/index.js +0 -6
  67. package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.d.ts +0 -33
  68. package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.d.ts.map +0 -1
  69. package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.js +0 -99
  70. package/dist/node_modules/@welshare/questionnaire/dist/esm/package.json +0 -3
  71. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.d.ts +0 -117
  72. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.d.ts.map +0 -1
  73. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.js +0 -3
  74. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.d.ts +0 -51
  75. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.d.ts.map +0 -1
  76. package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.js +0 -1
  77. package/dist/node_modules/@welshare/questionnaire/dist/styles.css +0 -467
  78. package/dist/node_modules/@welshare/questionnaire/dist/tokens.css +0 -130
  79. package/dist/node_modules/@welshare/questionnaire/package.json +0 -85
  80. package/dist/node_modules/@welshare/questionnaire/src/components/debug-section.tsx +0 -116
  81. package/dist/node_modules/@welshare/questionnaire/src/components/question-renderer.tsx +0 -391
  82. package/dist/node_modules/@welshare/questionnaire/src/components/questionnaire-styles.css +0 -467
  83. package/dist/node_modules/@welshare/questionnaire/src/components/questionnaire-tokens.css +0 -130
  84. package/dist/node_modules/@welshare/questionnaire/src/components/questions/boolean-question.tsx +0 -72
  85. package/dist/node_modules/@welshare/questionnaire/src/components/questions/choice-question.tsx +0 -68
  86. package/dist/node_modules/@welshare/questionnaire/src/components/questions/decimal-question.tsx +0 -32
  87. package/dist/node_modules/@welshare/questionnaire/src/components/questions/integer-question.tsx +0 -87
  88. package/dist/node_modules/@welshare/questionnaire/src/components/questions/multiple-choice-question.tsx +0 -119
  89. package/dist/node_modules/@welshare/questionnaire/src/components/questions/string-question.tsx +0 -31
  90. package/dist/node_modules/@welshare/questionnaire/src/contexts/questionnaire-context.tsx +0 -499
  91. package/dist/node_modules/@welshare/questionnaire/src/index.ts +0 -41
  92. package/dist/node_modules/@welshare/questionnaire/src/lib/__tests__/questionnaire-utils.test.ts +0 -578
  93. package/dist/node_modules/@welshare/questionnaire/src/lib/questionnaire-utils.ts +0 -122
  94. package/dist/node_modules/@welshare/questionnaire/src/types/fhir.ts +0 -126
  95. package/dist/node_modules/@welshare/questionnaire/src/types/index.ts +0 -44
  96. package/dist/node_modules/@welshare/questionnaire/tsconfig.json +0 -16
package/README.md CHANGED
@@ -97,12 +97,74 @@ const {
97
97
 
98
98
  **Supported Types:** `choice`, `boolean`, `integer`, `decimal`, `string`, `text`
99
99
 
100
+ ### BmiForm
101
+
102
+ A controlled component for collecting height, weight, and calculating BMI. The component acts as an input helper that manages BMI calculation internally and reports changes to the parent via callbacks. Unit system (metric/imperial) is managed internally and automatically clears all fields when switched.
103
+
104
+ **Props:**
105
+ - `height: number` - Current height value (0 when empty)
106
+ - `weight: number` - Current weight value (0 when empty)
107
+ - `bmi: number` - Current BMI value (calculated and set by the component, 0 when not calculated)
108
+ - `onHeightChange: (value: number, unit: "cm" | "in") => void` - Called when height changes, includes current unit
109
+ - `onWeightChange: (value: number, unit: "kg" | "lb") => void` - Called when weight changes, includes current unit
110
+ - `onBmiChange: (value: number) => void` - Called when BMI is calculated or cleared
111
+ - `className?: string` - Optional CSS classes
112
+
113
+ **Features:**
114
+ - Controlled numeric values (height, weight, bmi) managed by parent
115
+ - BMI calculation handled internally by the component
116
+ - Unit system managed internally (defaults to metric)
117
+ - Automatically clears all fields (sets to 0) when switching unit systems
118
+ - No unit conversion - values are cleared on unit system change
119
+ - Uses consistent styling with questionnaire components
120
+
121
+ **Example:**
122
+
123
+ ```tsx
124
+ import { useState } from 'react';
125
+ import { BmiForm, getBmiCategory } from '@welshare/questionnaire';
126
+ import '@welshare/questionnaire/tokens.css';
127
+ import '@welshare/questionnaire/styles.css';
128
+
129
+ function MyComponent() {
130
+ const [height, setHeight] = useState(0);
131
+ const [weight, setWeight] = useState(0);
132
+ const [bmi, setBmi] = useState(0);
133
+
134
+ const handleBmiChange = (value: number) => {
135
+ setBmi(value);
136
+
137
+ // Optional: Get BMI category
138
+ if (value) {
139
+ const category = getBmiCategory(value);
140
+ console.log(`BMI Category: ${category}`);
141
+ }
142
+ };
143
+
144
+ return (
145
+ <BmiForm
146
+ height={height}
147
+ weight={weight}
148
+ bmi={bmi}
149
+ onHeightChange={(value, unit) => setHeight(value)}
150
+ onWeightChange={(value, unit) => setWeight(value)}
151
+ onBmiChange={handleBmiChange}
152
+ />
153
+ );
154
+ }
155
+ ```
156
+
100
157
  ### Utilities
101
158
 
159
+ **Questionnaire Utilities:**
102
160
  - `getVisiblePages(questionnaire)` - Get visible page groups
103
161
  - `calculateProgress(currentIndex, total)` - Calculate progress percentage
104
162
  - `getAllQuestionsFromPage(pageItem)` - Get all questions from a page
105
163
 
164
+ **BMI Helper Functions:**
165
+ - `calculateBmi(height, weight, unitSystem)` - Calculate BMI from height and weight
166
+ - `getBmiCategory(bmi)` - Get WHO BMI category (Underweight, Normal weight, Overweight, Obese)
167
+
106
168
  ## Theming
107
169
 
108
170
  Override CSS custom properties:
@@ -168,6 +230,95 @@ Override CSS custom properties:
168
230
  }
169
231
  ```
170
232
 
233
+ ### Input Helpers
234
+
235
+ Input helpers allow you to provide auxiliary UI (like calculators or lookup dialogs) to assist users in filling out form fields. The library detects the extension and calls your `renderHelperTrigger` function, but you control the dialog/modal implementation.
236
+
237
+ **FHIR Extension:**
238
+ ```json
239
+ {
240
+ "linkId": "bmi",
241
+ "text": "Body Mass Index",
242
+ "type": "decimal",
243
+ "extension": [{
244
+ "url": "http://codes.welshare.app/StructureDefinition/questionnaire-inputHelper",
245
+ "valueCodeableConcept": {
246
+ "coding": [{
247
+ "system": "http://codes.welshare.app/input-helper-type",
248
+ "code": "bmi-calculator",
249
+ "display": "BMI Calculator"
250
+ }],
251
+ "text": "Calculate BMI from height and weight"
252
+ }
253
+ }]
254
+ }
255
+ ```
256
+
257
+ **Implementation:**
258
+
259
+ ```tsx
260
+ import { useState } from 'react';
261
+ import { QuestionRenderer, BmiForm, type HelperTriggerProps } from '@welshare/questionnaire';
262
+
263
+ function MyQuestionnaireRenderer({ item }) {
264
+ const [showBmiDialog, setShowBmiDialog] = useState(false);
265
+ const [helperCallback, setHelperCallback] = useState<((v: string | number) => void) | null>(null);
266
+
267
+ const handleHelperTrigger = ({ helper, onValueSelected }: HelperTriggerProps) => {
268
+ if (helper.type === 'bmi-calculator') {
269
+ return (
270
+ <button
271
+ type="button"
272
+ onClick={() => {
273
+ setHelperCallback(() => onValueSelected);
274
+ setShowBmiDialog(true);
275
+ }}
276
+ >
277
+ {helper.display || 'Calculate'}
278
+ </button>
279
+ );
280
+ }
281
+ return null;
282
+ };
283
+
284
+ return (
285
+ <>
286
+ <QuestionRenderer
287
+ item={item}
288
+ renderHelperTrigger={handleHelperTrigger}
289
+ />
290
+
291
+ {showBmiDialog && (
292
+ <Dialog onClose={() => setShowBmiDialog(false)}>
293
+ <BmiForm
294
+ onSubmit={({ bmi }) => {
295
+ helperCallback?.(bmi);
296
+ setShowBmiDialog(false);
297
+ }}
298
+ />
299
+ </Dialog>
300
+ )}
301
+ </>
302
+ );
303
+ }
304
+ ```
305
+
306
+ **Helper Trigger Props:**
307
+ - `helper: InputHelperConfig` - Configuration from the extension
308
+ - `type: string` - Helper identifier (e.g., "bmi-calculator")
309
+ - `display?: string` - Display name from the extension
310
+ - `description?: string` - Description/tooltip text
311
+ - `linkId: string` - The question's linkId
312
+ - `currentValue?: T` - Current field value (if any), where T defaults to `string | number`
313
+ - `onValueSelected: (value: T) => void` - Callback to update the field
314
+
315
+ **Note:** `HelperTriggerProps<T>` is a generic interface. For decimal questions, it's specialized as `HelperTriggerProps<number>`, but you can use different types for other question types (e.g., `HelperTriggerProps<string>` for text inputs).
316
+
317
+ **Note:** The library only renders the trigger element you provide. You are responsible for implementing the dialog/modal UI, as this allows you to use your preferred dialog system (e.g., shadcn/ui, Radix UI, MUI, etc.).
318
+
319
+
320
+
321
+
171
322
  ## License
172
323
 
173
324
  MIT © Welshare UG (haftungsbeschränkt)
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Result object returned on submission - all values in metric units
3
+ */
4
+ export interface BmiSubmissionResult {
5
+ /** Height in centimeters */
6
+ heightCm: number;
7
+ /** Weight in kilograms */
8
+ weightKg: number;
9
+ /** Calculated BMI (kg/m²) */
10
+ bmi: number;
11
+ }
12
+ /**
13
+ * Initial values for the BMI form - always in metric units
14
+ */
15
+ export interface BmiInitialValues {
16
+ /** Height in centimeters */
17
+ heightCm?: number;
18
+ /** Weight in kilograms */
19
+ weightKg?: number;
20
+ }
21
+ export interface BmiFormProps {
22
+ /**
23
+ * Initial values in metric units (cm, kg).
24
+ * These are used to populate the form on mount.
25
+ * If unitPreference is "imperial", they will be converted for display.
26
+ */
27
+ initialValues?: BmiInitialValues;
28
+ /**
29
+ * Preferred unit system for initial display.
30
+ * When "imperial", initial metric values are converted for display.
31
+ * Defaults to "metric".
32
+ */
33
+ unitPreference?: "metric" | "imperial";
34
+ /**
35
+ * Callback fired when user submits/confirms the BMI values.
36
+ * Returns all values in metric units (cm, kg).
37
+ */
38
+ onSubmit?: (result: BmiSubmissionResult) => void;
39
+ /**
40
+ * Callback fired whenever any value changes.
41
+ * Returns all values in metric units (cm, kg).
42
+ * Use this for real-time updates without requiring explicit submission.
43
+ */
44
+ onChange?: (result: BmiSubmissionResult) => void;
45
+ /**
46
+ * Additional CSS class names
47
+ */
48
+ className?: string;
49
+ }
50
+ /**
51
+ * BMI Form Component
52
+ *
53
+ * A self-contained form component for entering height and weight measurements.
54
+ * Manages all state internally and supports both metric (cm/kg) and imperial (ft+in/lb) input.
55
+ *
56
+ * **Clinical Standard Compliance:**
57
+ * - All external communication uses metric units (cm, kg)
58
+ * - Imperial inputs are UI conveniences that convert to metric internally
59
+ * - Initial values must always be provided in metric
60
+ * - BMI is always calculated using the metric formula: kg / m²
61
+ *
62
+ * **Unit System Behavior:**
63
+ * - Switching unit systems clears all input fields (no automatic conversion)
64
+ * - Initial values provided in metric are converted to imperial for display
65
+ * only when unitPreference is "imperial"
66
+ */
67
+ export declare const BmiForm: ({ initialValues, unitPreference, onSubmit, onChange, className, }: BmiFormProps) => import("react/jsx-runtime").JSX.Element;
68
+ //# sourceMappingURL=bmi-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bmi-form.d.ts","sourceRoot":"","sources":["../../../src/components/bmi-form.tsx"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,gBAAgB,CAAC;IAEjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEjD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEjD;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,OAAO,GAAI,mEAMrB,YAAY,4CAmSd,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback, useRef } from "react";
3
+ import { calculateBmi, cmToFeetAndInches, feetAndInchesToCm, kgToLbs, lbsToKg, } from "../lib/bmi-helpers.js";
4
+ /**
5
+ * BMI Form Component
6
+ *
7
+ * A self-contained form component for entering height and weight measurements.
8
+ * Manages all state internally and supports both metric (cm/kg) and imperial (ft+in/lb) input.
9
+ *
10
+ * **Clinical Standard Compliance:**
11
+ * - All external communication uses metric units (cm, kg)
12
+ * - Imperial inputs are UI conveniences that convert to metric internally
13
+ * - Initial values must always be provided in metric
14
+ * - BMI is always calculated using the metric formula: kg / m²
15
+ *
16
+ * **Unit System Behavior:**
17
+ * - Switching unit systems clears all input fields (no automatic conversion)
18
+ * - Initial values provided in metric are converted to imperial for display
19
+ * only when unitPreference is "imperial"
20
+ */
21
+ export const BmiForm = ({ initialValues, unitPreference = "metric", onSubmit, onChange, className = "", }) => {
22
+ // Track if this is the initial render to handle initial value conversion
23
+ const isInitialRender = useRef(true);
24
+ // Unit system state - starts with preference
25
+ const [unitSystem, setUnitSystem] = useState(unitPreference);
26
+ // Internal state for metric display (cm)
27
+ const [heightCmInput, setHeightCmInput] = useState("");
28
+ // Internal state for imperial height display (ft + in)
29
+ const [feetInput, setFeetInput] = useState("");
30
+ const [inchesInput, setInchesInput] = useState("");
31
+ // Internal state for metric weight display (kg)
32
+ const [weightKgInput, setWeightKgInput] = useState("");
33
+ // Internal state for imperial weight display (lb)
34
+ const [weightLbInput, setWeightLbInput] = useState("");
35
+ // The canonical metric values (source of truth for calculations)
36
+ const [heightCm, setHeightCm] = useState(0);
37
+ const [weightKg, setWeightKg] = useState(0);
38
+ // Calculated BMI (always from metric values)
39
+ const [bmi, setBmi] = useState(0);
40
+ // Initialize form with provided values (converted to display units if needed)
41
+ useEffect(() => {
42
+ if (!isInitialRender.current)
43
+ return;
44
+ isInitialRender.current = false;
45
+ const initialHeightCm = initialValues?.heightCm ?? 0;
46
+ const initialWeightKg = initialValues?.weightKg ?? 0;
47
+ // Set canonical metric values
48
+ setHeightCm(initialHeightCm);
49
+ setWeightKg(initialWeightKg);
50
+ if (unitPreference === "metric") {
51
+ // Display as metric
52
+ if (initialHeightCm > 0) {
53
+ setHeightCmInput(String(initialHeightCm));
54
+ }
55
+ if (initialWeightKg > 0) {
56
+ setWeightKgInput(String(initialWeightKg));
57
+ }
58
+ }
59
+ else {
60
+ // Convert metric to imperial for display
61
+ if (initialHeightCm > 0) {
62
+ const { feet, inches } = cmToFeetAndInches(initialHeightCm);
63
+ setFeetInput(String(feet));
64
+ setInchesInput(String(inches));
65
+ }
66
+ if (initialWeightKg > 0) {
67
+ const lbs = Math.round(kgToLbs(initialWeightKg) * 10) / 10;
68
+ setWeightLbInput(String(lbs));
69
+ }
70
+ }
71
+ }, [initialValues, unitPreference]);
72
+ // Calculate BMI whenever canonical metric values change
73
+ useEffect(() => {
74
+ const calculatedBmi = calculateBmi(heightCm, weightKg);
75
+ setBmi(calculatedBmi);
76
+ }, [heightCm, weightKg]);
77
+ // Notify parent of changes
78
+ useEffect(() => {
79
+ if (onChange && !isInitialRender.current) {
80
+ onChange({ heightCm, weightKg, bmi });
81
+ }
82
+ }, [heightCm, weightKg, bmi, onChange]);
83
+ // Handle unit system change - clear all fields
84
+ const handleUnitSystemChange = useCallback((newSystem) => {
85
+ if (newSystem === unitSystem)
86
+ return;
87
+ // Clear all inputs
88
+ setHeightCmInput("");
89
+ setFeetInput("");
90
+ setInchesInput("");
91
+ setWeightKgInput("");
92
+ setWeightLbInput("");
93
+ // Clear canonical values
94
+ setHeightCm(0);
95
+ setWeightKg(0);
96
+ setBmi(0);
97
+ setUnitSystem(newSystem);
98
+ }, [unitSystem]);
99
+ // Metric height change (cm)
100
+ const handleHeightCmChange = useCallback((value) => {
101
+ setHeightCmInput(value);
102
+ const numValue = parseFloat(value);
103
+ setHeightCm(isNaN(numValue) ? 0 : numValue);
104
+ }, []);
105
+ // Imperial height change (feet)
106
+ const handleFeetChange = useCallback((value) => {
107
+ setFeetInput(value);
108
+ const feet = parseFloat(value) || 0;
109
+ const inches = parseFloat(inchesInput) || 0;
110
+ setHeightCm(feetAndInchesToCm(feet, inches));
111
+ }, [inchesInput]);
112
+ // Imperial height change (inches)
113
+ const handleInchesChange = useCallback((value) => {
114
+ setInchesInput(value);
115
+ const feet = parseFloat(feetInput) || 0;
116
+ const inches = parseFloat(value) || 0;
117
+ setHeightCm(feetAndInchesToCm(feet, inches));
118
+ }, [feetInput]);
119
+ // Metric weight change (kg)
120
+ const handleWeightKgChange = useCallback((value) => {
121
+ setWeightKgInput(value);
122
+ const numValue = parseFloat(value);
123
+ setWeightKg(isNaN(numValue) ? 0 : numValue);
124
+ }, []);
125
+ // Imperial weight change (lb)
126
+ const handleWeightLbChange = useCallback((value) => {
127
+ setWeightLbInput(value);
128
+ const numValue = parseFloat(value);
129
+ setWeightKg(isNaN(numValue) ? 0 : lbsToKg(numValue));
130
+ }, []);
131
+ // Handle form submission
132
+ const handleSubmit = useCallback(() => {
133
+ if (onSubmit) {
134
+ onSubmit({ heightCm, weightKg, bmi });
135
+ }
136
+ }, [onSubmit, heightCm, weightKg, bmi]);
137
+ return (_jsxs("div", { className: `wq-bmi-form ${className}`, children: [_jsxs("div", { className: "wq-bmi-unit-toggle", children: [_jsx("button", { type: "button", className: `wq-bmi-unit-option ${unitSystem === "metric" ? "wq-selected" : ""}`, onClick: () => handleUnitSystemChange("metric"), "aria-pressed": unitSystem === "metric", children: "Metric" }), _jsx("button", { type: "button", className: `wq-bmi-unit-option ${unitSystem === "imperial" ? "wq-selected" : ""}`, onClick: () => handleUnitSystemChange("imperial"), "aria-pressed": unitSystem === "imperial", children: "Imperial" })] }), _jsxs("div", { className: "wq-bmi-field", children: [_jsx("label", { htmlFor: "bmi-height", className: "wq-bmi-label", children: unitSystem === "metric" ? "Height (cm)" : "Height (ft + in)" }), unitSystem === "metric" ? (_jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-height", type: "number", className: "wq-question-input", value: heightCmInput, onChange: (e) => handleHeightCmChange(e.target.value), placeholder: "Enter height in cm", step: "0.1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "cm" })] })) : (_jsxs("div", { className: "wq-bmi-imperial-height", children: [_jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-height-feet", type: "number", className: "wq-question-input", value: feetInput, onChange: (e) => handleFeetChange(e.target.value), placeholder: "Feet", step: "1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "ft" })] }), _jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-height-inches", type: "number", className: "wq-question-input", value: inchesInput, onChange: (e) => handleInchesChange(e.target.value), placeholder: "Inches", step: "0.1", min: "0", max: "11.9" }), _jsx("span", { className: "wq-bmi-unit", children: "in" })] })] }))] }), _jsxs("div", { className: "wq-bmi-field", children: [_jsx("label", { htmlFor: "bmi-weight", className: "wq-bmi-label", children: unitSystem === "metric" ? "Weight (kg)" : "Weight (lbs)" }), _jsx("div", { className: "wq-bmi-input-group", children: unitSystem === "metric" ? (_jsxs(_Fragment, { children: [_jsx("input", { id: "bmi-weight", type: "number", className: "wq-question-input", value: weightKgInput, onChange: (e) => handleWeightKgChange(e.target.value), placeholder: "Enter weight in kg", step: "0.1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "kg" })] })) : (_jsxs(_Fragment, { children: [_jsx("input", { id: "bmi-weight", type: "number", className: "wq-question-input", value: weightLbInput, onChange: (e) => handleWeightLbChange(e.target.value), placeholder: "Enter weight in lbs", step: "0.1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "lbs" })] })) })] }), _jsxs("div", { className: "wq-bmi-field", children: [_jsx("label", { htmlFor: "bmi-result", className: "wq-bmi-label", children: "BMI" }), _jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-result", type: "text", className: "wq-question-input wq-bmi-result", value: bmi ? bmi.toFixed(1) : "", placeholder: "Calculated automatically", readOnly: true, disabled: true }), _jsx("span", { className: "wq-bmi-unit", children: "kg/m\u00B2" })] })] }), bmi > 0 && (_jsx("div", { className: "wq-bmi-info", children: _jsx("p", { className: "wq-text-secondary", children: "BMI is calculated automatically from height and weight measurements." }) })), onSubmit && (_jsx("div", { className: "wq-bmi-actions", children: _jsx("button", { type: "button", className: "wq-button wq-button-primary", onClick: handleSubmit, disabled: !bmi, children: "Confirm Measurements" }) }))] }));
138
+ };
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { QuestionnaireItem } from "../types/fhir.js";
3
- import { RadioInputProps, CheckboxInputProps } from "../types/index.js";
3
+ import { RadioInputProps, CheckboxInputProps, HelperTriggerProps } from "../types/index.js";
4
4
  export interface QuestionRendererProps {
5
5
  item: QuestionnaireItem;
6
6
  /**
@@ -66,6 +66,11 @@ export interface QuestionRendererProps {
66
66
  * ```
67
67
  */
68
68
  renderCheckboxInput?: (props: CheckboxInputProps) => ReactNode;
69
+ /**
70
+ * Custom renderer for helper triggers on decimal inputs.
71
+ * Called when a decimal question has an inputHelper extension.
72
+ */
73
+ renderHelperTrigger?: (props: HelperTriggerProps) => ReactNode;
69
74
  }
70
75
  /**
71
76
  * Wrapper component that combines the library's QuestionRenderer with debug functionality.
@@ -1 +1 @@
1
- {"version":3,"file":"question-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/question-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAQ1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,iBAAiB,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;IACzD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAChE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,eAAe,qBAAqB,4CAyBpE,CAAC"}
1
+ {"version":3,"file":"question-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/question-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAU1D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5F,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,iBAAiB,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;IACzD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,SAAS,CAAC;IAC/D;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAChE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,eAAe,qBAAqB,4CAyBpE,CAAC"}
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useQuestionnaire } from "../contexts/questionnaire-context.js";
3
+ import { isQuestionEnabled } from "../lib/questionnaire-utils.js";
4
+ import { FHIR_EXTENSIONS, WELSHARE_EXTENSIONS, FHIR_CODE_SYSTEMS } from "../lib/constants.js";
3
5
  import { DebugSection } from "./debug-section.js";
4
6
  import { ChoiceQuestion } from "./questions/choice-question.js";
5
7
  import { MultipleChoiceQuestion } from "./questions/multiple-choice-question.js";
6
8
  import { IntegerQuestion } from "./questions/integer-question.js";
7
- import { DecimalQuestion } from "./questions/decimal-question.js";
9
+ import { DecimalQuestion, getDecimalHelperTriggerProps } from "./questions/decimal-question.js";
8
10
  import { StringQuestion } from "./questions/string-question.js";
9
11
  import { BooleanQuestion } from "./questions/boolean-question.js";
10
12
  /**
@@ -22,7 +24,7 @@ export const QuestionRenderer = (rendererProps) => {
22
24
  const currentAnswer = getAnswer(item.linkId);
23
25
  const currentAnswers = getAnswers(item.linkId);
24
26
  // Find LOINC code if present
25
- const loincCode = item.code?.find((coding) => coding.system === "http://loinc.org");
27
+ const loincCode = item.code?.find((coding) => coding.system === FHIR_CODE_SYSTEMS.LOINC);
26
28
  return (_jsxs("div", { children: [_jsx(QuestionRendererInternal, { ...rendererProps }), debugMode && (_jsx(DebugSection, { item: item, currentAnswer: currentAnswer, currentAnswers: currentAnswers, loincCode: loincCode }))] }));
27
29
  };
28
30
  /**
@@ -35,22 +37,26 @@ export const QuestionRenderer = (rendererProps) => {
35
37
  * - Auto-populated (hidden) fields
36
38
  * - Validation error display
37
39
  */
38
- const QuestionRendererInternal = ({ item, className = "", inputClassName = "", choiceClassName = "", renderRadioInput, renderCheckboxInput, }) => {
40
+ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", choiceClassName = "", renderRadioInput, renderCheckboxInput, renderHelperTrigger, }) => {
39
41
  const { updateAnswer, updateMultipleAnswers, getAnswer, getAnswers, hasValidationError, } = useQuestionnaire();
40
42
  const currentAnswer = getAnswer(item.linkId);
41
43
  const currentAnswers = getAnswers(item.linkId);
42
44
  const hasError = hasValidationError(item.linkId);
43
45
  // Check if this field should be hidden (auto-populated)
44
- const isAutoPopulated = item.extension?.some((ext) => ext.url ===
45
- "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden" &&
46
+ const isAutoPopulated = item.extension?.some((ext) => ext.url === FHIR_EXTENSIONS.QUESTIONNAIRE_HIDDEN &&
46
47
  ext.valueBoolean === true);
47
48
  // Don't render auto-populated fields
48
49
  if (isAutoPopulated) {
49
50
  return null;
50
51
  }
52
+ // Don't render questions that don't meet their enableWhen conditions
53
+ if (!isQuestionEnabled(item, getAnswer)) {
54
+ return null;
55
+ }
51
56
  const handleChoiceChange = (valueCoding, valueInteger) => {
52
57
  // For choice questions, only set one field: valueCoding takes precedence
53
- if (valueCoding && (valueCoding.code || valueCoding.display || valueCoding.system)) {
58
+ if (valueCoding &&
59
+ (valueCoding.code || valueCoding.display || valueCoding.system)) {
54
60
  updateAnswer(item.linkId, { valueCoding });
55
61
  }
56
62
  else if (valueInteger !== undefined) {
@@ -60,8 +66,7 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
60
66
  const handleMultipleChoiceToggle = (valueCoding, valueInteger) => {
61
67
  const isSelected = currentAnswers.some((answer) => answer.valueCoding?.code === valueCoding.code);
62
68
  // Check if there's an exclusive option extension
63
- const exclusiveOptionExt = item.extension?.find((ext) => ext.url ===
64
- "http://codes.welshare.app/StructureDefinition/questionnaire-exclusive-option");
69
+ const exclusiveOptionExt = item.extension?.find((ext) => ext.url === WELSHARE_EXTENSIONS.EXCLUSIVE_OPTION);
65
70
  const exclusiveOptionCode = exclusiveOptionExt?.valueString;
66
71
  let newAnswers;
67
72
  if (isSelected) {
@@ -73,7 +78,8 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
73
78
  if (exclusiveOptionCode && valueCoding.code === exclusiveOptionCode) {
74
79
  // Clear all other answers and only keep this one
75
80
  // For choice questions, only set one field: valueCoding takes precedence
76
- const answer = valueCoding && (valueCoding.code || valueCoding.display || valueCoding.system)
81
+ const answer = valueCoding &&
82
+ (valueCoding.code || valueCoding.display || valueCoding.system)
77
83
  ? { valueCoding }
78
84
  : valueInteger !== undefined
79
85
  ? { valueInteger }
@@ -87,7 +93,8 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
87
93
  // Remove exclusive option and add new selection
88
94
  newAnswers = currentAnswers.filter((answer) => answer.valueCoding?.code !== exclusiveOptionCode);
89
95
  // For choice questions, only set one field: valueCoding takes precedence
90
- const answer = valueCoding && (valueCoding.code || valueCoding.display || valueCoding.system)
96
+ const answer = valueCoding &&
97
+ (valueCoding.code || valueCoding.display || valueCoding.system)
91
98
  ? { valueCoding }
92
99
  : valueInteger !== undefined
93
100
  ? { valueInteger }
@@ -99,7 +106,8 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
99
106
  const maxAnswers = item.maxAnswers || Number.MAX_SAFE_INTEGER;
100
107
  if (currentAnswers.length < maxAnswers) {
101
108
  // For choice questions, only set one field: valueCoding takes precedence
102
- const answer = valueCoding && (valueCoding.code || valueCoding.display || valueCoding.system)
109
+ const answer = valueCoding &&
110
+ (valueCoding.code || valueCoding.display || valueCoding.system)
103
111
  ? { valueCoding }
104
112
  : valueInteger !== undefined
105
113
  ? { valueInteger }
@@ -145,8 +153,7 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
145
153
  };
146
154
  // Check if this field should use a slider control
147
155
  const getSliderConfig = () => {
148
- const sliderExt = item.extension?.find((ext) => ext.url ===
149
- "http://codes.welshare.app/StructureDefinition/questionnaire-slider-control");
156
+ const sliderExt = item.extension?.find((ext) => ext.url === WELSHARE_EXTENSIONS.SLIDER_CONTROL);
150
157
  if (!sliderExt?.extension)
151
158
  return null;
152
159
  const minValue = sliderExt.extension.find((e) => e.url === "minValue")?.valueInteger ?? 0;
@@ -179,5 +186,9 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
179
186
  return (_jsxs("div", { className: "wq-unsupported-type", children: ["Unsupported question type: ", item.type] }));
180
187
  }
181
188
  };
182
- return (_jsxs("div", { className: `wq-question-container ${hasError ? "wq-has-error" : ""} ${className}`, children: [_jsxs("div", { className: "wq-question-text", children: [item.text, item.required && _jsx("span", { className: "wq-required-indicator", children: "*" })] }), renderQuestion()] }));
189
+ // Get helper trigger for decimal questions
190
+ const helperTriggerProps = item.type === "decimal"
191
+ ? getDecimalHelperTriggerProps(item, currentAnswer, handleDecimalChange)
192
+ : null;
193
+ return (_jsxs("div", { className: `wq-question-container ${hasError ? "wq-has-error" : ""} ${className}`, children: [_jsxs("div", { className: "wq-question-label-row", children: [_jsxs("div", { className: "wq-question-text", children: [item.text, item.required && _jsx("span", { className: "wq-required-indicator", children: "*" })] }), helperTriggerProps && renderHelperTrigger && (_jsx("div", { className: "wq-question-helper-trigger", children: renderHelperTrigger(helperTriggerProps) }))] }), renderQuestion()] }));
183
194
  };
@@ -1,4 +1,5 @@
1
1
  import type { QuestionnaireItem, QuestionnaireResponseAnswer } from "../../types/fhir.js";
2
+ import type { HelperTriggerProps } from "../../types/index.js";
2
3
  export interface DecimalQuestionProps {
3
4
  item: QuestionnaireItem;
4
5
  currentAnswer?: QuestionnaireResponseAnswer;
@@ -7,6 +8,12 @@ export interface DecimalQuestionProps {
7
8
  }
8
9
  /**
9
10
  * Renders a decimal question with number input
11
+ * Helper trigger is now rendered in the question label row by the parent component
10
12
  */
11
- export declare const DecimalQuestion: ({ item, currentAnswer, onDecimalChange, inputClassName, }: DecimalQuestionProps) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const DecimalQuestion: ({ currentAnswer, onDecimalChange, inputClassName, }: DecimalQuestionProps) => import("react/jsx-runtime").JSX.Element;
14
+ /**
15
+ * Get helper trigger props for a decimal question if available
16
+ * Returns null if no helper is configured
17
+ */
18
+ export declare const getDecimalHelperTriggerProps: (item: QuestionnaireItem, currentAnswer: QuestionnaireResponseAnswer | undefined, onDecimalChange: (value: string) => void) => HelperTriggerProps | null;
12
19
  //# sourceMappingURL=decimal-question.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decimal-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/decimal-question.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,2DAK7B,oBAAoB,4CAWtB,CAAC"}
1
+ {"version":3,"file":"decimal-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/decimal-question.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,qDAI7B,oBAAoB,4CAWtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACvC,MAAM,iBAAiB,EACvB,eAAe,2BAA2B,GAAG,SAAS,EACtD,iBAAiB,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,KACvC,kBAAkB,GAAG,IAavB,CAAC"}
@@ -1,7 +1,25 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { getInputHelper } from "../../lib/questionnaire-utils.js";
2
3
  /**
3
4
  * Renders a decimal question with number input
5
+ * Helper trigger is now rendered in the question label row by the parent component
4
6
  */
5
- export const DecimalQuestion = ({ item, currentAnswer, onDecimalChange, inputClassName = "", }) => {
7
+ export const DecimalQuestion = ({ currentAnswer, onDecimalChange, inputClassName = "", }) => {
6
8
  return (_jsx("input", { type: "number", className: `wq-question-input ${inputClassName}`, value: currentAnswer?.valueDecimal ?? "", onChange: (e) => onDecimalChange(e.target.value), placeholder: "Enter a number", step: "0.1" }));
7
9
  };
10
+ /**
11
+ * Get helper trigger props for a decimal question if available
12
+ * Returns null if no helper is configured
13
+ */
14
+ export const getDecimalHelperTriggerProps = (item, currentAnswer, onDecimalChange) => {
15
+ const helper = getInputHelper(item);
16
+ if (!helper) {
17
+ return null;
18
+ }
19
+ return {
20
+ helper,
21
+ linkId: item.linkId,
22
+ currentValue: currentAnswer?.valueDecimal,
23
+ onValueSelected: (value) => onDecimalChange(String(value)),
24
+ };
25
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"multiple-choice-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/multiple-choice-question.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EACV,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,iBAAiB,CAAC;IACxB,cAAc,EAAE,2BAA2B,EAAE,CAAC;IAC9C,sBAAsB,EAAE,CACtB,WAAW,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EACjE,YAAY,CAAC,EAAE,MAAM,KAClB,IAAI,CAAC;IACV,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAChE;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,yFAMpC,2BAA2B,4CA0F7B,CAAC"}
1
+ {"version":3,"file":"multiple-choice-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/multiple-choice-question.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EACV,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,iBAAiB,CAAC;IACxB,cAAc,EAAE,2BAA2B,EAAE,CAAC;IAC9C,sBAAsB,EAAE,CACtB,WAAW,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EACjE,YAAY,CAAC,EAAE,MAAM,KAClB,IAAI,CAAC;IACV,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAChE;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,yFAMpC,2BAA2B,4CAwF7B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { WELSHARE_EXTENSIONS } from "../../lib/constants.js";
2
3
  /**
3
4
  * Renders a multi-select choice question with checkboxes
4
5
  * Supports exclusive options and maxAnswers limits
@@ -7,8 +8,7 @@ export const MultipleChoiceQuestion = ({ item, currentAnswers, onMultipleChoiceT
7
8
  const maxAnswers = item.maxAnswers || Number.MAX_SAFE_INTEGER;
8
9
  const atMaxAnswers = currentAnswers.length >= maxAnswers;
9
10
  // Check if there's an exclusive option extension
10
- const exclusiveOptionExt = item.extension?.find((ext) => ext.url ===
11
- "http://codes.welshare.app/StructureDefinition/questionnaire-exclusive-option");
11
+ const exclusiveOptionExt = item.extension?.find((ext) => ext.url === WELSHARE_EXTENSIONS.EXCLUSIVE_OPTION);
12
12
  const exclusiveOptionCode = exclusiveOptionExt?.valueString;
13
13
  // Check if the exclusive option is currently selected
14
14
  const exclusiveSelected = exclusiveOptionCode &&
@@ -1 +1 @@
1
- {"version":3,"file":"questionnaire-context.d.ts","sourceRoot":"","sources":["../../../src/contexts/questionnaire-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAE5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,KAAK,IAAI,CAAC;IAC5E,qBAAqB,EAAE,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,2BAA2B,EAAE,KACnC,IAAI,CAAC;IACV,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,2BAA2B,GAAG,SAAS,CAAC;IACvE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,2BAA2B,EAAE,CAAC;IAC9D,WAAW,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,OAAO,CAAC;IACzD,oBAAoB,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,CAAC;IAC9E,8BAA8B,EAAE,CAC9B,SAAS,EAAE,iBAAiB,EAAE,KAC3B,iBAAiB,EAAE,CAAC;IACzB,oBAAoB,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,IAAI,CAAC;IAC/D,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAMD,eAAO,MAAM,gBAAgB,gCAQ5B,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,GAAI,mEAKnC,0BAA0B,4CAoa5B,CAAC"}
1
+ {"version":3,"file":"questionnaire-context.d.ts","sourceRoot":"","sources":["../../../src/contexts/questionnaire-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAE5B,MAAM,kBAAkB,CAAC;AAM1B,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,2BAA2B,KAAK,IAAI,CAAC;IAC5E,qBAAqB,EAAE,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,2BAA2B,EAAE,KACnC,IAAI,CAAC;IACV,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,2BAA2B,GAAG,SAAS,CAAC;IACvE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,2BAA2B,EAAE,CAAC;IAC9D,WAAW,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,OAAO,CAAC;IACzD,oBAAoB,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,CAAC;IAC9E,8BAA8B,EAAE,CAC9B,SAAS,EAAE,iBAAiB,EAAE,KAC3B,iBAAiB,EAAE,CAAC;IACzB,oBAAoB,EAAE,CAAC,SAAS,EAAE,iBAAiB,EAAE,KAAK,IAAI,CAAC;IAC/D,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAMD,eAAO,MAAM,gBAAgB,gCAQ5B,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,GAAI,mEAKnC,0BAA0B,4CAya5B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useEffect, useState, } from "react";
3
- import { getAllQuestionsFromPage } from "../lib/questionnaire-utils.js";
3
+ import { getAllQuestionsFromPage, isQuestionEnabled, } from "../lib/questionnaire-utils.js";
4
4
  const QuestionnaireContext = createContext(undefined);
5
5
  export const useQuestionnaire = () => {
6
6
  const context = useContext(QuestionnaireContext);
@@ -258,7 +258,8 @@ export const QuestionnaireProvider = ({ children, questionnaire, questionnaireId
258
258
  type: "group",
259
259
  item: pageItems,
260
260
  });
261
- return allQuestionsOnPage.filter((item) => item.required === true);
261
+ // Filter for required questions that are also enabled (meet their enableWhen conditions)
262
+ return allQuestionsOnPage.filter((item) => item.required === true && isQuestionEnabled(item, getAnswer));
262
263
  };
263
264
  const isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
264
265
  const hasMeaningfulAnswer = (answer) => {