@evaro/consultation-sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -2
- package/dist/index.mjs +19896 -0
- package/dist/types/Consultation.d.ts +24 -0
- package/dist/types/api.d.ts +15 -0
- package/dist/types/events.d.ts +15 -0
- package/dist/types/flow/answer.d.ts +46 -0
- package/dist/types/flow/path.d.ts +27 -0
- package/dist/types/flow/questionnaire.d.ts +133 -0
- package/dist/types/flow/useConsultationFlow.d.ts +36 -0
- package/dist/types/flow/validations.d.ts +6 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/sanitize.d.ts +2 -0
- package/dist/types/targets/diagnosisRequest.d.ts +3 -0
- package/dist/types/targets/index.d.ts +3 -0
- package/dist/types/types.d.ts +67 -0
- package/dist/types/ui/Knockout.d.ts +8 -0
- package/dist/types/ui/Question.d.ts +21 -0
- package/dist/types/ui/ReadOnlyAnswers.d.ts +9 -0
- package/dist/types/ui/ShadowRoot.d.ts +6 -0
- package/dist/types/ui/primitives.d.ts +37 -0
- package/package.json +36 -4
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ConsultationEvent, ConsultationType, Environment, GetToken, Target } from './types';
|
|
2
|
+
export interface ConsultationProps {
|
|
3
|
+
/** The hash of the consultation to show. */
|
|
4
|
+
consultation: string;
|
|
5
|
+
/** The vendor hash. It selects the theme and goes to the API, as in Connect. */
|
|
6
|
+
vendor: string;
|
|
7
|
+
environment: Environment;
|
|
8
|
+
/** Where the answers are saved. With no target the package runs as a preview and saves nothing. */
|
|
9
|
+
target?: Target;
|
|
10
|
+
/** Returns the signed-in user's token for the target's calls. */
|
|
11
|
+
getToken?: GetToken;
|
|
12
|
+
/** Shows the target's saved answers. They cannot be changed or submitted. */
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
/** Loads the unpublished version of the consultation. */
|
|
15
|
+
preview?: boolean;
|
|
16
|
+
/** Reported as `consultation_type` on the events. Defaults to the consultation's own type. */
|
|
17
|
+
consultationType?: ConsultationType;
|
|
18
|
+
/** Called when the flow ends: after submit, or when the user closes the knockout screen. */
|
|
19
|
+
onDone?: () => void;
|
|
20
|
+
/** Receives the analytics signals, with the names and fields Connect uses. */
|
|
21
|
+
onEvent?: (event: ConsultationEvent) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare const DRAFT_DELAY_MS = 600;
|
|
24
|
+
export declare function Consultation(props: ConsultationProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Environment } from './types';
|
|
2
|
+
export declare const apiHost: (environment: Environment) => string;
|
|
3
|
+
export declare const nodeApiHost: (environment: Environment) => string;
|
|
4
|
+
export declare class RequestError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
constructor(message: string, status: number);
|
|
7
|
+
}
|
|
8
|
+
export declare function requestJson<T>(url: string, init?: RequestInit): Promise<T>;
|
|
9
|
+
export interface VendorTheme {
|
|
10
|
+
colours?: Partial<Record<'100' | '300' | '500' | '700' | '900', string>>;
|
|
11
|
+
}
|
|
12
|
+
/** The consultation as Connect loads it. `preview` reads the unpublished version, which needs no token. */
|
|
13
|
+
export declare function fetchVendorConsultation(environment: Environment, hash: string, vendor: string, preview: boolean): Promise<iConsultation>;
|
|
14
|
+
/** The vendor's colours, loaded once for each vendor. A missing theme leaves Connect's default palette. */
|
|
15
|
+
export declare function fetchVendorTheme(environment: Environment, vendor: string): Promise<VendorTheme | null>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ConsultationEvent, ConsultationType } from './types';
|
|
2
|
+
export interface EventContext {
|
|
3
|
+
consultationHash: string;
|
|
4
|
+
experimentOf: string | undefined;
|
|
5
|
+
consultationType: ConsultationType;
|
|
6
|
+
}
|
|
7
|
+
/** Builds the signals with the same names and fields as Connect's `utils/analytics.ts`. */
|
|
8
|
+
export declare const consultationEvents: (context: EventContext) => {
|
|
9
|
+
started: () => ConsultationEvent;
|
|
10
|
+
completed: () => ConsultationEvent;
|
|
11
|
+
exited: () => ConsultationEvent;
|
|
12
|
+
viewed: (number: number, all: number) => ConsultationEvent;
|
|
13
|
+
questionViewed: (hash: string, skipped?: boolean) => ConsultationEvent;
|
|
14
|
+
questionAnswered: (hash: string, skipped?: boolean) => ConsultationEvent;
|
|
15
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type AnswerValue = string | string[];
|
|
2
|
+
export type AnswerMap = Record<string, AnswerValue>;
|
|
3
|
+
type OptionLogic = iQuestion['options'][number]['logic'];
|
|
4
|
+
/** Types that wait for Continue: free text, multi-select and types with their own input. */
|
|
5
|
+
export declare const NON_AUTO_PROGRESS_TYPES: string[];
|
|
6
|
+
export interface AnswerState {
|
|
7
|
+
answers: AnswerMap;
|
|
8
|
+
optionHashes: AnswerMap;
|
|
9
|
+
}
|
|
10
|
+
export type Navigation = {
|
|
11
|
+
kind: 'none';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'next';
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'goto';
|
|
16
|
+
hash: string;
|
|
17
|
+
};
|
|
18
|
+
export interface AnswerResult extends AnswerState {
|
|
19
|
+
/** The jump to hold until Continue. `undefined` leaves the current one. */
|
|
20
|
+
nextOverride: string | null | undefined;
|
|
21
|
+
navigation: Navigation;
|
|
22
|
+
}
|
|
23
|
+
interface AnswerInput {
|
|
24
|
+
question: iQuestion;
|
|
25
|
+
state: AnswerState;
|
|
26
|
+
value: AnswerValue;
|
|
27
|
+
logic?: OptionLogic;
|
|
28
|
+
/** False for a follow-up that is not the last of its group, or while one above it is blank. */
|
|
29
|
+
ownsProgression: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Where a checkbox answer jumps: the first ticked option that has a go-to. The shared next-question rule takes
|
|
33
|
+
* the first ticked option and then reads its go-to, which misses a jump on a later option.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkboxJump(question: iQuestion | undefined, answer: AnswerValue | null | undefined, optionHash?: AnswerValue | null): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* The jump the live flow holds for Continue on a question: one set in an open follow-up, then a checkbox jump
|
|
38
|
+
* on the main question. A main radio's own go-to is left to the shared next-question rule.
|
|
39
|
+
*
|
|
40
|
+
* The live flow keeps the jump of the last click, and click order is not saved, so a later follow-up in the
|
|
41
|
+
* group wins here. The two differ only when two open follow-ups both hold a jump, or one clears another's.
|
|
42
|
+
*/
|
|
43
|
+
export declare function heldJump(question: iQuestion, allQuestions: iQuestion[], answers: AnswerMap, optionHashes?: AnswerMap): string | null;
|
|
44
|
+
/** Applies one answer. Returns null when the click changes nothing, such as the same radio option again. */
|
|
45
|
+
export declare function applyAnswer({ question, state, value, logic, ownsProgression }: AnswerInput): AnswerResult | null;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type AnswerMap } from './answer';
|
|
2
|
+
/**
|
|
3
|
+
* Only the answers on the path the user took. Going Back and choosing another option leaves the answers of the
|
|
4
|
+
* old branch in state; they must not be saved as if they were given on this path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function answersOnPath(history: string[], allQuestions: iQuestion[], answers: AnswerMap, optionHashes: AnswerMap): AnswerMap;
|
|
7
|
+
/** The consultation and the answers that a walk through the flow follows. */
|
|
8
|
+
export interface Walk {
|
|
9
|
+
allQuestions: iQuestion[];
|
|
10
|
+
visibleQuestions: iQuestion[];
|
|
11
|
+
consultationHash: string;
|
|
12
|
+
answers: AnswerMap;
|
|
13
|
+
optionHashes: AnswerMap;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Where a saved draft opens: walks the flow from the first question while each one holds a complete answer.
|
|
17
|
+
* Returns the questions passed on the way, with the one to show last.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resumeHistory(walk: Walk): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The answers a draft keeps: the path behind the user, and ahead of them the questions the current answers
|
|
22
|
+
* still lead to. After Back, answers further on survive while the user edits, even when an edit leaves the
|
|
23
|
+
* current question unfinished. Answers of a branch the user left are dropped.
|
|
24
|
+
*/
|
|
25
|
+
export declare function draftAnswers(history: string[], walk: Walk): AnswerMap;
|
|
26
|
+
/** Whether every question on the path has a complete answer, so the answers can be submitted. */
|
|
27
|
+
export declare function isPathComplete(history: string[], allQuestions: iQuestion[], answers: AnswerMap, optionHashes: AnswerMap): boolean;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic functions for the questionnaire consultation flow.
|
|
3
|
+
* Used by QuestionnaireManager and Question so the behaviour lives in one place.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Returns only the questions that should be visible to the patient:
|
|
7
|
+
* - excludes follow-up questions (shown inline)
|
|
8
|
+
* - excludes questions whose hash is in the hidden-answers map
|
|
9
|
+
* - excludes questions that don't apply to the patient's age or gender
|
|
10
|
+
*/
|
|
11
|
+
export declare function filterVisibleQuestions(questions: iQuestion[], hiddenAnswers: Record<string, unknown>, demographics?: PatientDemographics | null): iQuestion[];
|
|
12
|
+
/** Age and gender known before the consultation starts, when the patient is picked or described up front. */
|
|
13
|
+
export interface PatientDemographics {
|
|
14
|
+
age: number | null;
|
|
15
|
+
gender: 'male' | 'female' | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether a question doesn't apply to the patient. Questions carry no age or gender conditions yet, so
|
|
19
|
+
* nothing is hidden; the rules go here once the API sends them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isHiddenForPatient(_question: iQuestion, _demographics: PatientDemographics | null): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Clears every jump naming a question the list no longer holds, so the flow falls through in order rather
|
|
24
|
+
* than opening one that cannot render. Follow-ups too: a pending one that renders nothing holds Continue shut.
|
|
25
|
+
*/
|
|
26
|
+
export declare function withReachableJumps(questions: iQuestion[]): iQuestion[];
|
|
27
|
+
/**
|
|
28
|
+
* A `dosage` question is an unanswerable template until the API expands it under the same hash, as a radio.
|
|
29
|
+
* One that never expanded is dropped — nothing renders the type — and jumps aimed at it are cleared with it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function withExpandedDosage(questions: iQuestion[], expanded?: iQuestion | null): iQuestion[];
|
|
32
|
+
/**
|
|
33
|
+
* Whether the flow can open this question — for the `q` in the URL, the one hash nothing upstream vets.
|
|
34
|
+
* Plain membership, so hand it the visible set: a follow-up is asked inline and a carried answer is hidden,
|
|
35
|
+
* and neither has a screen of its own.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isRenderableQuestion(hash: string | null | undefined, questions: iQuestion[]): hash is string;
|
|
38
|
+
/**
|
|
39
|
+
* The BMI range a reading falls in. Ranges may share a bound (24-27 then 27-28), and the later one
|
|
40
|
+
* takes the shared value, so the patient's feedback and their routing agree on which range applies.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findBmiRange(options: iQuestion['options'] | undefined, bmi: number): iQuestion['options'][number] | undefined;
|
|
43
|
+
/** The BMI a stored answer carries (`height=…cm weight=…kg bmi=…`), or null when it has none. */
|
|
44
|
+
export declare function bmiFromAnswer(answer: string | string[] | null | undefined): number | null;
|
|
45
|
+
/**
|
|
46
|
+
* Determines which question to navigate to next. Throws when questionIndex is negative.
|
|
47
|
+
* Priority order:
|
|
48
|
+
* 1. nextQuestionHashOverride (set by inline follow-up logic)
|
|
49
|
+
* 2. the go-to on the answered option, or on the range a BMI answer falls in
|
|
50
|
+
* 3. consultation-level logic.next on the current question
|
|
51
|
+
* 4. the standard next question in sequence
|
|
52
|
+
*
|
|
53
|
+
* The final branch reads `.hash` off the next question directly (no optional
|
|
54
|
+
* chaining) so an out-of-bounds index throws here, at the source, rather than
|
|
55
|
+
* silently returning undefined and crashing a component further down.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveNextQuestionHash(questions: iQuestion[], questionIndex: number, nextQuestionHashOverride: string | null, currentQuestion: iQuestion | undefined, consultationHash: string, answer?: string | string[] | null): string;
|
|
58
|
+
/**
|
|
59
|
+
* Where a navigation target lands once hidden questions are taken into account:
|
|
60
|
+
* the target itself when visible, else the first visible question after both the
|
|
61
|
+
* target and the current question in consultation order, else null. A follow-up
|
|
62
|
+
* or unknown target continues after the current question.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveVisibleTarget(allQuestions: iQuestion[], visibleQuestions: iQuestion[], targetHash: string, currentHash?: string | null): string | null;
|
|
65
|
+
type Option = iQuestion['options'][0];
|
|
66
|
+
type OptionLogic = Option['logic'];
|
|
67
|
+
/**
|
|
68
|
+
* Whether an option is the current answer — matched by option hash (primary)
|
|
69
|
+
* or by label (backward-compatibility fallback).
|
|
70
|
+
*/
|
|
71
|
+
export declare function isOptionSelected(option: Option, answer: string | string[] | null | undefined, answerOptionHash: string | string[] | null | undefined): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Returns the logic entries for whichever options match the current answer.
|
|
74
|
+
* Handles both single-value (radio) and multi-value (checkbox) answers.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getMatchedOptionLogics(answer: string | string[] | null | undefined, answerOptionHash: string | string[] | null | undefined, options: iQuestion['options']): OptionLogic[];
|
|
77
|
+
/**
|
|
78
|
+
* Collects all inline follow-up question hashes from the active logics,
|
|
79
|
+
* de-duplicating so each follow-up appears once.
|
|
80
|
+
*/
|
|
81
|
+
export declare function getFollowUpHashes(logics: OptionLogic[]): string[];
|
|
82
|
+
/** Whether an option logic carries an exit condition. */
|
|
83
|
+
export declare function hasExitLogic(logic: OptionLogic | undefined): boolean;
|
|
84
|
+
export declare const FREE_TEXT_TYPES: string[];
|
|
85
|
+
/** Free text needs enough to be worth reading, so a stray character does not unlock Continue. */
|
|
86
|
+
export declare function isFreeTextTooShort(type: string, answer: string | string[] | null | undefined): boolean;
|
|
87
|
+
type AnswerMap = Record<string, string | string[]>;
|
|
88
|
+
/** Whether every follow-up in the group is answered, walking nested ones. `seen` guards a chain that loops back. */
|
|
89
|
+
export declare function areFollowUpsAnswered(followUpHashes: string[], questions: iQuestion[], answers: AnswerMap, optionHashes: AnswerMap, seen?: Set<string>): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Every top-level question, plus the follow-ups the selected options open, walking nested ones.
|
|
92
|
+
* Jumps (`logic.next`) are not followed, so a top-level question the run skipped still counts as reached.
|
|
93
|
+
*/
|
|
94
|
+
export declare function reachableQuestionHashes(questions: iQuestion[], answers: AnswerMap, optionHashes: AnswerMap): Set<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Decides whether the Continue button should be rendered:
|
|
97
|
+
* - there is an answer
|
|
98
|
+
* - no exit condition is triggered
|
|
99
|
+
* - no inline follow-up questions are pending
|
|
100
|
+
* - the question type doesn't auto-navigate (radio) or has its own submit (uploader)
|
|
101
|
+
* - date fields must be fully filled (length === 10 for DD-MM-YYYY)
|
|
102
|
+
* - the answer passes validation
|
|
103
|
+
*/
|
|
104
|
+
export declare function shouldShowButton(type: string, answer: string | string[] | null | undefined, exitQuestion: OptionLogic | undefined, followUpHashes: string[], hasValidationError: boolean | null | undefined): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Computes the new checkbox answer when a value is toggled.
|
|
107
|
+
* - If the value is already in the array it is removed.
|
|
108
|
+
* - If it is not, it is appended.
|
|
109
|
+
* - Returns null when the resulting array is empty (clears the answer).
|
|
110
|
+
*/
|
|
111
|
+
export declare function applyCheckboxToggle(currentAnswer: string | string[] | null | undefined, value: string): string[] | null;
|
|
112
|
+
/**
|
|
113
|
+
* The microcopy shown above the answer options, telling the patient how the answer
|
|
114
|
+
* behaves. Set by the answer type rather than authored per question, so every
|
|
115
|
+
* consultation reads the same way. Types not listed have no hint.
|
|
116
|
+
*/
|
|
117
|
+
export declare function inputHintFor(type: string): string | null;
|
|
118
|
+
/**
|
|
119
|
+
* CMS HTML with its non-breaking spaces made ordinary. Copy pasted into the editor can arrive
|
|
120
|
+
* with every space non-breaking, which leaves a line nowhere to wrap but mid-word.
|
|
121
|
+
*/
|
|
122
|
+
export declare function withBreakableSpaces(html: string | null | undefined): string;
|
|
123
|
+
/**
|
|
124
|
+
* The flags an answer raises, derived from the answer itself: those on the options the
|
|
125
|
+
* patient has picked, plus the `flags_unselected` of the options they left alone. A
|
|
126
|
+
* clinical flag sometimes turns on an absent answer, and an option can only flag itself
|
|
127
|
+
* when chosen. Deriving both sides from the selection means the stored flags always match
|
|
128
|
+
* the stored answer, rather than only the option that was clicked last.
|
|
129
|
+
*/
|
|
130
|
+
export declare function collectFlags(selectedLabels: string[], options: iQuestion['options'] | undefined): string[] | null;
|
|
131
|
+
/** Where "Review answer" returns to. Same-origin paths only, since the value arrives in the URL; null leaves the caller on its own path. */
|
|
132
|
+
export declare function resolveExitReturnUrl(returnTo: string | undefined | null, mainQuestionHash: string | undefined | null): string | null;
|
|
133
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ConsultationEvent, ConsultationType } from '../types';
|
|
2
|
+
import { type AnswerMap, type AnswerValue } from './answer';
|
|
3
|
+
type OptionLogic = iQuestion['options'][number]['logic'];
|
|
4
|
+
export interface ExitState {
|
|
5
|
+
questionHash: string;
|
|
6
|
+
title: string | null;
|
|
7
|
+
content: string | null;
|
|
8
|
+
}
|
|
9
|
+
export type Phase = 'question' | 'exit' | 'complete';
|
|
10
|
+
interface Options {
|
|
11
|
+
consultation: iConsultation;
|
|
12
|
+
consultationType: ConsultationType;
|
|
13
|
+
initialAnswers: AnswerMap;
|
|
14
|
+
/** False when a saved draft is reopened, so the start is not counted twice. */
|
|
15
|
+
isNewStart: boolean;
|
|
16
|
+
emit: (event: ConsultationEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function useConsultationFlow({ consultation, consultationType, initialAnswers, isNewStart, emit }: Options): {
|
|
19
|
+
allQuestions: iQuestion[];
|
|
20
|
+
questions: iQuestion[];
|
|
21
|
+
current: string;
|
|
22
|
+
questionIndex: number;
|
|
23
|
+
history: string[];
|
|
24
|
+
answers: AnswerMap;
|
|
25
|
+
optionHashes: AnswerMap;
|
|
26
|
+
phase: Phase;
|
|
27
|
+
exit: ExitState;
|
|
28
|
+
revision: number;
|
|
29
|
+
canGoBack: boolean;
|
|
30
|
+
answer: (question: iQuestion, value: AnswerValue, logic: OptionLogic | undefined, ownsProgression: boolean) => void;
|
|
31
|
+
nextQuestion: () => void;
|
|
32
|
+
back: () => void;
|
|
33
|
+
reviewExitAnswer: () => void;
|
|
34
|
+
restart: () => void;
|
|
35
|
+
};
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Environment, GetToken, TargetAdapter } from '../types';
|
|
2
|
+
/** Assessment answers stored on a diagnosis request, through NodeAPI with the Prescriber App user's token. */
|
|
3
|
+
export declare function diagnosisRequestTarget(environment: Environment, id: string, getToken: GetToken): TargetAdapter;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Environment, GetToken, Target, TargetAdapter } from '../types';
|
|
2
|
+
/** One adapter for each target type. To save answers in a new place, add a type here, not code in the host. */
|
|
3
|
+
export declare function createTargetAdapter(target: Target, environment: Environment, getToken: GetToken): TargetAdapter;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type Environment = 'development' | 'staging' | 'production';
|
|
2
|
+
/** An answer in Connect's format: option labels for choice types, text for the rest, `DD-MM-YYYY` for a date. */
|
|
3
|
+
export type Answer = string | string[];
|
|
4
|
+
export type Answers = Record<string, Answer>;
|
|
5
|
+
/** Where the answers are saved. With no target the package runs as a preview and saves nothing. */
|
|
6
|
+
export type Target = {
|
|
7
|
+
type: 'diagnosis-request';
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
export type GetToken = () => Promise<string | null> | string | null;
|
|
11
|
+
export type ConsultationType = 'treatment' | 'renew' | 'reorder' | 'review' | 'care' | (string & {});
|
|
12
|
+
/** Saved answers as a target returns them. */
|
|
13
|
+
export interface SavedAnswers {
|
|
14
|
+
answers: Answers;
|
|
15
|
+
status: 'empty' | 'draft' | 'submitted';
|
|
16
|
+
/** The consultation version the answers were given against, when the target knows it. */
|
|
17
|
+
consultationVersion: string | null;
|
|
18
|
+
submittedAt: string | null;
|
|
19
|
+
}
|
|
20
|
+
/** The calls the package makes for one target type. The adapter is the only code that knows the endpoints. */
|
|
21
|
+
export interface TargetAdapter {
|
|
22
|
+
load: () => Promise<SavedAnswers>;
|
|
23
|
+
saveDraft: (answers: Answers) => Promise<void>;
|
|
24
|
+
submit: (answers: Answers) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
interface ConsultationFields {
|
|
27
|
+
questionnaire_hash: string;
|
|
28
|
+
experiment_of: string | undefined;
|
|
29
|
+
consultation_type: ConsultationType;
|
|
30
|
+
}
|
|
31
|
+
interface QuestionFields {
|
|
32
|
+
question_hash: string;
|
|
33
|
+
consultation_hash: string;
|
|
34
|
+
is_skipped: boolean;
|
|
35
|
+
experiment_of: string | undefined;
|
|
36
|
+
consultation_type: ConsultationType;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* An analytics signal. The names and fields are the ones Connect sends, so a host can forward them as they are.
|
|
40
|
+
* The package does not send them anywhere itself.
|
|
41
|
+
*/
|
|
42
|
+
export type ConsultationEvent = {
|
|
43
|
+
name: 'consultation_started';
|
|
44
|
+
data: ConsultationFields;
|
|
45
|
+
} | {
|
|
46
|
+
name: 'consultation_viewed';
|
|
47
|
+
data: {
|
|
48
|
+
question_number: string;
|
|
49
|
+
question_number_of_total: string;
|
|
50
|
+
};
|
|
51
|
+
} | {
|
|
52
|
+
name: 'question_viewed';
|
|
53
|
+
data: QuestionFields;
|
|
54
|
+
} | {
|
|
55
|
+
name: 'question_answered';
|
|
56
|
+
data: QuestionFields;
|
|
57
|
+
} | {
|
|
58
|
+
name: 'consultation_completed';
|
|
59
|
+
data: ConsultationFields;
|
|
60
|
+
} | {
|
|
61
|
+
name: 'consultation_exited';
|
|
62
|
+
data: {
|
|
63
|
+
questionnaire_hash: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export type ConsultationEventName = ConsultationEvent['name'];
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AnswerMap, AnswerValue } from '../flow/answer';
|
|
2
|
+
type OptionLogic = iQuestion['options'][number]['logic'];
|
|
3
|
+
/** The types this version can fill in. The rest show a notice. */
|
|
4
|
+
export declare const FILL_TYPES: string[];
|
|
5
|
+
interface Props {
|
|
6
|
+
hash: string;
|
|
7
|
+
consultationHash: string;
|
|
8
|
+
questions: iQuestion[];
|
|
9
|
+
answers: AnswerMap;
|
|
10
|
+
optionHashes: AnswerMap;
|
|
11
|
+
onAnswer: (question: iQuestion, value: AnswerValue, logic: OptionLogic | undefined, ownsProgression: boolean) => void;
|
|
12
|
+
onContinue: () => void;
|
|
13
|
+
isMainQuestion?: boolean;
|
|
14
|
+
isLastFollowUp?: boolean;
|
|
15
|
+
precedingFollowUpsAnswered?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** In a preview a type this version cannot fill in can be skipped, so the rest of the flow stays reachable. */
|
|
18
|
+
allowSkipUnsupported?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const Question: ({ hash, consultationHash, questions, answers, optionHashes, onAnswer, onContinue, isMainQuestion, isLastFollowUp, precedingFollowUpsAnswered, disabled, allowSkipUnsupported }: Props) => import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Saved answers, one question after another. Every type shows its answer, including the ones that cannot be filled in here. */
|
|
2
|
+
import type { AnswerMap } from '../flow/answer';
|
|
3
|
+
interface Props {
|
|
4
|
+
questions: iQuestion[];
|
|
5
|
+
answers: AnswerMap;
|
|
6
|
+
submittedAt: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare const ReadOnlyAnswers: ({ questions, answers, submittedAt }: Props) => import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
/** Renders the children in a shadow root with the package CSS, so the host CSS and the package CSS do not touch. */
|
|
3
|
+
export declare const ShadowRoot: ({ children, style }: {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
style?: CSSProperties;
|
|
6
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
export declare const CircularProgress: ({ size, className }: {
|
|
4
|
+
size?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
}) => React.JSX.Element;
|
|
7
|
+
export declare const CheckCircleIcon: ({ className }: {
|
|
8
|
+
className?: string;
|
|
9
|
+
}) => React.JSX.Element;
|
|
10
|
+
export declare const EmptyCircleIcon: () => React.JSX.Element;
|
|
11
|
+
export declare const ArrowLeftIcon: () => React.JSX.Element;
|
|
12
|
+
interface ButtonProps extends React.ComponentProps<'button'> {
|
|
13
|
+
variant?: 'text' | 'contained' | 'outlined' | 'consultation';
|
|
14
|
+
color?: 'primary' | 'secondary' | 'success';
|
|
15
|
+
fullWidth?: boolean;
|
|
16
|
+
loading?: boolean;
|
|
17
|
+
startIcon?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
export declare const Button: ({ variant, color, fullWidth, children, className, startIcon, disabled, loading, type, ...rest }: ButtonProps) => React.JSX.Element;
|
|
20
|
+
export interface TextFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
21
|
+
error?: boolean;
|
|
22
|
+
label?: string;
|
|
23
|
+
helperText?: string;
|
|
24
|
+
multiline?: boolean;
|
|
25
|
+
rows?: number;
|
|
26
|
+
labelClassName?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const TextField: React.ForwardRefExoticComponent<TextFieldProps & React.RefAttributes<HTMLInputElement>>;
|
|
29
|
+
/** A masked `DD-MM-YYYY` text field, as in Connect. It needs no popover, so it works inside a shadow root. */
|
|
30
|
+
export declare const DatePicker: ({ onChange, ...props }: Omit<TextFieldProps, 'onChange'> & {
|
|
31
|
+
onChange: (value: string) => void;
|
|
32
|
+
}) => React.JSX.Element;
|
|
33
|
+
export declare const Accordion: ({ title, description }: {
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
}) => React.JSX.Element;
|
|
37
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evaro/consultation-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Evaro consultation flow as a React component: one question at a time, follow-ups, jumps and the knockout screen.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"private": false,
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/TotalAccessHealth/consultation.git",
|
|
13
|
+
"directory": "sdk"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.mjs",
|
|
17
|
+
"module": "./dist/index.mjs",
|
|
18
|
+
"types": "./dist/types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"import": "./dist/index.mjs",
|
|
23
|
+
"default": "./dist/index.mjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^19.0.0",
|
|
33
|
+
"react-dom": "^19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"prepublishOnly": "pnpm -C .. build:sdk && pnpm -C .. test:sdk"
|
|
37
|
+
}
|
|
38
|
+
}
|