@welshare/questionnaire 0.2.0 → 0.2.2

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 (36) hide show
  1. package/README.md +305 -60
  2. package/dist/esm/components/bmi-form.d.ts.map +1 -1
  3. package/dist/esm/components/legal-consent-form.d.ts +123 -0
  4. package/dist/esm/components/legal-consent-form.d.ts.map +1 -0
  5. package/dist/esm/components/legal-consent-form.js +97 -0
  6. package/dist/esm/components/media-attachment.d.ts +21 -0
  7. package/dist/esm/components/media-attachment.d.ts.map +1 -0
  8. package/dist/esm/components/media-attachment.js +22 -0
  9. package/dist/esm/components/question-renderer.d.ts.map +1 -1
  10. package/dist/esm/components/question-renderer.js +9 -4
  11. package/dist/esm/components/questions/boolean-question.d.ts +4 -4
  12. package/dist/esm/components/questions/boolean-question.d.ts.map +1 -1
  13. package/dist/esm/components/questions/boolean-question.js +6 -6
  14. package/dist/esm/components/questions/choice-question.d.ts +4 -4
  15. package/dist/esm/components/questions/choice-question.d.ts.map +1 -1
  16. package/dist/esm/components/questions/choice-question.js +19 -11
  17. package/dist/esm/components/questions/multiple-choice-question.d.ts.map +1 -1
  18. package/dist/esm/components/questions/multiple-choice-question.js +19 -11
  19. package/dist/esm/contexts/questionnaire-context.d.ts.map +1 -1
  20. package/dist/esm/contexts/questionnaire-context.js +4 -22
  21. package/dist/esm/index.d.ts +4 -2
  22. package/dist/esm/index.d.ts.map +1 -1
  23. package/dist/esm/index.js +2 -1
  24. package/dist/esm/lib/bmi-helpers.d.ts.map +1 -1
  25. package/dist/esm/lib/constants.d.ts +12 -0
  26. package/dist/esm/lib/constants.d.ts.map +1 -1
  27. package/dist/esm/lib/constants.js +12 -0
  28. package/dist/esm/lib/questionnaire-utils.d.ts +15 -1
  29. package/dist/esm/lib/questionnaire-utils.d.ts.map +1 -1
  30. package/dist/esm/lib/questionnaire-utils.js +26 -0
  31. package/dist/esm/types/fhir.d.ts +25 -5
  32. package/dist/esm/types/fhir.d.ts.map +1 -1
  33. package/dist/esm/types/fhir.js +3 -1
  34. package/dist/esm/types/index.d.ts.map +1 -1
  35. package/dist/styles.css +153 -0
  36. package/package.json +2 -2
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from "react";
3
+ const DEFAULT_TERMS_URL = "https://www.welshare.health/terms-hpmp";
4
+ const DEFAULT_PRIVACY_URL = "https://www.welshare.health/privacy-hpmp";
5
+ /**
6
+ * Legal Consent Form Component
7
+ *
8
+ * A self-contained form component for collecting user consent for:
9
+ * 1. Terms & Conditions and Privacy Policy (required)
10
+ * 2. Study invitation notifications (optional)
11
+ *
12
+ * **Usage Pattern:**
13
+ * This component provides the content for a consent dialog. Applications
14
+ * should wrap this component in their own Dialog/Modal component, similar
15
+ * to how BmiForm is used.
16
+ *
17
+ * **Customization:**
18
+ * - Provide custom checkbox renderer via `renderCheckbox` prop
19
+ * - Customize document URLs via `documentLinks` prop
20
+ * - Style with CSS classes using `className` prop
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <Dialog open={showConsent} onOpenChange={setShowConsent}>
25
+ * <DialogContent>
26
+ * <DialogHeader>
27
+ * <DialogTitle>Health Data Storage Consent</DialogTitle>
28
+ * </DialogHeader>
29
+ * <LegalConsentForm
30
+ * renderCheckbox={({ id, checked, onCheckedChange, className }) => (
31
+ * <Checkbox
32
+ * id={id}
33
+ * checked={checked}
34
+ * onCheckedChange={onCheckedChange}
35
+ * className={className}
36
+ * />
37
+ * )}
38
+ * onConfirm={(result) => {
39
+ * handleConsent(result);
40
+ * setShowConsent(false);
41
+ * }}
42
+ * onCancel={() => setShowConsent(false)}
43
+ * />
44
+ * </DialogContent>
45
+ * </Dialog>
46
+ * ```
47
+ */
48
+ export const LegalConsentForm = ({ initialValues, documentLinks, onConfirm, onCancel, onChange, renderCheckbox, confirmButtonLabel = "Confirm & Continue", cancelButtonLabel = "Cancel", className = "", }) => {
49
+ // Consent state
50
+ const [termsAccepted, setTermsAccepted] = useState(initialValues?.termsAccepted ?? false);
51
+ const [studyConsentAccepted, setStudyConsentAccepted] = useState(initialValues?.studyConsentAccepted ?? false);
52
+ // Resolve document URLs
53
+ const termsUrl = documentLinks?.termsUrl ?? DEFAULT_TERMS_URL;
54
+ const privacyUrl = documentLinks?.privacyUrl ?? DEFAULT_PRIVACY_URL;
55
+ // Handle terms acceptance change
56
+ const handleTermsChange = useCallback((checked) => {
57
+ setTermsAccepted(checked);
58
+ onChange?.({
59
+ termsAccepted: checked,
60
+ studyConsentAccepted,
61
+ });
62
+ }, [onChange, studyConsentAccepted]);
63
+ // Handle study consent change
64
+ const handleStudyConsentChange = useCallback((checked) => {
65
+ setStudyConsentAccepted(checked);
66
+ onChange?.({
67
+ termsAccepted,
68
+ studyConsentAccepted: checked,
69
+ });
70
+ }, [onChange, termsAccepted]);
71
+ // Handle confirm
72
+ const handleConfirm = useCallback(() => {
73
+ if (termsAccepted && onConfirm) {
74
+ onConfirm({
75
+ termsAccepted,
76
+ studyConsentAccepted,
77
+ });
78
+ }
79
+ }, [termsAccepted, studyConsentAccepted, onConfirm]);
80
+ // Handle cancel
81
+ const handleCancel = useCallback(() => {
82
+ onCancel?.();
83
+ }, [onCancel]);
84
+ // Render checkbox - use custom renderer or fallback to native
85
+ const renderCheckboxElement = (id, checked, onCheckedChange) => {
86
+ if (renderCheckbox) {
87
+ return renderCheckbox({
88
+ id,
89
+ checked,
90
+ onCheckedChange,
91
+ className: "wq-legal-checkbox",
92
+ });
93
+ }
94
+ return (_jsx("input", { type: "checkbox", id: id, checked: checked, onChange: (e) => onCheckedChange(e.target.checked), className: "wq-legal-checkbox" }));
95
+ };
96
+ return (_jsxs("div", { className: `wq-legal-consent-form ${className}`, children: [_jsxs("div", { className: "wq-legal-consent-sections", children: [_jsxs("div", { className: "wq-legal-consent-section", children: [_jsx("h3", { className: "wq-legal-consent-heading", children: "1. Agree to T&Cs & Privacy Policy" }), _jsxs("div", { className: "wq-legal-consent-checkbox-row", children: [renderCheckboxElement("terms", termsAccepted, handleTermsChange), _jsxs("label", { htmlFor: "terms", className: "wq-legal-consent-label", children: ["I agree to the", " ", _jsx("a", { href: termsUrl, target: "_blank", rel: "noopener noreferrer", className: "wq-legal-consent-link", children: "Terms & Conditions" }), " ", "and", " ", _jsx("a", { href: privacyUrl, target: "_blank", rel: "noopener noreferrer", className: "wq-legal-consent-link", children: "Privacy Policy" }), "."] })] })] }), _jsxs("div", { className: "wq-legal-consent-section", children: [_jsx("h3", { className: "wq-legal-consent-heading", children: "2. Consent for study invitations / recruitment notifications (optional)" }), _jsx("p", { className: "wq-legal-consent-subtitle", children: "Receive study invitations (optional)" }), _jsx("p", { className: "wq-legal-consent-description", children: "If you opt in, you may be contacted about relevant studies. We will not share your contact or identity with a sponsor unless you explicitly accept a specific invitation." }), _jsxs("div", { className: "wq-legal-consent-info-box", children: [_jsx("p", { className: "wq-legal-consent-info-title", children: "What happens if I opt in?" }), _jsxs("ul", { className: "wq-legal-consent-info-list", children: [_jsx("li", { children: "You may receive emails/in-app messages about studies that match your profile." }), _jsx("li", { children: "If you click \"I'm interested\", we'll show you the details and ask for separate consent to share your contact or to pre-screen you \u2014 you control this per study." }), _jsx("li", { children: "You can opt out of invitations at any time." })] })] }), _jsxs("div", { className: "wq-legal-consent-checkbox-row", children: [renderCheckboxElement("study-consent", studyConsentAccepted, handleStudyConsentChange), _jsx("label", { htmlFor: "study-consent", className: "wq-legal-consent-label", children: "I consent to receive study invitations from Welshare. I can opt out anytime." })] })] })] }), _jsxs("div", { className: "wq-legal-consent-actions", children: [_jsx("button", { type: "button", className: "wq-button wq-button-outline", onClick: handleCancel, children: cancelButtonLabel }), _jsx("button", { type: "button", className: "wq-button wq-button-primary", onClick: handleConfirm, disabled: !termsAccepted, children: confirmButtonLabel })] })] }));
97
+ };
@@ -0,0 +1,21 @@
1
+ import type { Attachment } from "../types/fhir.js";
2
+ export interface MediaAttachmentProps {
3
+ attachment: Attachment;
4
+ alt: string;
5
+ className?: string;
6
+ }
7
+ /**
8
+ * Renders a single media attachment (image) if it has a URL and is an image type
9
+ */
10
+ export declare const MediaAttachment: ({ attachment, alt, className, }: MediaAttachmentProps) => import("react/jsx-runtime").JSX.Element | null;
11
+ export interface MediaAttachmentsProps {
12
+ attachments: Attachment[];
13
+ baseAlt?: string;
14
+ className?: string;
15
+ containerClassName?: string;
16
+ }
17
+ /**
18
+ * Renders multiple media attachments in a container
19
+ */
20
+ export declare const MediaAttachments: ({ attachments, baseAlt, className, containerClassName, }: MediaAttachmentsProps) => import("react/jsx-runtime").JSX.Element | null;
21
+ //# sourceMappingURL=media-attachment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-attachment.d.ts","sourceRoot":"","sources":["../../../src/components/media-attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,iCAI7B,oBAAoB,mDAkBtB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,0DAK9B,qBAAqB,mDAevB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Renders a single media attachment (image) if it has a URL and is an image type
4
+ */
5
+ export const MediaAttachment = ({ attachment, alt, className = "", }) => {
6
+ // Only render if URL exists
7
+ if (!attachment.url)
8
+ return null;
9
+ // Check if it's an image (or assume image if no contentType)
10
+ const isImage = !attachment.contentType || attachment.contentType.startsWith("image/");
11
+ if (!isImage)
12
+ return null;
13
+ return (_jsx("img", { src: attachment.url, alt: alt, className: className, title: attachment.title }));
14
+ };
15
+ /**
16
+ * Renders multiple media attachments in a container
17
+ */
18
+ export const MediaAttachments = ({ attachments, baseAlt = "Image", className = "", containerClassName = "", }) => {
19
+ if (attachments.length === 0)
20
+ return null;
21
+ return (_jsx("div", { className: containerClassName, children: attachments.map((attachment, index) => (_jsx(MediaAttachment, { attachment: attachment, alt: attachment.title || `${baseAlt} ${index + 1}`, className: className }, index))) }));
22
+ };
@@ -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;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
+ {"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;AAkB1D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,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,12 +1,13 @@
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
+ import { isQuestionEnabled, getItemMedia } from "../lib/questionnaire-utils.js";
4
+ import { FHIR_EXTENSIONS, WELSHARE_EXTENSIONS, FHIR_CODE_SYSTEMS, } from "../lib/constants.js";
5
5
  import { DebugSection } from "./debug-section.js";
6
+ import { MediaAttachments } from "./media-attachment.js";
6
7
  import { ChoiceQuestion } from "./questions/choice-question.js";
7
8
  import { MultipleChoiceQuestion } from "./questions/multiple-choice-question.js";
8
9
  import { IntegerQuestion } from "./questions/integer-question.js";
9
- import { DecimalQuestion, getDecimalHelperTriggerProps } from "./questions/decimal-question.js";
10
+ import { DecimalQuestion, getDecimalHelperTriggerProps, } from "./questions/decimal-question.js";
10
11
  import { StringQuestion } from "./questions/string-question.js";
11
12
  import { BooleanQuestion } from "./questions/boolean-question.js";
12
13
  /**
@@ -166,7 +167,9 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
166
167
  const sliderConfig = getSliderConfig();
167
168
  const renderQuestion = () => {
168
169
  switch (item.type) {
170
+ // FHIR R4 uses 'choice', R5 uses 'coding' - both render the same way
169
171
  case "choice":
172
+ case "coding":
170
173
  // Multi-select with checkboxes when repeats is true
171
174
  if (item.repeats) {
172
175
  return (_jsx(MultipleChoiceQuestion, { item: item, currentAnswers: currentAnswers, onMultipleChoiceToggle: handleMultipleChoiceToggle, choiceClassName: choiceClassName, renderCheckboxInput: renderCheckboxInput }));
@@ -190,5 +193,7 @@ const QuestionRendererInternal = ({ item, className = "", inputClassName = "", c
190
193
  const helperTriggerProps = item.type === "decimal"
191
194
  ? getDecimalHelperTriggerProps(item, currentAnswer, handleDecimalChange)
192
195
  : 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()] }));
196
+ // Get media attachments for this question
197
+ const mediaAttachments = getItemMedia(item);
198
+ return (_jsxs("div", { className: `wq-question-container ${hasError ? "wq-has-error" : ""} ${className}`, children: [_jsx(MediaAttachments, { attachments: mediaAttachments, baseAlt: "Question image", className: "wq-question-image", containerClassName: "wq-question-media" }), _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()] }));
194
199
  };
@@ -1,6 +1,6 @@
1
- import { type ReactNode } from 'react';
2
- import type { QuestionnaireItem, QuestionnaireResponseAnswer } from '../../types/fhir.js';
3
- import { RadioInputProps } from '@/types/index.js';
1
+ import { type ReactNode } from "react";
2
+ import type { QuestionnaireItem, QuestionnaireResponseAnswer } from "../../types/fhir.js";
3
+ import { RadioInputProps } from "@/types/index.js";
4
4
  export interface BooleanQuestionProps {
5
5
  item: QuestionnaireItem;
6
6
  currentAnswer?: QuestionnaireResponseAnswer;
@@ -11,5 +11,5 @@ export interface BooleanQuestionProps {
11
11
  /**
12
12
  * Renders a boolean question with Yes/No radio buttons
13
13
  */
14
- export declare const BooleanQuestion: ({ item, currentAnswer, onBooleanChange, choiceClassName, renderRadioInput }: BooleanQuestionProps) => import("react/jsx-runtime").JSX.Element;
14
+ export declare const BooleanQuestion: ({ item, currentAnswer, onBooleanChange, choiceClassName, renderRadioInput, }: BooleanQuestionProps) => import("react/jsx-runtime").JSX.Element;
15
15
  //# sourceMappingURL=boolean-question.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"boolean-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/boolean-question.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;CAC1D;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,6EAM7B,oBAAoB,4CAgDtB,CAAC"}
1
+ {"version":3,"file":"boolean-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/boolean-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,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;CAC1D;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,8EAM7B,oBAAoB,4CAoDtB,CAAC"}
@@ -2,18 +2,18 @@ import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-run
2
2
  /**
3
3
  * Renders a boolean question with Yes/No radio buttons
4
4
  */
5
- export const BooleanQuestion = ({ item, currentAnswer, onBooleanChange, choiceClassName = '', renderRadioInput }) => {
5
+ export const BooleanQuestion = ({ item, currentAnswer, onBooleanChange, choiceClassName = "", renderRadioInput, }) => {
6
6
  return (_jsx("div", { className: `wq-question-choice ${choiceClassName}`, children: renderRadioInput ? (_jsxs(_Fragment, { children: [renderRadioInput({
7
7
  linkId: item.linkId,
8
8
  checked: currentAnswer?.valueBoolean === true,
9
9
  onChange: () => onBooleanChange(true),
10
- label: 'Yes',
11
- index: 0
10
+ label: "Yes",
11
+ index: 0,
12
12
  }), renderRadioInput({
13
13
  linkId: item.linkId,
14
14
  checked: currentAnswer?.valueBoolean === false,
15
15
  onChange: () => onBooleanChange(false),
16
- label: 'No',
17
- index: 1
18
- })] })) : (_jsxs(_Fragment, { children: [_jsxs("label", { className: `wq-choice-option ${currentAnswer?.valueBoolean === true ? 'wq-selected' : ''}`, children: [_jsx("input", { type: "radio", name: item.linkId, checked: currentAnswer?.valueBoolean === true, onChange: () => onBooleanChange(true), "data-wq-input": "radio", "data-wq-selected": currentAnswer?.valueBoolean === true }), _jsx("span", { className: "wq-choice-label", children: "Yes" })] }), _jsxs("label", { className: `wq-choice-option ${currentAnswer?.valueBoolean === false ? 'wq-selected' : ''}`, children: [_jsx("input", { type: "radio", name: item.linkId, checked: currentAnswer?.valueBoolean === false, onChange: () => onBooleanChange(false), "data-wq-input": "radio", "data-wq-selected": currentAnswer?.valueBoolean === false }), _jsx("span", { className: "wq-choice-label", children: "No" })] })] })) }));
16
+ label: "No",
17
+ index: 1,
18
+ })] })) : (_jsxs(_Fragment, { children: [_jsxs("label", { className: `wq-choice-option ${currentAnswer?.valueBoolean === true ? "wq-selected" : ""}`, children: [_jsx("input", { type: "radio", name: item.linkId, checked: currentAnswer?.valueBoolean === true, onChange: () => onBooleanChange(true), "data-wq-input": "radio", "data-wq-selected": currentAnswer?.valueBoolean === true }), _jsx("span", { className: "wq-choice-label", children: "Yes" })] }), _jsxs("label", { className: `wq-choice-option ${currentAnswer?.valueBoolean === false ? "wq-selected" : ""}`, children: [_jsx("input", { type: "radio", name: item.linkId, checked: currentAnswer?.valueBoolean === false, onChange: () => onBooleanChange(false), "data-wq-input": "radio", "data-wq-selected": currentAnswer?.valueBoolean === false }), _jsx("span", { className: "wq-choice-label", children: "No" })] })] })) }));
19
19
  };
@@ -1,6 +1,6 @@
1
- import { type ReactNode } from 'react';
2
- import type { QuestionnaireItem, QuestionnaireResponseAnswer } from '../../types/fhir.js';
3
- import { RadioInputProps } from '@/types/index.js';
1
+ import { type ReactNode } from "react";
2
+ import type { QuestionnaireItem, QuestionnaireResponseAnswer } from "../../types/fhir.js";
3
+ import { RadioInputProps } from "@/types/index.js";
4
4
  export interface ChoiceQuestionProps {
5
5
  item: QuestionnaireItem;
6
6
  currentAnswer?: QuestionnaireResponseAnswer;
@@ -15,5 +15,5 @@ export interface ChoiceQuestionProps {
15
15
  /**
16
16
  * Renders a single-select choice question with radio buttons
17
17
  */
18
- export declare const ChoiceQuestion: ({ item, currentAnswer, onChoiceChange, choiceClassName, renderRadioInput }: ChoiceQuestionProps) => import("react/jsx-runtime").JSX.Element;
18
+ export declare const ChoiceQuestion: ({ item, currentAnswer, onChoiceChange, choiceClassName, renderRadioInput, }: ChoiceQuestionProps) => import("react/jsx-runtime").JSX.Element;
19
19
  //# sourceMappingURL=choice-question.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"choice-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/choice-question.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,cAAc,EAAE,CACd,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,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;CAC1D;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,4EAM5B,mBAAmB,4CAyCrB,CAAC"}
1
+ {"version":3,"file":"choice-question.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/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;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,cAAc,EAAE,CACd,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,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;CAC1D;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,6EAM5B,mBAAmB,4CA4ErB,CAAC"}
@@ -1,23 +1,31 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getAnswerOptionMedia } from "../../lib/questionnaire-utils.js";
3
+ import { MediaAttachment } from "../media-attachment.js";
2
4
  /**
3
5
  * Renders a single-select choice question with radio buttons
4
6
  */
5
- export const ChoiceQuestion = ({ item, currentAnswer, onChoiceChange, choiceClassName = '', renderRadioInput }) => {
7
+ export const ChoiceQuestion = ({ item, currentAnswer, onChoiceChange, choiceClassName = "", renderRadioInput, }) => {
6
8
  return (_jsx("div", { className: `wq-question-choice ${choiceClassName}`, children: item.answerOption?.map((option, index) => {
7
9
  const isSelected = currentAnswer?.valueCoding?.code === option.valueCoding?.code;
10
+ // Get media attachment for this answer option
11
+ const mediaAttachment = getAnswerOptionMedia(option);
8
12
  // Use custom renderer if provided
9
13
  if (renderRadioInput) {
10
- return (_jsx("div", { children: renderRadioInput({
11
- linkId: item.linkId,
12
- valueCoding: option.valueCoding,
13
- valueInteger: option.valueInteger,
14
- checked: isSelected,
15
- onChange: () => onChoiceChange(option.valueCoding || {}, option.valueInteger),
16
- label: option.valueCoding?.display || '',
17
- index
18
- }) }, index));
14
+ return (_jsxs("div", { className: "wq-choice-option-wrapper", children: [mediaAttachment && (_jsx(MediaAttachment, { attachment: mediaAttachment, alt: mediaAttachment.title ||
15
+ option.valueCoding?.display ||
16
+ `Option ${index + 1}`, className: "wq-choice-option-image" })), renderRadioInput({
17
+ linkId: item.linkId,
18
+ valueCoding: option.valueCoding,
19
+ valueInteger: option.valueInteger,
20
+ checked: isSelected,
21
+ onChange: () => onChoiceChange(option.valueCoding || {}, option.valueInteger),
22
+ label: option.valueCoding?.display || "",
23
+ index,
24
+ })] }, index));
19
25
  }
20
26
  // Default rendering
21
- return (_jsxs("label", { className: `wq-choice-option ${isSelected ? 'wq-selected' : ''}`, children: [_jsx("input", { type: "radio", name: item.linkId, value: option.valueCoding?.code, checked: isSelected, onChange: () => onChoiceChange(option.valueCoding || {}, option.valueInteger), "data-wq-input": "radio", "data-wq-selected": isSelected }), _jsx("span", { className: "wq-choice-label", children: option.valueCoding?.display })] }, index));
27
+ return (_jsxs("div", { className: "wq-choice-option-wrapper", children: [mediaAttachment && (_jsx(MediaAttachment, { attachment: mediaAttachment, alt: mediaAttachment.title ||
28
+ option.valueCoding?.display ||
29
+ `Option ${index + 1}`, className: "wq-choice-option-image" })), _jsxs("label", { className: `wq-choice-option ${isSelected ? "wq-selected" : ""}`, children: [_jsx("input", { type: "radio", name: item.linkId, value: option.valueCoding?.code, checked: isSelected, onChange: () => onChoiceChange(option.valueCoding || {}, option.valueInteger), "data-wq-input": "radio", "data-wq-selected": isSelected }), _jsx("span", { className: "wq-choice-label", children: option.valueCoding?.display })] })] }, index));
22
30
  }) }));
23
31
  };
@@ -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;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
+ {"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;AAI7B,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,4CAkH7B,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { WELSHARE_EXTENSIONS } from "../../lib/constants.js";
3
+ import { getAnswerOptionMedia } from "../../lib/questionnaire-utils.js";
4
+ import { MediaAttachment } from "../media-attachment.js";
3
5
  /**
4
6
  * Renders a multi-select choice question with checkboxes
5
7
  * Supports exclusive options and maxAnswers limits
@@ -20,20 +22,26 @@ export const MultipleChoiceQuestion = ({ item, currentAnswers, onMultipleChoiceT
20
22
  return (_jsxs("div", { className: `wq-question-choice ${choiceClassName}`, children: [optionsToShow?.map((option, index) => {
21
23
  const isSelected = currentAnswers.some((answer) => answer.valueCoding?.code === option.valueCoding?.code);
22
24
  const isDisabled = !isSelected && atMaxAnswers;
25
+ // Get media attachment for this answer option
26
+ const mediaAttachment = getAnswerOptionMedia(option);
23
27
  // Use custom renderer if provided
24
28
  if (renderCheckboxInput) {
25
- return (_jsx("div", { children: renderCheckboxInput({
26
- linkId: item.linkId,
27
- valueCoding: option.valueCoding,
28
- valueInteger: option.valueInteger,
29
- checked: isSelected,
30
- disabled: isDisabled,
31
- onChange: () => onMultipleChoiceToggle(option.valueCoding || {}, option.valueInteger),
32
- label: option.valueCoding?.display || "",
33
- index,
34
- }) }, index));
29
+ return (_jsxs("div", { className: "wq-choice-option-wrapper", children: [mediaAttachment && (_jsx(MediaAttachment, { attachment: mediaAttachment, alt: mediaAttachment.title ||
30
+ option.valueCoding?.display ||
31
+ `Option ${index + 1}`, className: "wq-choice-option-image" })), renderCheckboxInput({
32
+ linkId: item.linkId,
33
+ valueCoding: option.valueCoding,
34
+ valueInteger: option.valueInteger,
35
+ checked: isSelected,
36
+ disabled: isDisabled,
37
+ onChange: () => onMultipleChoiceToggle(option.valueCoding || {}, option.valueInteger),
38
+ label: option.valueCoding?.display || "",
39
+ index,
40
+ })] }, index));
35
41
  }
36
42
  // Default rendering
37
- return (_jsxs("label", { className: `wq-choice-option ${isSelected ? "wq-selected" : ""} ${isDisabled ? "wq-disabled" : ""}`, children: [_jsx("input", { type: "checkbox", name: item.linkId, value: option.valueCoding?.code, checked: isSelected, disabled: isDisabled, onChange: () => onMultipleChoiceToggle(option.valueCoding || {}, option.valueInteger), "data-wq-input": "checkbox", "data-wq-selected": isSelected }), _jsx("span", { className: "wq-choice-label", children: option.valueCoding?.display })] }, index));
43
+ return (_jsxs("div", { className: "wq-choice-option-wrapper", children: [mediaAttachment && (_jsx(MediaAttachment, { attachment: mediaAttachment, alt: mediaAttachment.title ||
44
+ option.valueCoding?.display ||
45
+ `Option ${index + 1}`, className: "wq-choice-option-image" })), _jsxs("label", { className: `wq-choice-option ${isSelected ? "wq-selected" : ""} ${isDisabled ? "wq-disabled" : ""}`, children: [_jsx("input", { type: "checkbox", name: item.linkId, value: option.valueCoding?.code, checked: isSelected, disabled: isDisabled, onChange: () => onMultipleChoiceToggle(option.valueCoding || {}, option.valueInteger), "data-wq-input": "checkbox", "data-wq-selected": isSelected }), _jsx("span", { className: "wq-choice-label", children: option.valueCoding?.display })] })] }, index));
38
46
  }), item.maxAnswers && (_jsxs("div", { className: "wq-max-answers-hint", children: ["Selected: ", currentAnswers.length, " / ", item.maxAnswers] }))] }));
39
47
  };
@@ -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;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
+ {"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,4CAuZ5B,CAAC"}
@@ -91,10 +91,7 @@ export const QuestionnaireProvider = ({ children, questionnaire, questionnaireId
91
91
  }
92
92
  // If this item has nested items, search within them
93
93
  if (item.item && item.item.length > 0) {
94
- return {
95
- ...item,
96
- item: updateNestedItem(item.item),
97
- };
94
+ return { ...item, item: updateNestedItem(item.item) };
98
95
  }
99
96
  return item;
100
97
  });
@@ -122,13 +119,7 @@ export const QuestionnaireProvider = ({ children, questionnaire, questionnaireId
122
119
  }
123
120
  else {
124
121
  // Create new item
125
- return [
126
- ...items,
127
- {
128
- linkId,
129
- answer: [answer],
130
- },
131
- ];
122
+ return [...items, { linkId, answer: [answer] }];
132
123
  }
133
124
  };
134
125
  newResponse.item = findOrCreateItem(newResponse.item);
@@ -161,10 +152,7 @@ export const QuestionnaireProvider = ({ children, questionnaire, questionnaireId
161
152
  }
162
153
  // If this item has nested items, search within them
163
154
  if (item.item && item.item.length > 0) {
164
- return {
165
- ...item,
166
- item: updateNestedItem(item.item),
167
- };
155
+ return { ...item, item: updateNestedItem(item.item) };
168
156
  }
169
157
  return item;
170
158
  });
@@ -190,13 +178,7 @@ export const QuestionnaireProvider = ({ children, questionnaire, questionnaireId
190
178
  return updated;
191
179
  }
192
180
  else {
193
- return [
194
- ...items,
195
- {
196
- linkId,
197
- answer: answers,
198
- },
199
- ];
181
+ return [...items, { linkId, answer: answers }];
200
182
  }
201
183
  };
202
184
  newResponse.item = findOrCreateItem(newResponse.item);
@@ -2,10 +2,12 @@ export { QuestionRenderer } from "./components/question-renderer.js";
2
2
  export type { QuestionRendererProps } from "./components/question-renderer.js";
3
3
  export { BmiForm } from "./components/bmi-form.js";
4
4
  export type { BmiFormProps } from "./components/bmi-form.js";
5
+ export { LegalConsentForm } from "./components/legal-consent-form.js";
6
+ export type { LegalConsentFormProps, LegalConsentResult, LegalCheckboxProps, LegalDocumentLinks, } from "./components/legal-consent-form.js";
5
7
  export { QuestionnaireProvider, useQuestionnaire, type QuestionnaireContextType, type QuestionnaireProviderProps, } from "./contexts/questionnaire-context.js";
6
- export type { Coding, Extension, Questionnaire, QuestionnaireItem, QuestionnaireItemAnswerOption, QuestionnaireResponse, QuestionnaireResponseAnswer, QuestionnaireResponseItem, } from "./types/fhir.js";
8
+ export type { Attachment, Coding, Extension, Questionnaire, QuestionnaireItem, QuestionnaireItemAnswerOption, QuestionnaireResponse, QuestionnaireResponseAnswer, QuestionnaireResponseItem, } from "./types/fhir.js";
7
9
  export type { RadioInputProps, CheckboxInputProps, InputHelperConfig, HelperTriggerProps, } from "./types/index.js";
8
- export { calculateProgress, findQuestionnaireItem, getAllQuestionsFromPage, getExclusiveOptionCode, getInputHelper, getVisiblePages, hasAnswerValue, isQuestionHidden, } from "./lib/questionnaire-utils.js";
10
+ export { calculateProgress, findQuestionnaireItem, getAllQuestionsFromPage, getAnswerOptionMedia, getExclusiveOptionCode, getInputHelper, getItemMedia, getVisiblePages, hasAnswerValue, isQuestionHidden, } from "./lib/questionnaire-utils.js";
9
11
  export { calculateBmi } from "./lib/bmi-helpers.js";
10
12
  export { FHIR_EXTENSIONS, WELSHARE_EXTENSIONS, WELSHARE_CODE_SYSTEMS, FHIR_CODE_SYSTEMS, } from "./lib/constants.js";
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,YAAY,EACV,qBAAqB,EACtB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,GAChC,MAAM,qCAAqC,CAAC;AAG7C,YAAY,EACV,MAAM,EACN,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,6BAA6B,EAC7B,qBAAqB,EACrB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,cAAc,EACd,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,YAAY,EACb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE/E,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,YAAY,EACV,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,GAChC,MAAM,qCAAqC,CAAC;AAG7C,YAAY,EACV,UAAU,EACV,MAAM,EACN,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,6BAA6B,EAC7B,qBAAqB,EACrB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC"}
package/dist/esm/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  // Components
2
2
  export { QuestionRenderer } from "./components/question-renderer.js";
3
3
  export { BmiForm } from "./components/bmi-form.js";
4
+ export { LegalConsentForm } from "./components/legal-consent-form.js";
4
5
  // Contexts
5
6
  export { QuestionnaireProvider, useQuestionnaire, } from "./contexts/questionnaire-context.js";
6
7
  // Utils
7
- export { calculateProgress, findQuestionnaireItem, getAllQuestionsFromPage, getExclusiveOptionCode, getInputHelper, getVisiblePages, hasAnswerValue, isQuestionHidden, } from "./lib/questionnaire-utils.js";
8
+ export { calculateProgress, findQuestionnaireItem, getAllQuestionsFromPage, getAnswerOptionMedia, getExclusiveOptionCode, getInputHelper, getItemMedia, getVisiblePages, hasAnswerValue, isQuestionHidden, } from "./lib/questionnaire-utils.js";
8
9
  export { calculateBmi } from "./lib/bmi-helpers.js";
9
10
  // Constants
10
11
  export { FHIR_EXTENSIONS, WELSHARE_EXTENSIONS, WELSHARE_CODE_SYSTEMS, FHIR_CODE_SYSTEMS, } from "./lib/constants.js";
@@ -1 +1 @@
1
- {"version":3,"file":"bmi-helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/bmi-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,OAAO,CAAC;AAChC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,SAAS,aAAa,CAAC;AAEpC;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAK9E;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOvE"}
1
+ {"version":3,"file":"bmi-helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/bmi-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,OAAO,CAAC;AAChC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,SAAS,aAAa,CAAC;AAEpC;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAKA;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOvE"}
@@ -9,6 +9,18 @@ export declare const FHIR_EXTENSIONS: {
9
9
  * @see http://hl7.org/fhir/StructureDefinition/questionnaire-hidden
10
10
  */
11
11
  readonly QUESTIONNAIRE_HIDDEN: "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden";
12
+ /**
13
+ * SDC extension for attaching media (images, audio, video) to questionnaire items
14
+ * Type: Attachment with contentType, url, title, etc.
15
+ * @see http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia
16
+ */
17
+ readonly ITEM_MEDIA: "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia";
18
+ /**
19
+ * SDC extension for attaching media to specific answer options
20
+ * Type: Attachment with contentType, url, title, etc.
21
+ * @see http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia
22
+ */
23
+ readonly ITEM_ANSWER_MEDIA: "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia";
12
24
  };
13
25
  /**
14
26
  * Welshare Custom Extension URLs
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;OAIG;;CAEK,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;IAC9B;;;;;;;;;;;OAWG;;IAGH;;;;;;;;;;;;;;;;;OAiBG;;IAGH;;;;;;;;;;;;;;;;OAgBG;;CAEK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,qBAAqB;IAChC;;;;OAIG;;CAEK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,iBAAiB;IAC5B;;;;OAIG;;CAEK,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;OAIG;;IAIH;;;;OAIG;;IAIH;;;;OAIG;;CAGK,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;IAC9B;;;;;;;;;;;OAWG;;IAIH;;;;;;;;;;;;;;;;;OAiBG;;IAIH;;;;;;;;;;;;;;;;OAgBG;;CAGK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,qBAAqB;IAChC;;;;OAIG;;CAEK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,iBAAiB;IAC5B;;;;OAIG;;CAEK,CAAC"}
@@ -9,6 +9,18 @@ export const FHIR_EXTENSIONS = {
9
9
  * @see http://hl7.org/fhir/StructureDefinition/questionnaire-hidden
10
10
  */
11
11
  QUESTIONNAIRE_HIDDEN: "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
12
+ /**
13
+ * SDC extension for attaching media (images, audio, video) to questionnaire items
14
+ * Type: Attachment with contentType, url, title, etc.
15
+ * @see http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia
16
+ */
17
+ ITEM_MEDIA: "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia",
18
+ /**
19
+ * SDC extension for attaching media to specific answer options
20
+ * Type: Attachment with contentType, url, title, etc.
21
+ * @see http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia
22
+ */
23
+ ITEM_ANSWER_MEDIA: "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia",
12
24
  };
13
25
  /**
14
26
  * Welshare Custom Extension URLs
@@ -1,4 +1,4 @@
1
- import type { Questionnaire, QuestionnaireItem, QuestionnaireResponseAnswer } from "../types/fhir.js";
1
+ import type { Questionnaire, QuestionnaireItem, QuestionnaireResponseAnswer, QuestionnaireItemAnswerOption, Attachment } from "../types/fhir.js";
2
2
  /**
3
3
  * Get visible pages from a questionnaire (excludes hidden groups)
4
4
  */
@@ -50,4 +50,18 @@ export declare const getInputHelper: (item: QuestionnaireItem) => InputHelperCon
50
50
  * @returns true if the question should be enabled, false otherwise
51
51
  */
52
52
  export declare const isQuestionEnabled: (item: QuestionnaireItem, getAnswer: (linkId: string) => QuestionnaireResponseAnswer | undefined) => boolean;
53
+ /**
54
+ * Extract media attachments from a questionnaire item's extensions
55
+ * Returns all itemMedia extensions as an array of Attachments
56
+ * @param item The questionnaire item to extract media from
57
+ * @returns Array of Attachment objects (can be empty if no media found)
58
+ */
59
+ export declare const getItemMedia: (item: QuestionnaireItem) => Attachment[];
60
+ /**
61
+ * Extract media attachment from an answer option's extensions
62
+ * Returns the first itemAnswerMedia extension found
63
+ * @param option The answer option to extract media from
64
+ * @returns Attachment object or undefined if no media found
65
+ */
66
+ export declare const getAnswerOptionMedia: (option: QuestionnaireItemAnswerOption) => Attachment | undefined;
53
67
  //# sourceMappingURL=questionnaire-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"questionnaire-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/questionnaire-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAG1B;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,eAAe,aAAa,KAC3B,iBAAiB,EAcnB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,kBAAkB,MAAM,EACxB,YAAY,MAAM,KACjB,MAGF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,iBAAiB,KAC1B,iBAAiB,EAsBnB,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACtC,MAAM,EAAE,MAAM,GACb,iBAAiB,GAAG,IAAI,CAe1B;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,GAAG,KAAG,OAe5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,iBAAiB,KAAG,OAQ1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,iBAAiB,KACtB,MAAM,GAAG,SAKX,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,iBAAiB,KACtB,iBAAiB,GAAG,IAatB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,iBAAiB,EACvB,WAAW,CAAC,MAAM,EAAE,MAAM,KAAK,2BAA2B,GAAG,SAAS,KACrE,OAkEF,CAAC"}
1
+ {"version":3,"file":"questionnaire-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/questionnaire-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,2BAA2B,EAC3B,6BAA6B,EAC7B,UAAU,EACX,MAAM,kBAAkB,CAAC;AAG1B;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,eAAe,aAAa,KAC3B,iBAAiB,EAcnB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,kBAAkB,MAAM,EACxB,YAAY,MAAM,KACjB,MAGF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,iBAAiB,KAC1B,iBAAiB,EAsBnB,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACtC,MAAM,EAAE,MAAM,GACb,iBAAiB,GAAG,IAAI,CAe1B;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,GAAG,KAAG,OAe5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,iBAAiB,KAAG,OAQ1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,iBAAiB,KACtB,MAAM,GAAG,SAKX,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,iBAAiB,KACtB,iBAAiB,GAAG,IAatB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,iBAAiB,EACvB,WAAW,CAAC,MAAM,EAAE,MAAM,KAAK,2BAA2B,GAAG,SAAS,KACrE,OAkEF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,iBAAiB,KAAG,UAAU,EAOhE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,6BAA6B,KACpC,UAAU,GAAG,SAQf,CAAC"}
@@ -178,3 +178,29 @@ export const isQuestionEnabled = (item, getAnswer) => {
178
178
  }
179
179
  return false;
180
180
  };
181
+ /**
182
+ * Extract media attachments from a questionnaire item's extensions
183
+ * Returns all itemMedia extensions as an array of Attachments
184
+ * @param item The questionnaire item to extract media from
185
+ * @returns Array of Attachment objects (can be empty if no media found)
186
+ */
187
+ export const getItemMedia = (item) => {
188
+ if (!item.extension)
189
+ return [];
190
+ return item.extension
191
+ .filter((ext) => ext.url === FHIR_EXTENSIONS.ITEM_MEDIA)
192
+ .map((ext) => ext.valueAttachment)
193
+ .filter((attachment) => attachment !== undefined);
194
+ };
195
+ /**
196
+ * Extract media attachment from an answer option's extensions
197
+ * Returns the first itemAnswerMedia extension found
198
+ * @param option The answer option to extract media from
199
+ * @returns Attachment object or undefined if no media found
200
+ */
201
+ export const getAnswerOptionMedia = (option) => {
202
+ if (!option.extension)
203
+ return undefined;
204
+ const mediaExt = option.extension.find((ext) => ext.url === FHIR_EXTENSIONS.ITEM_ANSWER_MEDIA);
205
+ return mediaExt?.valueAttachment;
206
+ };