@aehrc/smart-forms-renderer 0.25.2 → 0.26.1

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 (87) hide show
  1. package/lib/components/FormComponents/BooleanItem/BooleanItem.js +6 -1
  2. package/lib/components/FormComponents/BooleanItem/BooleanItem.js.map +1 -1
  3. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +5 -2
  4. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
  5. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +5 -2
  6. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
  7. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +5 -2
  8. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
  9. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js +5 -2
  10. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js.map +1 -1
  11. package/lib/components/FormComponents/DecimalItem/DecimalItem.js +9 -3
  12. package/lib/components/FormComponents/DecimalItem/DecimalItem.js.map +1 -1
  13. package/lib/components/FormComponents/IntegerItem/IntegerItem.js +7 -3
  14. package/lib/components/FormComponents/IntegerItem/IntegerItem.js.map +1 -1
  15. package/lib/components/FormComponents/StringItem/StringItem.js +7 -3
  16. package/lib/components/FormComponents/StringItem/StringItem.js.map +1 -1
  17. package/lib/components/FormComponents/TextItem/TextItem.js +7 -3
  18. package/lib/components/FormComponents/TextItem/TextItem.js.map +1 -1
  19. package/lib/components/Renderer/BaseRenderer.js +3 -3
  20. package/lib/components/Renderer/BaseRenderer.js.map +1 -1
  21. package/lib/hooks/useBooleanCalculatedExpression.d.ts +5 -4
  22. package/lib/hooks/useBooleanCalculatedExpression.js +12 -7
  23. package/lib/hooks/useBooleanCalculatedExpression.js.map +1 -1
  24. package/lib/hooks/useCodingCalculatedExpression.d.ts +4 -3
  25. package/lib/hooks/useCodingCalculatedExpression.js +14 -7
  26. package/lib/hooks/useCodingCalculatedExpression.js.map +1 -1
  27. package/lib/hooks/useDecimalCalculatedExpression.d.ts +3 -3
  28. package/lib/hooks/useDecimalCalculatedExpression.js +11 -7
  29. package/lib/hooks/useDecimalCalculatedExpression.js.map +1 -1
  30. package/lib/hooks/useDisplayCalculatedExpression.js +8 -1
  31. package/lib/hooks/useDisplayCalculatedExpression.js.map +1 -1
  32. package/lib/hooks/useIntegerCalculatedExpression.d.ts +3 -3
  33. package/lib/hooks/useIntegerCalculatedExpression.js +11 -7
  34. package/lib/hooks/useIntegerCalculatedExpression.js.map +1 -1
  35. package/lib/hooks/useStringCalculatedExpression.d.ts +3 -3
  36. package/lib/hooks/useStringCalculatedExpression.js +12 -7
  37. package/lib/hooks/useStringCalculatedExpression.js.map +1 -1
  38. package/lib/interfaces/calculatedExpression.interface.d.ts +1 -1
  39. package/lib/stores/questionnaireResponseStore.d.ts +3 -1
  40. package/lib/stores/questionnaireResponseStore.js +9 -1
  41. package/lib/stores/questionnaireResponseStore.js.map +1 -1
  42. package/lib/stores/questionnaireStore.d.ts +6 -2
  43. package/lib/stores/questionnaireStore.js +13 -2
  44. package/lib/stores/questionnaireStore.js.map +1 -1
  45. package/lib/utils/calculatedExpression.d.ts +2 -1
  46. package/lib/utils/calculatedExpression.js +14 -8
  47. package/lib/utils/calculatedExpression.js.map +1 -1
  48. package/lib/utils/enableWhenExpression.d.ts +3 -2
  49. package/lib/utils/enableWhenExpression.js +15 -5
  50. package/lib/utils/enableWhenExpression.js.map +1 -1
  51. package/lib/utils/fhirpath.d.ts +5 -4
  52. package/lib/utils/fhirpath.js +45 -33
  53. package/lib/utils/fhirpath.js.map +1 -1
  54. package/lib/utils/index.d.ts +1 -0
  55. package/lib/utils/index.js +1 -0
  56. package/lib/utils/index.js.map +1 -1
  57. package/lib/utils/initialise.js +5 -2
  58. package/lib/utils/initialise.js.map +1 -1
  59. package/lib/utils/parseInputs.js +1 -1
  60. package/lib/utils/parseInputs.js.map +1 -1
  61. package/package.json +2 -1
  62. package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +9 -1
  63. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +5 -2
  64. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +5 -2
  65. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +5 -2
  66. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx +5 -2
  67. package/src/components/FormComponents/DecimalItem/DecimalItem.tsx +14 -3
  68. package/src/components/FormComponents/IntegerItem/IntegerItem.tsx +10 -3
  69. package/src/components/FormComponents/StringItem/StringItem.tsx +10 -3
  70. package/src/components/FormComponents/TextItem/TextItem.tsx +10 -3
  71. package/src/components/Renderer/BaseRenderer.tsx +3 -3
  72. package/src/hooks/useBooleanCalculatedExpression.ts +19 -12
  73. package/src/hooks/useCodingCalculatedExpression.ts +18 -8
  74. package/src/hooks/useDecimalCalculatedExpression.ts +26 -17
  75. package/src/hooks/useDisplayCalculatedExpression.ts +12 -1
  76. package/src/hooks/useIntegerCalculatedExpression.ts +18 -13
  77. package/src/hooks/useStringCalculatedExpression.ts +16 -13
  78. package/src/interfaces/calculatedExpression.interface.ts +1 -1
  79. package/src/stores/questionnaireResponseStore.ts +16 -2
  80. package/src/stores/questionnaireStore.ts +22 -2
  81. package/src/utils/calculatedExpression.ts +24 -10
  82. package/src/utils/enableWhenExpression.ts +22 -9
  83. package/src/utils/fhirpath.ts +62 -41
  84. package/src/utils/index.ts +1 -0
  85. package/src/utils/initialise.ts +5 -2
  86. package/src/utils/parseInputs.ts +1 -1
  87. package/src/utils/questionnaireResponseStoreUtils/updatableResponseItems.ts +62 -0
@@ -16,8 +16,7 @@
16
16
  */
17
17
 
18
18
  import { useEffect, useState } from 'react';
19
- import { createEmptyQrItem } from '../utils/qrItem';
20
- import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
19
+ import type { QuestionnaireItem } from 'fhir/r4';
21
20
  import { useQuestionnaireStore } from '../stores';
22
21
 
23
22
  interface UseDecimalCalculatedExpression {
@@ -28,14 +27,20 @@ interface useDecimalCalculatedExpressionProps {
28
27
  qItem: QuestionnaireItem;
29
28
  inputValue: string;
30
29
  precision: number | null;
31
- setInputValue: (value: string) => void;
32
- onQrItemChange: (qrItem: QuestionnaireResponseItem) => void;
30
+ onChangeByCalcExpressionDecimal: (calcExpressionValue: number) => void;
31
+ onChangeByCalcExpressionNull: () => void;
33
32
  }
34
33
 
35
34
  function useDecimalCalculatedExpression(
36
35
  props: useDecimalCalculatedExpressionProps
37
36
  ): UseDecimalCalculatedExpression {
38
- const { qItem, inputValue, precision, setInputValue, onQrItemChange } = props;
37
+ const {
38
+ qItem,
39
+ inputValue,
40
+ precision,
41
+ onChangeByCalcExpressionDecimal,
42
+ onChangeByCalcExpressionNull
43
+ } = props;
39
44
 
40
45
  const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
41
46
 
@@ -52,10 +57,14 @@ function useDecimalCalculatedExpression(
52
57
  }
53
58
 
54
59
  // only update if calculated value is different from current value
55
- if (calcExpression.value !== inputValue && typeof calcExpression.value === 'number') {
56
- const calcExpressionValue = precision
57
- ? parseFloat(calcExpression.value.toFixed(precision))
58
- : calcExpression.value;
60
+ if (
61
+ calcExpression.value !== inputValue &&
62
+ (typeof calcExpression.value === 'number' || calcExpression.value === null)
63
+ ) {
64
+ const calcExpressionValue =
65
+ typeof calcExpression.value === 'number' && typeof precision === 'number'
66
+ ? parseFloat(calcExpression.value.toFixed(precision))
67
+ : calcExpression.value;
59
68
 
60
69
  // only update if calculated value is different from current value
61
70
  if (calcExpressionValue !== parseFloat(inputValue)) {
@@ -65,14 +74,14 @@ function useDecimalCalculatedExpression(
65
74
  setCalcExpUpdated(false);
66
75
  }, 500);
67
76
 
68
- // update questionnaireResponse
69
- setInputValue(
70
- precision ? calcExpressionValue.toFixed(precision) : calcExpressionValue.toString()
71
- );
72
- onQrItemChange({
73
- ...createEmptyQrItem(qItem),
74
- answer: [{ valueDecimal: calcExpressionValue }]
75
- });
77
+ // calculatedExpression value is null
78
+ if (calcExpressionValue === null) {
79
+ onChangeByCalcExpressionNull();
80
+ return;
81
+ }
82
+
83
+ // calculatedExpression value is a number
84
+ onChangeByCalcExpressionDecimal(calcExpressionValue);
76
85
  }
77
86
  }
78
87
  },
@@ -24,11 +24,22 @@ function useDisplayCalculatedExpression(qItem: QuestionnaireItem): string | null
24
24
  const calcExpression = calculatedExpressions[qItem.linkId]?.find(
25
25
  (exp) => exp.from === 'item._text'
26
26
  );
27
+
27
28
  if (!calcExpression) {
28
29
  return null;
29
30
  }
30
31
 
31
- if (typeof calcExpression.value === 'string' || typeof calcExpression.value === 'number') {
32
+ if (
33
+ typeof calcExpression.value === 'string' ||
34
+ typeof calcExpression.value === 'number' ||
35
+ calcExpression.value === null
36
+ ) {
37
+ // calculatedExpression value is null
38
+ if (calcExpression.value === null) {
39
+ return '';
40
+ }
41
+
42
+ // calculatedExpression value is string or number
32
43
  return typeof calcExpression.value === 'string'
33
44
  ? calcExpression.value
34
45
  : calcExpression.value.toString();
@@ -16,9 +16,8 @@
16
16
  */
17
17
 
18
18
  import { useEffect, useState } from 'react';
19
- import { createEmptyQrItem } from '../utils/qrItem';
20
- import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
21
- import { useQuestionnaireStore } from '../stores/questionnaireStore';
19
+ import type { QuestionnaireItem } from 'fhir/r4';
20
+ import { useQuestionnaireStore } from '../stores';
22
21
 
23
22
  interface UseIntegerCalculatedExpression {
24
23
  calcExpUpdated: boolean;
@@ -27,14 +26,15 @@ interface UseIntegerCalculatedExpression {
27
26
  interface useIntegerCalculatedExpressionProps {
28
27
  qItem: QuestionnaireItem;
29
28
  inputValue: string;
30
- setInputValue: (value: string) => void;
31
- onQrItemChange: (qrItem: QuestionnaireResponseItem) => void;
29
+ onChangeByCalcExpressionInteger: (calcExpressionValue: number) => void;
30
+ onChangeByCalcExpressionNull: () => void;
32
31
  }
33
32
 
34
33
  function useIntegerCalculatedExpression(
35
34
  props: useIntegerCalculatedExpressionProps
36
35
  ): UseIntegerCalculatedExpression {
37
- const { qItem, inputValue, setInputValue, onQrItemChange } = props;
36
+ const { qItem, inputValue, onChangeByCalcExpressionInteger, onChangeByCalcExpressionNull } =
37
+ props;
38
38
 
39
39
  const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
40
40
 
@@ -51,7 +51,10 @@ function useIntegerCalculatedExpression(
51
51
  }
52
52
 
53
53
  // only update if calculated value is different from current value
54
- if (calcExpression.value !== inputValue && typeof calcExpression.value === 'number') {
54
+ if (
55
+ calcExpression.value !== inputValue &&
56
+ (typeof calcExpression.value === 'number' || calcExpression.value === null)
57
+ ) {
55
58
  const calcExpressionValue = calcExpression.value;
56
59
 
57
60
  if (calcExpressionValue !== parseInt(inputValue)) {
@@ -61,12 +64,14 @@ function useIntegerCalculatedExpression(
61
64
  setCalcExpUpdated(false);
62
65
  }, 500);
63
66
 
64
- // update questionnaireResponse
65
- setInputValue(calcExpressionValue.toString());
66
- onQrItemChange({
67
- ...createEmptyQrItem(qItem),
68
- answer: [{ valueInteger: calcExpressionValue }]
69
- });
67
+ // calculatedExpression value is null
68
+ if (calcExpressionValue === null) {
69
+ onChangeByCalcExpressionNull();
70
+ return;
71
+ }
72
+
73
+ // calculatedExpression value is a number
74
+ onChangeByCalcExpressionInteger(calcExpressionValue);
70
75
  }
71
76
  }
72
77
  },
@@ -16,8 +16,7 @@
16
16
  */
17
17
 
18
18
  import { useEffect, useState } from 'react';
19
- import { createEmptyQrItem } from '../utils/qrItem';
20
- import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
19
+ import type { QuestionnaireItem } from 'fhir/r4';
21
20
  import { useQuestionnaireStore } from '../stores';
22
21
 
23
22
  interface UseStringCalculatedExpression {
@@ -27,14 +26,14 @@ interface UseStringCalculatedExpression {
27
26
  interface useStringCalculatedExpressionProps {
28
27
  qItem: QuestionnaireItem;
29
28
  inputValue: string;
30
- setInputValue: (value: string) => void;
31
- onQrItemChange: (qrItem: QuestionnaireResponseItem) => void;
29
+ onChangeByCalcExpressionString: (newValueString: string) => void;
30
+ onChangeByCalcExpressionNull: () => void;
32
31
  }
33
32
 
34
33
  function useStringCalculatedExpression(
35
34
  props: useStringCalculatedExpressionProps
36
35
  ): UseStringCalculatedExpression {
37
- const { qItem, inputValue, setInputValue, onQrItemChange } = props;
36
+ const { qItem, inputValue, onChangeByCalcExpressionString, onChangeByCalcExpressionNull } = props;
38
37
 
39
38
  const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
40
39
 
@@ -53,7 +52,9 @@ function useStringCalculatedExpression(
53
52
  // only update if calculated value is different from current value
54
53
  if (
55
54
  calcExpression.value !== inputValue &&
56
- (typeof calcExpression.value === 'string' || typeof calcExpression.value === 'number')
55
+ (typeof calcExpression.value === 'string' ||
56
+ typeof calcExpression.value === 'number' ||
57
+ calcExpression.value === null)
57
58
  ) {
58
59
  // update ui to show calculated value changes
59
60
  setCalcExpUpdated(true);
@@ -61,17 +62,19 @@ function useStringCalculatedExpression(
61
62
  setCalcExpUpdated(false);
62
63
  }, 500);
63
64
 
64
- const updatedInputValue =
65
+ // calculatedExpression value is null
66
+ if (calcExpression.value === null) {
67
+ onChangeByCalcExpressionNull();
68
+ return;
69
+ }
70
+
71
+ // calculatedExpression value is a string or number
72
+ const newInputValue =
65
73
  typeof calcExpression.value === 'string'
66
74
  ? calcExpression.value
67
75
  : calcExpression.value.toString();
68
76
 
69
- // update questionnaireResponse
70
- setInputValue(updatedInputValue);
71
- onQrItemChange({
72
- ...createEmptyQrItem(qItem),
73
- answer: [{ valueString: updatedInputValue }]
74
- });
77
+ onChangeByCalcExpressionString(newInputValue);
75
78
  }
76
79
  },
77
80
  // Only trigger this effect if calculatedExpression of item changes
@@ -1,5 +1,5 @@
1
1
  export interface CalculatedExpression {
2
2
  expression: string;
3
3
  from: 'item' | 'item._text';
4
- value?: number | string | boolean | object;
4
+ value?: number | string | boolean | object | null;
5
5
  }
@@ -16,7 +16,12 @@
16
16
  */
17
17
 
18
18
  import { createStore } from 'zustand/vanilla';
19
- import type { OperationOutcome, Questionnaire, QuestionnaireResponse } from 'fhir/r4';
19
+ import type {
20
+ OperationOutcome,
21
+ Questionnaire,
22
+ QuestionnaireResponse,
23
+ QuestionnaireResponseItem
24
+ } from 'fhir/r4';
20
25
  import { emptyResponse } from '../utils/emptyResource';
21
26
  import cloneDeep from 'lodash.clonedeep';
22
27
  import type { Diff } from 'deep-diff';
@@ -24,10 +29,12 @@ import { diff } from 'deep-diff';
24
29
  import { createSelectors } from './selector';
25
30
  import { validateQuestionnaire } from '../utils/validateQuestionnaire';
26
31
  import { questionnaireStore } from './questionnaireStore';
32
+ import { createQuestionnaireResponseItemMap } from '../utils/questionnaireResponseStoreUtils/updatableResponseItems';
27
33
 
28
34
  interface QuestionnaireResponseStoreType {
29
35
  sourceResponse: QuestionnaireResponse;
30
36
  updatableResponse: QuestionnaireResponse;
37
+ updatableResponseItems: Record<string, QuestionnaireResponseItem[]>;
31
38
  formChangesHistory: (Diff<QuestionnaireResponse, QuestionnaireResponse>[] | null)[];
32
39
  invalidItems: Record<string, OperationOutcome>;
33
40
  responseIsValid: boolean;
@@ -47,6 +54,7 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
47
54
  (set, get) => ({
48
55
  sourceResponse: cloneDeep(emptyResponse),
49
56
  updatableResponse: cloneDeep(emptyResponse),
57
+ updatableResponseItems: {},
50
58
  formChangesHistory: [],
51
59
  invalidItems: {},
52
60
  responseIsValid: true,
@@ -74,13 +82,15 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
74
82
  buildSourceResponse: (questionnaireResponse: QuestionnaireResponse) => {
75
83
  set(() => ({
76
84
  sourceResponse: questionnaireResponse,
77
- updatableResponse: questionnaireResponse
85
+ updatableResponse: questionnaireResponse,
86
+ updatableResponseItems: createQuestionnaireResponseItemMap(questionnaireResponse)
78
87
  }));
79
88
  },
80
89
  setUpdatableResponseAsPopulated: (populatedResponse: QuestionnaireResponse) => {
81
90
  const formChanges = diff(get().updatableResponse, populatedResponse) ?? null;
82
91
  set(() => ({
83
92
  updatableResponse: populatedResponse,
93
+ updatableResponseItems: createQuestionnaireResponseItemMap(populatedResponse),
84
94
  formChangesHistory: [...get().formChangesHistory, formChanges]
85
95
  }));
86
96
  },
@@ -88,6 +98,7 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
88
98
  const formChanges = diff(get().updatableResponse, updatedResponse) ?? null;
89
99
  set(() => ({
90
100
  updatableResponse: updatedResponse,
101
+ updatableResponseItems: createQuestionnaireResponseItemMap(updatedResponse),
91
102
  formChangesHistory: [...get().formChangesHistory, formChanges]
92
103
  }));
93
104
  },
@@ -95,17 +106,20 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
95
106
  set(() => ({
96
107
  sourceResponse: savedResponse,
97
108
  updatableResponse: savedResponse,
109
+ updatableResponseItems: createQuestionnaireResponseItemMap(savedResponse),
98
110
  formChangesHistory: []
99
111
  })),
100
112
  setUpdatableResponseAsEmpty: (clearedResponse: QuestionnaireResponse) =>
101
113
  set(() => ({
102
114
  updatableResponse: clearedResponse,
115
+ updatableResponseItems: createQuestionnaireResponseItemMap(clearedResponse),
103
116
  formChangesHistory: []
104
117
  })),
105
118
  destroySourceResponse: () =>
106
119
  set(() => ({
107
120
  sourceResponse: cloneDeep(emptyResponse),
108
121
  updatableResponse: cloneDeep(emptyResponse),
122
+ updatableResponseItems: createQuestionnaireResponseItemMap(cloneDeep(emptyResponse)),
109
123
  formChangesHistory: []
110
124
  }))
111
125
  })
@@ -45,6 +45,7 @@ import { terminologyServerStore } from './terminologyServerStore';
45
45
  import { createSelectors } from './selector';
46
46
  import { mutateRepeatEnableWhenExpressionInstances } from '../utils/enableWhenExpression';
47
47
  import { questionnaireResponseStore } from './questionnaireResponseStore';
48
+ import { createQuestionnaireResponseItemMap } from '../utils/questionnaireResponseStoreUtils/updatableResponseItems';
48
49
 
49
50
  interface QuestionnaireStoreType {
50
51
  sourceQuestionnaire: Questionnaire;
@@ -63,6 +64,7 @@ interface QuestionnaireStoreType {
63
64
  processedValueSetUrls: Record<string, string>;
64
65
  cachedValueSetCodings: Record<string, Coding[]>;
65
66
  fhirPathContext: Record<string, any>;
67
+ populatedContext: Record<string, any>;
66
68
  focusedLinkId: string;
67
69
  readOnly: boolean;
68
70
  buildSourceQuestionnaire: (
@@ -90,9 +92,11 @@ interface QuestionnaireStoreType {
90
92
  addCodingToCache: (valueSetUrl: string, codings: Coding[]) => void;
91
93
  updatePopulatedProperties: (
92
94
  populatedResponse: QuestionnaireResponse,
95
+ populatedContext?: Record<string, any>,
93
96
  persistTabIndex?: boolean
94
97
  ) => QuestionnaireResponse;
95
98
  onFocusLinkId: (linkId: string) => void;
99
+ setPopulatedContext: (newPopulatedContext: Record<string, any>) => void;
96
100
  }
97
101
 
98
102
  export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, get) => ({
@@ -112,6 +116,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
112
116
  processedValueSetUrls: {},
113
117
  cachedValueSetCodings: {},
114
118
  fhirPathContext: {},
119
+ populatedContext: {},
115
120
  focusedLinkId: '',
116
121
  readOnly: false,
117
122
  buildSourceQuestionnaire: async (
@@ -232,6 +237,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
232
237
 
233
238
  const { updatedEnableWhenExpressions, isUpdated } = mutateRepeatEnableWhenExpressionInstances({
234
239
  questionnaireResponse: questionnaireResponseStore.getState().updatableResponse,
240
+ questionnaireResponseItemMap: questionnaireResponseStore.getState().updatableResponseItems,
235
241
  variablesFhirPath: get().variables.fhirPathVariables,
236
242
  existingFhirPathContext: get().fhirPathContext,
237
243
  enableWhenExpressions: enableWhenExpressions,
@@ -250,6 +256,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
250
256
  toggleEnableWhenActivation: (isActivated: boolean) =>
251
257
  set(() => ({ enableWhenIsActivated: isActivated })),
252
258
  updateExpressions: (updatedResponse: QuestionnaireResponse) => {
259
+ const updatedResponseItemMap = createQuestionnaireResponseItemMap(updatedResponse);
253
260
  const {
254
261
  isUpdated,
255
262
  updatedEnableWhenExpressions,
@@ -257,6 +264,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
257
264
  updatedFhirPathContext
258
265
  } = evaluateUpdatedExpressions({
259
266
  updatedResponse,
267
+ updatedResponseItemMap,
260
268
  enableWhenExpressions: get().enableWhenExpressions,
261
269
  calculatedExpressions: get().calculatedExpressions,
262
270
  variablesFhirPath: get().variables.fhirPathVariables,
@@ -283,9 +291,16 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
283
291
  [valueSetUrl]: codings
284
292
  }
285
293
  })),
286
- updatePopulatedProperties: (populatedResponse: QuestionnaireResponse, persistTabIndex) => {
294
+ updatePopulatedProperties: (
295
+ populatedResponse: QuestionnaireResponse,
296
+ populatedContext?: Record<string, any>,
297
+ persistTabIndex?: boolean
298
+ ) => {
299
+ const initialResponseItemMap = createQuestionnaireResponseItemMap(populatedResponse);
300
+
287
301
  const evaluateInitialCalculatedExpressionsResult = evaluateInitialCalculatedExpressions({
288
302
  initialResponse: populatedResponse,
303
+ initialResponseItemMap: initialResponseItemMap,
289
304
  calculatedExpressions: get().calculatedExpressions,
290
305
  variablesFhirPath: get().variables.fhirPathVariables,
291
306
  existingFhirPathContext: get().fhirPathContext
@@ -321,7 +336,8 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
321
336
  enableWhenExpressions: initialEnableWhenExpressions,
322
337
  calculatedExpressions: initialCalculatedExpressions,
323
338
  currentTabIndex: persistTabIndex ? get().currentTabIndex : firstVisibleTab,
324
- fhirPathContext: updatedFhirPathContext
339
+ fhirPathContext: updatedFhirPathContext,
340
+ populatedContext: populatedContext ?? get().populatedContext
325
341
  }));
326
342
 
327
343
  return updatedResponse;
@@ -329,6 +345,10 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
329
345
  onFocusLinkId: (linkId: string) =>
330
346
  set(() => ({
331
347
  focusedLinkId: linkId
348
+ })),
349
+ setPopulatedContext: (newPopulatedContext: Record<string, any>) =>
350
+ set(() => ({
351
+ populatedContext: newPopulatedContext
332
352
  }))
333
353
  }));
334
354
 
@@ -37,6 +37,7 @@ import dayjs from 'dayjs';
37
37
 
38
38
  interface EvaluateInitialCalculatedExpressionsParams {
39
39
  initialResponse: QuestionnaireResponse;
40
+ initialResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
40
41
  calculatedExpressions: Record<string, CalculatedExpression[]>;
41
42
  variablesFhirPath: Record<string, Expression[]>;
42
43
  existingFhirPathContext: Record<string, any>;
@@ -48,8 +49,13 @@ export function evaluateInitialCalculatedExpressions(
48
49
  initialCalculatedExpressions: Record<string, CalculatedExpression[]>;
49
50
  updatedFhirPathContext: Record<string, any>;
50
51
  } {
51
- const { initialResponse, calculatedExpressions, variablesFhirPath, existingFhirPathContext } =
52
- params;
52
+ const {
53
+ initialResponse,
54
+ initialResponseItemMap,
55
+ calculatedExpressions,
56
+ variablesFhirPath,
57
+ existingFhirPathContext
58
+ } = params;
53
59
 
54
60
  // Return early if initialResponse is empty or there are no calculated expressions to evaluate
55
61
  if (
@@ -65,10 +71,11 @@ export function evaluateInitialCalculatedExpressions(
65
71
  const initialCalculatedExpressions: Record<string, CalculatedExpression[]> = {
66
72
  ...calculatedExpressions
67
73
  };
74
+
68
75
  const updatedFhirPathContext = createFhirPathContext(
69
76
  initialResponse,
70
- variablesFhirPath,
71
- existingFhirPathContext
77
+ initialResponseItemMap,
78
+ variablesFhirPath
72
79
  );
73
80
 
74
81
  for (const linkId in initialCalculatedExpressions) {
@@ -83,7 +90,8 @@ export function evaluateInitialCalculatedExpressions(
83
90
  fhirpath_r4_model
84
91
  );
85
92
 
86
- if (!_isEqual(calcExpression.value, result[0])) {
93
+ // Only update calculatedExpressions if length of result array > 0
94
+ if (result.length > 0 && !_isEqual(calcExpression.value, result[0])) {
87
95
  calcExpression.value = result[0];
88
96
  }
89
97
  } catch (e) {
@@ -124,11 +132,17 @@ export function evaluateCalculatedExpressions(
124
132
  fhirpath_r4_model
125
133
  );
126
134
 
127
- if (result.length > 0) {
128
- if (!_isEqual(calcExpression.value, result[0])) {
129
- isUpdated = true;
130
- calcExpression.value = result[0];
131
- }
135
+ // Update calculatedExpressions if length of result array > 0
136
+ // Only update when current calcExpression value is different from the result, otherwise it will result in an infinite loop as per issue #733
137
+ if (result.length > 0 && !_isEqual(calcExpression.value, result[0])) {
138
+ isUpdated = true;
139
+ calcExpression.value = result[0];
140
+ }
141
+
142
+ // Update calculatedExpression value to null if no result is returned
143
+ if (result.length === 0 && calcExpression.value !== null) {
144
+ isUpdated = true;
145
+ calcExpression.value = null;
132
146
  }
133
147
  } catch (e) {
134
148
  console.warn(e.message, `LinkId: ${linkId}\nExpression: ${calcExpression.expression}`);
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { Expression, QuestionnaireResponse } from 'fhir/r4';
18
+ import type { Expression, QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4';
19
19
  import { createFhirPathContext } from './fhirpath';
20
20
  import fhirpath from 'fhirpath';
21
21
  import fhirpath_r4_model from 'fhirpath/fhir-context/r4';
@@ -27,9 +27,9 @@ import type {
27
27
 
28
28
  interface EvaluateInitialEnableWhenExpressionsParams {
29
29
  initialResponse: QuestionnaireResponse;
30
+ initialResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
30
31
  enableWhenExpressions: EnableWhenExpressions;
31
32
  variablesFhirPath: Record<string, Expression[]>;
32
- existingFhirPathContext: Record<string, any>;
33
33
  }
34
34
 
35
35
  export function evaluateInitialEnableWhenExpressions(
@@ -38,7 +38,7 @@ export function evaluateInitialEnableWhenExpressions(
38
38
  initialEnableWhenExpressions: EnableWhenExpressions;
39
39
  updatedFhirPathContext: Record<string, any>;
40
40
  } {
41
- const { initialResponse, enableWhenExpressions, variablesFhirPath, existingFhirPathContext } =
41
+ const { initialResponse, initialResponseItemMap, enableWhenExpressions, variablesFhirPath } =
42
42
  params;
43
43
 
44
44
  const initialEnableWhenExpressions: EnableWhenExpressions = {
@@ -46,8 +46,8 @@ export function evaluateInitialEnableWhenExpressions(
46
46
  };
47
47
  const updatedFhirPathContext = createFhirPathContext(
48
48
  initialResponse,
49
- variablesFhirPath,
50
- existingFhirPathContext
49
+ initialResponseItemMap,
50
+ variablesFhirPath
51
51
  );
52
52
 
53
53
  const initialEnableWhenSingleExpressions = evaluateEnableWhenSingleExpressions(
@@ -85,12 +85,18 @@ function evaluateEnableWhenSingleExpressions(
85
85
  const result = fhirpath.evaluate('', expression, updatedFhirPathContext, fhirpath_r4_model);
86
86
 
87
87
  // Update enableWhenExpressions if length of result array > 0
88
- // Only update when current isEnabled value is different from the result, otherwise it will result in am infinite loop as per #733
88
+ // Only update when current isEnabled value is different from the result, otherwise it will result in an infinite loop as per issue #733
89
89
  if (result.length > 0 && initialValue !== result[0] && typeof result[0] === 'boolean') {
90
90
  enableWhenSingleExpressions[linkId].isEnabled = result[0];
91
91
  isUpdated = true;
92
92
  }
93
93
 
94
+ // Update isEnabled value to false if no result is returned
95
+ if (result.length === 0 && initialValue !== false) {
96
+ enableWhenSingleExpressions[linkId].isEnabled = false;
97
+ isUpdated = true;
98
+ }
99
+
94
100
  // handle intersect edge case - evaluate() returns empty array if result is false
95
101
  if (
96
102
  enableWhenSingleExpressions[linkId].expression.includes('intersect') &&
@@ -198,6 +204,12 @@ export function evaluateEnableWhenRepeatExpressionInstance(
198
204
  isUpdated = true;
199
205
  }
200
206
 
207
+ // Update isEnabled value to false if no result is returned
208
+ if (result.length === 0 && initialValue !== false) {
209
+ isEnabled = false;
210
+ isUpdated = true;
211
+ }
212
+
201
213
  // handle intersect edge case - evaluate() returns empty array if result is false
202
214
  if (expression.includes('intersect') && result.length === 0 && initialValue !== result[0]) {
203
215
  isEnabled = false;
@@ -245,6 +257,7 @@ export function evaluateEnableWhenExpressions(
245
257
 
246
258
  interface MutateRepeatEnableWhenExpressionInstancesParams {
247
259
  questionnaireResponse: QuestionnaireResponse;
260
+ questionnaireResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
248
261
  variablesFhirPath: Record<string, Expression[]>;
249
262
  existingFhirPathContext: Record<string, any>;
250
263
  enableWhenExpressions: EnableWhenExpressions;
@@ -258,8 +271,8 @@ export function mutateRepeatEnableWhenExpressionInstances(
258
271
  ): { updatedEnableWhenExpressions: EnableWhenExpressions; isUpdated: boolean } {
259
272
  const {
260
273
  questionnaireResponse,
274
+ questionnaireResponseItemMap,
261
275
  variablesFhirPath,
262
- existingFhirPathContext,
263
276
  enableWhenExpressions,
264
277
  parentRepeatGroupLinkId,
265
278
  parentRepeatGroupIndex,
@@ -270,8 +283,8 @@ export function mutateRepeatEnableWhenExpressionInstances(
270
283
 
271
284
  const updatedFhirPathContext = createFhirPathContext(
272
285
  questionnaireResponse,
273
- variablesFhirPath,
274
- existingFhirPathContext
286
+ questionnaireResponseItemMap,
287
+ variablesFhirPath
275
288
  );
276
289
 
277
290
  let isUpdated = false;