Skip to content

Commit 38d1128

Browse files
committed
Support item initial
1 parent 8ad9cd7 commit 38d1128

File tree

9 files changed

+463
-71
lines changed

9 files changed

+463
-71
lines changed

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,24 @@ Each milestone lists the core features and extensions required to reach full com
4141
Focus: Establish baseline FHIR Questionnaire support and base rendering logic.
4242

4343
* [ ] **Core Questionnaire Resource**
44-
* [ ] Support `Questionnaire.item` elements (text, type, linkId, required, repeats)
45-
* [ ] Implement display of `prefix`, `text`, `item.type`
44+
* [x] Support `Questionnaire.item` elements (text, type, linkId, required, repeats)
45+
* [x] Implement display of `prefix`, `text`, `item.type`
4646
* [ ] Apply constraints: `minLength`, `maxLength`, `minValue`, `maxValue`, `maxOccurs`
47-
* [ ] Enable `readOnly`, `initial`, and `defaultValue` behavior
48-
* [ ] Support FHIR `enableWhen` conditions and nested groups
47+
* [x] Enable `readOnly`, `initial` behavior
48+
* [x] Support FHIR `enableWhen` conditions and nested groups
4949
* [ ] Render `item.control` and `item.control.displayCategory` extensions (basic)
50-
* [ ] **Questionnaire Navigation & Layout**
51-
* [ ] Implement group hierarchy rendering
50+
* [ ] **Questionnaire Navigation & Layout*_*_
51+
* [x] Implement group hierarchy rendering
5252
* [ ] Respect `hidden` and `displayCategory` controls
5353
* [ ] Add label localization and prefix rendering
5454
* [ ] **Input Controls**
55-
* [ ] Map FHIR types → UI components (`string`, `boolean`, `integer`, `choice`, `date`, `quantity`, etc.)
55+
* [x] Map FHIR types → UI components (`string`, `boolean`, `integer`, `choice`, `date`, `quantity`, etc.)
5656
* [ ] Render choice options (`answerOption`, `answerValueSet`)
5757
* [ ] Support open-choice types
5858
* [ ] **Basic Validation**
59-
* [ ] Validate required questions
60-
* [ ] Validate numeric and string bounds
61-
* [ ] Validate answer cardinality (`repeats`, `maxOccurs`)
59+
* [x] Validate required questions
60+
* [x] Validate numeric and string bounds
61+
* [x] Validate answer cardinality (`repeats`, `maxOccurs`)
6262

6363
## ⚙️ **November 2025 — Advanced Form Rendering & Behavior**
6464

@@ -69,8 +69,8 @@ Focus: Interactive logic, calculations, adaptive elements, and modular assembly.
6969
* [ ] Handle `appearance` extension rendering hints
7070
* [ ] Support `rendering-style`, `rendering-xhtml`, and `markdown` items
7171
* [ ] **Form Behavior & Calculation**
72-
* [ ] Implement calculated expressions via `calculatedExpression`
73-
* [ ] Dynamic enablement: `enableWhenExpression`
72+
* [x] Implement calculated expressions via `calculatedExpression`
73+
* [x] Dynamic enablement: `enableWhenExpression`
7474
* [ ] Validation expressions: `constraint`, `constraintExpression`
7575
* [ ] Item visibility & computed display expressions
7676
* [ ] Value propagation (`derivedFrom`, `answerExpression`)
@@ -81,16 +81,16 @@ Focus: Interactive logic, calculations, adaptive elements, and modular assembly.
8181
* [ ] Support inclusion of external subforms and library dependencies
8282
* [ ] **User Interaction Layer**
8383
* [ ] Autosave progress to `QuestionnaireResponse`
84-
* [ ] Support for resuming saved sessions
85-
* [ ] Dynamic updates to visible fields based on answers
84+
* [x] Support for resuming saved sessions
85+
* [x] Dynamic updates to visible fields based on answers
8686

8787
## 🧩 **December 2025 — Data Population & Extraction**
8888

8989
Focus: Form prefill, structured output mapping, and integration services.
9090

9191
* [ ] **Form Population**
9292
* [ ] Implement population via:
93-
* [ ] `initialExpression`
93+
* [x] `initialExpression`
9494
* [ ] `itemPopulationContext`
9595
* [ ] `candidateExpression`
9696
* [ ] Support FHIR `$populate` operation (StructureMap-based)
@@ -100,7 +100,7 @@ Focus: Form prefill, structured output mapping, and integration services.
100100
* [ ] Map responses → resources using `structureMap`
101101
* [ ] Handle `extract` extensions: `sdc-questionnaire-extract`, `itemExtractionContext`
102102
* [ ] **Export and Serialization**
103-
* [ ] Generate valid `QuestionnaireResponse`
103+
* [x] Generate valid `QuestionnaireResponse`
104104
* [ ] Support export in JSON and XML
105105
* [ ] Validate `QuestionnaireResponse` against source `Questionnaire`
106106
* [ ] **Polish & QA**

demo/app.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ export function App() {
124124

125125
const sampleOptions = useMemo(
126126
() =>
127-
demoSamples.map((sample) => ({
127+
demoSamples.map((sample, index) => ({
128128
id: sample.id,
129-
label: sample.label,
129+
label: `${index + 1}. ${sample.label}`,
130130
})),
131131
[],
132132
);

demo/samples/index.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import numericThresholds from "./numeric-thresholds.json" assert { type: "json"
66
import repeatingQuestion from "./repeating-question.json" assert { type: "json" };
77
import repeatingGroup from "./repeating-group.json" assert { type: "json" };
88
import nestedFollowUps from "./nested-follow-ups.json" assert { type: "json" };
9+
import staticInitials from "./static-initials.json" assert { type: "json" };
910
import expressionInitial from "./expression-initial.json" assert { type: "json" };
1011
import expressionCalculated from "./expression-calculated.json" assert { type: "json" };
1112
import expressionEnableWhen from "./expression-enable-when.json" assert { type: "json" };
@@ -20,52 +21,57 @@ type DemoSample = {
2021
export const demoSamples: readonly DemoSample[] = [
2122
{
2223
id: "text-controls",
23-
label: "1. Basic text inputs",
24+
label: "Basic text inputs",
2425
questionnaire: textControls as Questionnaire,
2526
},
2627
{
2728
id: "boolean-gating",
28-
label: "2. Boolean enableWhen",
29+
label: "Boolean enableWhen",
2930
questionnaire: booleanGating as Questionnaire,
3031
},
3132
{
3233
id: "numeric-thresholds",
33-
label: "3. Numeric comparisons",
34+
label: "Numeric comparisons",
3435
questionnaire: numericThresholds as Questionnaire,
3536
},
3637
{
3738
id: "repeating-question",
38-
label: "4. Repeating question answers",
39+
label: "Repeating question answers",
3940
questionnaire: repeatingQuestion as Questionnaire,
4041
},
4142
{
4243
id: "repeating-group",
43-
label: "5. Repeating group instances",
44+
label: "Repeating group instances",
4445
questionnaire: repeatingGroup as Questionnaire,
4546
},
4647
{
4748
id: "nested-follow-ups",
48-
label: "6. Nested follow-up questions",
49+
label: "Nested follow-up questions",
4950
questionnaire: nestedFollowUps as Questionnaire,
5051
},
52+
{
53+
id: "static-initials",
54+
label: "Static initial values",
55+
questionnaire: staticInitials as Questionnaire,
56+
},
5157
{
5258
id: "expression-initial",
53-
label: "7. Initial expression defaults",
59+
label: "Initial expression defaults",
5460
questionnaire: expressionInitial as Questionnaire,
5561
},
5662
{
5763
id: "expression-calculated",
58-
label: "8. Calculated expressions",
64+
label: "Calculated expressions",
5965
questionnaire: expressionCalculated as Questionnaire,
6066
},
6167
{
6268
id: "expression-enable-when",
63-
label: "9. Expression-based enablement",
69+
label: "Expression-based enablement",
6470
questionnaire: expressionEnableWhen as Questionnaire,
6571
},
6672
{
6773
id: "expression-dynamic-bounds",
68-
label: "10. Dynamic min/max expressions",
74+
label: "Dynamic min/max expressions",
6975
questionnaire: expressionDynamicBounds as Questionnaire,
7076
},
7177
] as const;

demo/samples/static-initials.json

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"resourceType": "Questionnaire",
3+
"id": "static-initials",
4+
"status": "active",
5+
"title": "Static Initial Values",
6+
"description": "Shows how questionnaire.item.initial seeds single and repeating answers, including read-only fields and minOccurs padding.",
7+
"item": [
8+
{
9+
"linkId": "intro",
10+
"type": "display",
11+
"text": "Static initial values come from the questionnaire item. Edit the nickname, add an extra care-team member, and notice the read-only review date stays locked."
12+
},
13+
{
14+
"linkId": "profile",
15+
"type": "group",
16+
"text": "Patient profile",
17+
"item": [
18+
{
19+
"linkId": "nickname",
20+
"type": "string",
21+
"text": "Preferred nickname",
22+
"initial": [
23+
{
24+
"valueString": "Buddy"
25+
}
26+
]
27+
},
28+
{
29+
"linkId": "review-date",
30+
"type": "date",
31+
"text": "Last reviewed",
32+
"readOnly": true,
33+
"initial": [
34+
{
35+
"valueDate": "2025-10-30"
36+
}
37+
]
38+
}
39+
]
40+
},
41+
{
42+
"linkId": "care-team",
43+
"type": "group",
44+
"text": "Care team contacts",
45+
"item": [
46+
{
47+
"linkId": "team-member",
48+
"type": "string",
49+
"text": "Team member name",
50+
"repeats": true,
51+
"extension": [
52+
{
53+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-minOccurs",
54+
"valueInteger": 3
55+
},
56+
{
57+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-maxOccurs",
58+
"valueInteger": 4
59+
}
60+
],
61+
"initial": [
62+
{
63+
"valueString": "Primary Physician — Dr. Sloan"
64+
},
65+
{
66+
"valueString": "Nurse Navigator — Casey Lee"
67+
}
68+
]
69+
}
70+
]
71+
},
72+
{
73+
"linkId": "vitals",
74+
"type": "group",
75+
"text": "Baseline vitals",
76+
"item": [
77+
{
78+
"linkId": "baseline-weight",
79+
"type": "quantity",
80+
"text": "Weight",
81+
"initial": [
82+
{
83+
"valueQuantity": {
84+
"value": 72.5,
85+
"unit": "kg",
86+
"system": "http://unitsofmeasure.org",
87+
"code": "kg"
88+
}
89+
}
90+
]
91+
}
92+
]
93+
}
94+
]
95+
}

lib/stores/__tests__/calculated-expression.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,30 @@ describe("calculatedExpression", () => {
204204
]);
205205
});
206206

207+
it("overwrites template initial values when calculated expressions run", () => {
208+
const questionnaire: Questionnaire = {
209+
resourceType: "Questionnaire",
210+
status: "active",
211+
item: [
212+
{
213+
linkId: "result",
214+
type: "decimal",
215+
initial: [{ valueDecimal: 5 }],
216+
extension: [makeCalculatedExpression(undefined, "10")],
217+
},
218+
],
219+
};
220+
221+
const form = new FormStore(questionnaire);
222+
const node = form.scope.lookupNode("result");
223+
224+
if (!node || !isQuestionNode(node)) {
225+
throw new Error("Expected result question");
226+
}
227+
228+
expect(node.answers[0]?.value).toBe(10);
229+
});
230+
207231
it("allows named calculated expressions to be reused", () => {
208232
const questionnaire: Questionnaire = {
209233
resourceType: "Questionnaire",

0 commit comments

Comments
 (0)