@pie-element/mc-populated-blank 0.2.10 → 0.2.12
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 +0 -2
- package/dist/author/index.js +186 -152
- package/dist/author/index.js.map +1 -1
- package/dist/controller/defaults.d.ts +41 -73
- package/dist/controller/defaults.d.ts.map +1 -1
- package/dist/controller/index.d.ts +154 -10
- package/dist/controller/index.d.ts.map +1 -1
- package/dist/controller/index.js +29 -30
- package/dist/controller/index.js.map +1 -1
- package/dist/delivery/computeAudioMode.d.ts +7 -0
- package/dist/delivery/computeAudioMode.d.ts.map +1 -0
- package/dist/delivery/computeChoiceCorrectness.d.ts +18 -0
- package/dist/delivery/computeChoiceCorrectness.d.ts.map +1 -0
- package/dist/delivery/computeDisplayState.d.ts +47 -0
- package/dist/delivery/computeDisplayState.d.ts.map +1 -0
- package/dist/delivery/computeLayoutProfile.d.ts +18 -0
- package/dist/delivery/computeLayoutProfile.d.ts.map +1 -0
- package/dist/delivery/computeLayoutStyle.d.ts +27 -0
- package/dist/delivery/computeLayoutStyle.d.ts.map +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1591 -927
- package/dist/delivery/index.js.map +1 -1
- package/dist/delivery/variant-css-map.d.ts.map +1 -1
- package/dist/index.iife.d.ts +151 -10
- package/dist/index.iife.d.ts.map +1 -1
- package/dist/index.iife.js +233 -114
- package/dist/index.js +1591 -927
- package/dist/index.js.map +1 -1
- package/dist/print/index.js +194 -166
- package/dist/print/index.js.map +1 -1
- package/dist/shared/layoutLimits.d.ts +40 -0
- package/dist/shared/layoutLimits.d.ts.map +1 -0
- package/dist/shared/types.d.ts +82 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +5 -14
- package/src/author/Author.svelte +0 -41
- package/src/author/index.ts +0 -28
- package/src/controller/defaults.ts +0 -91
- package/src/controller/index.ts +0 -359
- package/src/delivery/McPopulatedBlank.svelte +0 -1246
- package/src/delivery/cqt-css/sel-r1-g-stem.css +0 -29
- package/src/delivery/cqt-css/sel-r1-gg-plus.css +0 -29
- package/src/delivery/cqt-css/sel-r1-ggplus.css +0 -34
- package/src/delivery/cqt-css/sel-r1-gplusggg.css +0 -20
- package/src/delivery/cqt-css/sel-r1-plusggg.css +0 -20
- package/src/delivery/cqt-css/sel-r1-s3.css +0 -91
- package/src/delivery/cqt-css/sel-vic.css +0 -38
- package/src/delivery/cqt-css/sr-vic.css +0 -14
- package/src/delivery/index.ts +0 -103
- package/src/delivery/variant-css-map.ts +0 -110
- package/src/index.iife.ts +0 -13
- package/src/index.ts +0 -1
- package/src/print/Print.svelte +0 -95
- package/src/print/index.ts +0 -18
- package/src/types/css-raw.d.ts +0 -4
|
@@ -1,43 +1,7 @@
|
|
|
1
|
+
import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';
|
|
2
|
+
export { DEFAULT_LAYOUT_LIMITS };
|
|
1
3
|
/** Token authors must include exactly once in `template` (HTML). */
|
|
2
4
|
export declare const BLANK_TOKEN = "{{blank}}";
|
|
3
|
-
/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */
|
|
4
|
-
export declare const DEFAULT_LAYOUT_LIMITS: {
|
|
5
|
-
readonly blankStandaloneWidthRem: 7;
|
|
6
|
-
readonly blankWideWidthRem: 10;
|
|
7
|
-
readonly blankUnderlineWidthPx: 2;
|
|
8
|
-
readonly blankUnderlineWideWidthPx: 4;
|
|
9
|
-
readonly horizontalChoiceWidthPx: 170;
|
|
10
|
-
readonly horizontalChoiceWidthVw: 30;
|
|
11
|
-
readonly horizontalChoiceTileMinHeightRem: 11;
|
|
12
|
-
readonly horizontalChoiceContentMinHeightRem: 7.5;
|
|
13
|
-
readonly selectedImageMaxHeightRem: 4;
|
|
14
|
-
readonly choiceImageMaxHeightRem: 5;
|
|
15
|
-
readonly listenButtonSizePx: 128;
|
|
16
|
-
readonly stimulusMinColumnPx: 210;
|
|
17
|
-
readonly textMinColumnPx: 260;
|
|
18
|
-
readonly legendMaxChars: 120;
|
|
19
|
-
readonly choiceGroupGapRem: 0.5;
|
|
20
|
-
readonly choiceRowGapRem: 0.5;
|
|
21
|
-
readonly toggleButtonGapRem: 0.5;
|
|
22
|
-
readonly horizontalChoiceRadioTopMarginRem: 0.5;
|
|
23
|
-
readonly audioBlankTemplateMarginTopRem: 0.8;
|
|
24
|
-
readonly audioBlankTemplateMarginBottomRem: 1.8;
|
|
25
|
-
readonly audioInstructionsMaxWidthPx: 875;
|
|
26
|
-
readonly narrowHorizontalChoiceMaxWidthPx: 230;
|
|
27
|
-
readonly stimulusGridColumnGapRem: 2;
|
|
28
|
-
readonly stimulusGridRowGapRem: 0.7;
|
|
29
|
-
readonly stimulusSentenceMarginTopRem: 0.2;
|
|
30
|
-
readonly stimulusChoicesMarginTopRem: 0.9;
|
|
31
|
-
readonly tokenGridColumnGapRem: 1.5;
|
|
32
|
-
readonly tokenGridRowGapRem: 0.8;
|
|
33
|
-
readonly tokenTemplateMarginTopRem: 0.6;
|
|
34
|
-
readonly tokenInlineTokenGapRem: 0.35;
|
|
35
|
-
readonly tokenChoicesMarginTopRem: 0.2;
|
|
36
|
-
readonly inlineGridColumnGapRem: 1.5;
|
|
37
|
-
readonly inlineGridRowGapRem: 0.65;
|
|
38
|
-
readonly inlineTemplateMarginTopRem: 0.45;
|
|
39
|
-
readonly inlineChoicesMarginTopRem: 0.25;
|
|
40
|
-
};
|
|
41
5
|
declare const _default: {
|
|
42
6
|
model: {
|
|
43
7
|
id: string;
|
|
@@ -49,41 +13,41 @@ declare const _default: {
|
|
|
49
13
|
choiceLayout: string;
|
|
50
14
|
layoutProfilePresets: {};
|
|
51
15
|
layoutLimits: {
|
|
52
|
-
blankStandaloneWidthRem:
|
|
53
|
-
blankWideWidthRem:
|
|
54
|
-
blankUnderlineWidthPx:
|
|
55
|
-
blankUnderlineWideWidthPx:
|
|
56
|
-
horizontalChoiceWidthPx:
|
|
57
|
-
horizontalChoiceWidthVw:
|
|
58
|
-
horizontalChoiceTileMinHeightRem:
|
|
59
|
-
horizontalChoiceContentMinHeightRem:
|
|
60
|
-
selectedImageMaxHeightRem:
|
|
61
|
-
choiceImageMaxHeightRem:
|
|
62
|
-
listenButtonSizePx:
|
|
63
|
-
stimulusMinColumnPx:
|
|
64
|
-
textMinColumnPx:
|
|
65
|
-
legendMaxChars:
|
|
66
|
-
choiceGroupGapRem:
|
|
67
|
-
choiceRowGapRem:
|
|
68
|
-
toggleButtonGapRem:
|
|
69
|
-
horizontalChoiceRadioTopMarginRem:
|
|
70
|
-
audioBlankTemplateMarginTopRem:
|
|
71
|
-
audioBlankTemplateMarginBottomRem:
|
|
72
|
-
audioInstructionsMaxWidthPx:
|
|
73
|
-
narrowHorizontalChoiceMaxWidthPx:
|
|
74
|
-
stimulusGridColumnGapRem:
|
|
75
|
-
stimulusGridRowGapRem:
|
|
76
|
-
stimulusSentenceMarginTopRem:
|
|
77
|
-
stimulusChoicesMarginTopRem:
|
|
78
|
-
tokenGridColumnGapRem:
|
|
79
|
-
tokenGridRowGapRem:
|
|
80
|
-
tokenTemplateMarginTopRem:
|
|
81
|
-
tokenInlineTokenGapRem:
|
|
82
|
-
tokenChoicesMarginTopRem:
|
|
83
|
-
inlineGridColumnGapRem:
|
|
84
|
-
inlineGridRowGapRem:
|
|
85
|
-
inlineTemplateMarginTopRem:
|
|
86
|
-
inlineChoicesMarginTopRem:
|
|
16
|
+
blankStandaloneWidthRem: number;
|
|
17
|
+
blankWideWidthRem: number;
|
|
18
|
+
blankUnderlineWidthPx: number;
|
|
19
|
+
blankUnderlineWideWidthPx: number;
|
|
20
|
+
horizontalChoiceWidthPx: number;
|
|
21
|
+
horizontalChoiceWidthVw: number;
|
|
22
|
+
horizontalChoiceTileMinHeightRem: number;
|
|
23
|
+
horizontalChoiceContentMinHeightRem: number;
|
|
24
|
+
selectedImageMaxHeightRem: number;
|
|
25
|
+
choiceImageMaxHeightRem: number;
|
|
26
|
+
listenButtonSizePx: number;
|
|
27
|
+
stimulusMinColumnPx: number;
|
|
28
|
+
textMinColumnPx: number;
|
|
29
|
+
legendMaxChars: number;
|
|
30
|
+
choiceGroupGapRem: number;
|
|
31
|
+
choiceRowGapRem: number;
|
|
32
|
+
toggleButtonGapRem: number;
|
|
33
|
+
horizontalChoiceRadioTopMarginRem: number;
|
|
34
|
+
audioBlankTemplateMarginTopRem: number;
|
|
35
|
+
audioBlankTemplateMarginBottomRem: number;
|
|
36
|
+
audioInstructionsMaxWidthPx: number;
|
|
37
|
+
narrowHorizontalChoiceMaxWidthPx: number;
|
|
38
|
+
stimulusGridColumnGapRem: number;
|
|
39
|
+
stimulusGridRowGapRem: number;
|
|
40
|
+
stimulusSentenceMarginTopRem: number;
|
|
41
|
+
stimulusChoicesMarginTopRem: number;
|
|
42
|
+
tokenGridColumnGapRem: number;
|
|
43
|
+
tokenGridRowGapRem: number;
|
|
44
|
+
tokenTemplateMarginTopRem: number;
|
|
45
|
+
tokenInlineTokenGapRem: number;
|
|
46
|
+
tokenChoicesMarginTopRem: number;
|
|
47
|
+
inlineGridColumnGapRem: number;
|
|
48
|
+
inlineGridRowGapRem: number;
|
|
49
|
+
inlineTemplateMarginTopRem: number;
|
|
50
|
+
inlineChoicesMarginTopRem: number;
|
|
87
51
|
};
|
|
88
52
|
audioButtonSkin: null;
|
|
89
53
|
audioButtonSkinsByLocale: {};
|
|
@@ -97,6 +61,10 @@ declare const _default: {
|
|
|
97
61
|
transcriptLabel: string;
|
|
98
62
|
listenLabelEn: string;
|
|
99
63
|
listenLabelEs: string;
|
|
64
|
+
listenSilentAlt: string;
|
|
65
|
+
listenPlayingAlt: string;
|
|
66
|
+
listenSilentAltEs: string;
|
|
67
|
+
listenPlayingAltEs: string;
|
|
100
68
|
};
|
|
101
69
|
sentenceHtml: string;
|
|
102
70
|
template: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/controller/defaults.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/controller/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,qBAAqB,EAAE,CAAC;AAEjC,oEAAoE;AACpE,eAAO,MAAM,WAAW,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEvC,wBAqDE"}
|
|
@@ -1,12 +1,156 @@
|
|
|
1
|
+
import type { McpbChoice, McpbQuestion, McpbSession, McpbEnv, McpbCorrectness } from '../shared/types';
|
|
1
2
|
export declare function countBlankTokens(template: string): number;
|
|
2
|
-
export declare const getCorrectness: (question:
|
|
3
|
-
export declare const getPartialScore: (_question:
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
export declare const getCorrectness: (question: McpbQuestion, session: McpbSession) => McpbCorrectness;
|
|
4
|
+
export declare const getPartialScore: (_question: McpbQuestion, session: McpbSession) => 0 | 1;
|
|
5
|
+
export declare const outcome: (question: McpbQuestion, session: McpbSession, env: McpbEnv) => Promise<unknown>;
|
|
6
|
+
export declare const createDefaultModel: (model?: McpbQuestion) => {
|
|
7
|
+
layoutLimits: {
|
|
8
|
+
blankStandaloneWidthRem: number;
|
|
9
|
+
blankWideWidthRem: number;
|
|
10
|
+
blankUnderlineWidthPx: number;
|
|
11
|
+
blankUnderlineWideWidthPx: number;
|
|
12
|
+
horizontalChoiceWidthPx: number;
|
|
13
|
+
horizontalChoiceWidthVw: number;
|
|
14
|
+
horizontalChoiceTileMinHeightRem: number;
|
|
15
|
+
horizontalChoiceContentMinHeightRem: number;
|
|
16
|
+
selectedImageMaxHeightRem: number;
|
|
17
|
+
choiceImageMaxHeightRem: number;
|
|
18
|
+
listenButtonSizePx: number;
|
|
19
|
+
stimulusMinColumnPx: number;
|
|
20
|
+
textMinColumnPx: number;
|
|
21
|
+
legendMaxChars: number;
|
|
22
|
+
choiceGroupGapRem: number;
|
|
23
|
+
choiceRowGapRem: number;
|
|
24
|
+
toggleButtonGapRem: number;
|
|
25
|
+
horizontalChoiceRadioTopMarginRem: number;
|
|
26
|
+
audioBlankTemplateMarginTopRem: number;
|
|
27
|
+
audioBlankTemplateMarginBottomRem: number;
|
|
28
|
+
audioInstructionsMaxWidthPx: number;
|
|
29
|
+
narrowHorizontalChoiceMaxWidthPx: number;
|
|
30
|
+
stimulusGridColumnGapRem: number;
|
|
31
|
+
stimulusGridRowGapRem: number;
|
|
32
|
+
stimulusSentenceMarginTopRem: number;
|
|
33
|
+
stimulusChoicesMarginTopRem: number;
|
|
34
|
+
tokenGridColumnGapRem: number;
|
|
35
|
+
tokenGridRowGapRem: number;
|
|
36
|
+
tokenTemplateMarginTopRem: number;
|
|
37
|
+
tokenInlineTokenGapRem: number;
|
|
38
|
+
tokenChoicesMarginTopRem: number;
|
|
39
|
+
inlineGridColumnGapRem: number;
|
|
40
|
+
inlineGridRowGapRem: number;
|
|
41
|
+
inlineTemplateMarginTopRem: number;
|
|
42
|
+
inlineChoicesMarginTopRem: number;
|
|
43
|
+
};
|
|
44
|
+
id: string;
|
|
45
|
+
element: string;
|
|
46
|
+
prompt: string;
|
|
47
|
+
promptEnabled: boolean;
|
|
48
|
+
interactionMode: import("../shared/types").McpbInteractionMode;
|
|
49
|
+
layoutProfile: string;
|
|
50
|
+
choiceLayout: string;
|
|
51
|
+
layoutProfilePresets: {};
|
|
52
|
+
audioButtonSkin: import("../shared/types").McpbAudioButtonSkin | null;
|
|
53
|
+
audioButtonSkinsByLocale: {};
|
|
54
|
+
uiText: import("../shared/types").McpbUiText;
|
|
55
|
+
sentenceHtml: string;
|
|
56
|
+
template: string;
|
|
57
|
+
choiceMode: import("../shared/types").McpbChoiceMode;
|
|
58
|
+
choices: McpbChoice[] | {
|
|
59
|
+
id: string;
|
|
60
|
+
labelHtml: string;
|
|
61
|
+
}[];
|
|
62
|
+
correctChoiceId: string;
|
|
63
|
+
hasAudio: boolean;
|
|
64
|
+
autoplayAudioEnabled: boolean;
|
|
65
|
+
completeAudioEnabled: boolean;
|
|
66
|
+
audioUrl: string;
|
|
67
|
+
audioTranscript: string;
|
|
68
|
+
showVisibleTranscript: boolean;
|
|
69
|
+
useFeatureButtonAudio?: boolean;
|
|
70
|
+
locale: string;
|
|
71
|
+
shuffle?: boolean;
|
|
72
|
+
lockChoiceOrder?: boolean;
|
|
73
|
+
teacherInstructions: string;
|
|
74
|
+
teacherInstructionsEnabled: boolean;
|
|
75
|
+
customType?: string;
|
|
76
|
+
};
|
|
77
|
+
export declare const normalize: (question?: McpbQuestion) => {
|
|
78
|
+
layoutLimits: {
|
|
79
|
+
blankStandaloneWidthRem: number;
|
|
80
|
+
blankWideWidthRem: number;
|
|
81
|
+
blankUnderlineWidthPx: number;
|
|
82
|
+
blankUnderlineWideWidthPx: number;
|
|
83
|
+
horizontalChoiceWidthPx: number;
|
|
84
|
+
horizontalChoiceWidthVw: number;
|
|
85
|
+
horizontalChoiceTileMinHeightRem: number;
|
|
86
|
+
horizontalChoiceContentMinHeightRem: number;
|
|
87
|
+
selectedImageMaxHeightRem: number;
|
|
88
|
+
choiceImageMaxHeightRem: number;
|
|
89
|
+
listenButtonSizePx: number;
|
|
90
|
+
stimulusMinColumnPx: number;
|
|
91
|
+
textMinColumnPx: number;
|
|
92
|
+
legendMaxChars: number;
|
|
93
|
+
choiceGroupGapRem: number;
|
|
94
|
+
choiceRowGapRem: number;
|
|
95
|
+
toggleButtonGapRem: number;
|
|
96
|
+
horizontalChoiceRadioTopMarginRem: number;
|
|
97
|
+
audioBlankTemplateMarginTopRem: number;
|
|
98
|
+
audioBlankTemplateMarginBottomRem: number;
|
|
99
|
+
audioInstructionsMaxWidthPx: number;
|
|
100
|
+
narrowHorizontalChoiceMaxWidthPx: number;
|
|
101
|
+
stimulusGridColumnGapRem: number;
|
|
102
|
+
stimulusGridRowGapRem: number;
|
|
103
|
+
stimulusSentenceMarginTopRem: number;
|
|
104
|
+
stimulusChoicesMarginTopRem: number;
|
|
105
|
+
tokenGridColumnGapRem: number;
|
|
106
|
+
tokenGridRowGapRem: number;
|
|
107
|
+
tokenTemplateMarginTopRem: number;
|
|
108
|
+
tokenInlineTokenGapRem: number;
|
|
109
|
+
tokenChoicesMarginTopRem: number;
|
|
110
|
+
inlineGridColumnGapRem: number;
|
|
111
|
+
inlineGridRowGapRem: number;
|
|
112
|
+
inlineTemplateMarginTopRem: number;
|
|
113
|
+
inlineChoicesMarginTopRem: number;
|
|
114
|
+
};
|
|
115
|
+
id: string;
|
|
116
|
+
element: string;
|
|
117
|
+
prompt: string;
|
|
118
|
+
promptEnabled: boolean;
|
|
119
|
+
interactionMode: import("../shared/types").McpbInteractionMode;
|
|
120
|
+
layoutProfile: string;
|
|
121
|
+
choiceLayout: string;
|
|
122
|
+
layoutProfilePresets: {};
|
|
123
|
+
audioButtonSkin: import("../shared/types").McpbAudioButtonSkin | null;
|
|
124
|
+
audioButtonSkinsByLocale: {};
|
|
125
|
+
uiText: import("../shared/types").McpbUiText;
|
|
126
|
+
sentenceHtml: string;
|
|
127
|
+
template: string;
|
|
128
|
+
choiceMode: import("../shared/types").McpbChoiceMode;
|
|
129
|
+
choices: McpbChoice[] | {
|
|
130
|
+
id: string;
|
|
131
|
+
labelHtml: string;
|
|
132
|
+
}[];
|
|
133
|
+
correctChoiceId: string;
|
|
134
|
+
hasAudio: boolean;
|
|
135
|
+
autoplayAudioEnabled: boolean;
|
|
136
|
+
completeAudioEnabled: boolean;
|
|
137
|
+
audioUrl: string;
|
|
138
|
+
audioTranscript: string;
|
|
139
|
+
showVisibleTranscript: boolean;
|
|
140
|
+
useFeatureButtonAudio?: boolean;
|
|
141
|
+
locale: string;
|
|
142
|
+
shuffle?: boolean;
|
|
143
|
+
lockChoiceOrder?: boolean;
|
|
144
|
+
teacherInstructions: string;
|
|
145
|
+
teacherInstructionsEnabled: boolean;
|
|
146
|
+
customType?: string;
|
|
147
|
+
};
|
|
148
|
+
export declare const normalizeSession: (s: McpbSession) => McpbSession;
|
|
149
|
+
type UpdateSessionFn = (id: string, element: string, data: {
|
|
150
|
+
shuffledValues: string[];
|
|
151
|
+
}) => Promise<void>;
|
|
152
|
+
export declare const model: (question: McpbQuestion, session: McpbSession | null, env: McpbEnv | null, updateSession?: UpdateSessionFn) => Promise<Record<string, unknown>>;
|
|
153
|
+
export declare const createCorrectResponseSession: (question: McpbQuestion, env: McpbEnv) => Promise<unknown>;
|
|
154
|
+
export declare const validate: (question?: McpbQuestion, _config?: Record<string, unknown>) => Record<string, string>;
|
|
155
|
+
export {};
|
|
12
156
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controller/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controller/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,OAAO,EACP,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAQzB,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,eAAO,MAAM,cAAc,GAAI,UAAU,YAAY,EAAE,SAAS,WAAW,KAAG,eAS7E,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,WAAW,YAAY,EAAE,SAAS,WAAW,UAK5E,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,UAAU,YAAY,EAAE,SAAS,WAAW,EAAE,KAAK,OAAO,qBA+B9E,CAAC;AAEL,eAAO,MAAM,kBAAkB,GAAI,QAAO,YAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOzD,CAAC;AAEH,eAAO,MAAM,SAAS,GAAI,WAAU,YAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAiC,CAAC;AAEvF,eAAO,MAAM,gBAAgB,GAAI,GAAG,WAAW,KAAG,WAAyB,CAAC;AA4C5E,KAAK,eAAe,GAAG,CACrB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,KAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;AAkCnB,eAAO,MAAM,KAAK,GAChB,UAAU,YAAY,EACtB,SAAS,WAAW,GAAG,IAAI,EAC3B,KAAK,OAAO,GAAG,IAAI,EACnB,gBAAgB,eAAe,qCAmEhC,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,UAAU,YAAY,EAAE,KAAK,OAAO,qBAYhF,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,WAAU,YAAiB,EAAE,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,2BAuH1F,CAAC"}
|
package/dist/controller/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
/** Token authors must include exactly once in `template` (HTML). */
|
|
3
|
-
var BLANK_TOKEN = "{{blank}}";
|
|
1
|
+
//#region src/shared/layoutLimits.ts
|
|
4
2
|
/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */
|
|
5
3
|
var DEFAULT_LAYOUT_LIMITS = {
|
|
6
4
|
blankStandaloneWidthRem: 7,
|
|
@@ -39,6 +37,10 @@ var DEFAULT_LAYOUT_LIMITS = {
|
|
|
39
37
|
inlineTemplateMarginTopRem: .45,
|
|
40
38
|
inlineChoicesMarginTopRem: .25
|
|
41
39
|
};
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/controller/defaults.ts
|
|
42
|
+
/** Token authors must include exactly once in `template` (HTML). */
|
|
43
|
+
var BLANK_TOKEN = "{{blank}}";
|
|
42
44
|
var defaults_default = {
|
|
43
45
|
model: {
|
|
44
46
|
id: "1",
|
|
@@ -54,14 +56,18 @@ var defaults_default = {
|
|
|
54
56
|
audioButtonSkinsByLocale: {},
|
|
55
57
|
uiText: {
|
|
56
58
|
answerChoices: "Answer choices",
|
|
57
|
-
selectedAnswerInSentence: "
|
|
59
|
+
selectedAnswerInSentence: "blank",
|
|
58
60
|
showCorrectAnswer: "Show correct answer",
|
|
59
61
|
hideCorrectAnswer: "Hide correct answer",
|
|
60
62
|
clickToEnableAutoplay: "Click to enable audio autoplay",
|
|
61
63
|
audioResourceUnavailable: "Audio is enabled but no playable audio URL is configured.",
|
|
62
64
|
transcriptLabel: "Transcript",
|
|
63
65
|
listenLabelEn: "Listen",
|
|
64
|
-
listenLabelEs: "Escuchar"
|
|
66
|
+
listenLabelEs: "Escuchar",
|
|
67
|
+
listenSilentAlt: "Repeat instructions",
|
|
68
|
+
listenPlayingAlt: "Instructions are playing",
|
|
69
|
+
listenSilentAltEs: "Escuchar. Repetir las instrucciones.",
|
|
70
|
+
listenPlayingAltEs: "Escuchar. Estas son las instrucciones."
|
|
65
71
|
},
|
|
66
72
|
sentenceHtml: "",
|
|
67
73
|
template: `<p>The answer is ${BLANK_TOKEN}.</p>`,
|
|
@@ -104,7 +110,7 @@ function countBlankTokens(template) {
|
|
|
104
110
|
return (template.match(/\{\{blank\}\}/g) || []).length;
|
|
105
111
|
}
|
|
106
112
|
var getCorrectness = (question, session) => {
|
|
107
|
-
if (!session
|
|
113
|
+
if (!session?.choiceId) return "unanswered";
|
|
108
114
|
const correct = question?.correctChoiceId || "";
|
|
109
115
|
if (session.choiceId === correct) return "correct";
|
|
110
116
|
return "incorrect";
|
|
@@ -113,13 +119,6 @@ var getPartialScore = (_question, session) => {
|
|
|
113
119
|
if (!session || isEmptyObject(session) || !session.choiceId) return 0;
|
|
114
120
|
return 1;
|
|
115
121
|
};
|
|
116
|
-
var isComplete = (question, session, audioComplete = false) => {
|
|
117
|
-
if (!session || !session.choiceId) return false;
|
|
118
|
-
const autoplayAudioEnabled = !!question?.autoplayAudioEnabled;
|
|
119
|
-
const completeAudioEnabled = !!question?.completeAudioEnabled;
|
|
120
|
-
if (autoplayAudioEnabled && completeAudioEnabled && !!question?.hasAudio && !audioComplete) return false;
|
|
121
|
-
return true;
|
|
122
|
-
};
|
|
123
122
|
var outcome = (question, session, env) => new Promise((resolve) => {
|
|
124
123
|
if (!session || isEmptyObject(session)) {
|
|
125
124
|
resolve({
|
|
@@ -177,7 +176,7 @@ function shuffleArray(items) {
|
|
|
177
176
|
}
|
|
178
177
|
var getStoredShuffle = (session) => Array.isArray(session?.data?.shuffledValues) ? session.data.shuffledValues : Array.isArray(session?.shuffledValues) ? session.shuffledValues : [];
|
|
179
178
|
var applyShuffledValues = (choices, shuffledValues, choiceKey) => {
|
|
180
|
-
const orderedChoices = shuffledValues.map((value) => choices.find((choice) => choice?.[choiceKey] === value)).filter(
|
|
179
|
+
const orderedChoices = shuffledValues.map((value) => choices.find((choice) => choice?.[choiceKey] === value)).filter((c) => !!c);
|
|
181
180
|
if (orderedChoices.length === choices.length) return orderedChoices;
|
|
182
181
|
const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));
|
|
183
182
|
const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));
|
|
@@ -197,10 +196,10 @@ var getOrderedChoices = async (question, session, env, updateSession) => {
|
|
|
197
196
|
return shuffledChoices;
|
|
198
197
|
};
|
|
199
198
|
var model = async (question, session, env, updateSession) => {
|
|
200
|
-
|
|
199
|
+
const safeSession = session || {};
|
|
201
200
|
const safeEnv = env || {};
|
|
202
201
|
const normalizedQuestion = normalize(question);
|
|
203
|
-
const choices = await getOrderedChoices(normalizedQuestion,
|
|
202
|
+
const choices = await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession);
|
|
204
203
|
const out = {
|
|
205
204
|
prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,
|
|
206
205
|
interactionMode: normalizedQuestion.interactionMode || "populate_blank",
|
|
@@ -228,7 +227,7 @@ var model = async (question, session, env, updateSession) => {
|
|
|
228
227
|
mode: safeEnv.mode
|
|
229
228
|
};
|
|
230
229
|
if (safeEnv.mode === "evaluate") {
|
|
231
|
-
const correctness = getCorrectness(normalizedQuestion,
|
|
230
|
+
const correctness = getCorrectness(normalizedQuestion, safeSession);
|
|
232
231
|
out.correctness = correctness;
|
|
233
232
|
out.responseCorrect = correctness === "correct";
|
|
234
233
|
out.correctChoiceId = normalizedQuestion.correctChoiceId;
|
|
@@ -247,23 +246,23 @@ var createCorrectResponseSession = (question, env) => {
|
|
|
247
246
|
else resolve(null);
|
|
248
247
|
});
|
|
249
248
|
};
|
|
250
|
-
var validate = (
|
|
249
|
+
var validate = (question = {}, _config = {}) => {
|
|
251
250
|
const errors = {};
|
|
252
|
-
if (
|
|
253
|
-
const p =
|
|
251
|
+
if (question.promptEnabled) {
|
|
252
|
+
const p = question.prompt?.trim() || "";
|
|
254
253
|
if (!p || p === "<p></p>") errors.prompt = "Prompt is required when prompt is enabled";
|
|
255
254
|
}
|
|
256
|
-
const interactionMode =
|
|
257
|
-
const n = countBlankTokens(
|
|
255
|
+
const interactionMode = question.interactionMode || "populate_blank";
|
|
256
|
+
const n = countBlankTokens(question.template || "");
|
|
258
257
|
if (interactionMode === "audio_mc_only") {
|
|
259
258
|
if (n > 0) errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;
|
|
260
259
|
} else if (interactionMode === "populate_blank") {
|
|
261
260
|
if (n === 0) errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;
|
|
262
261
|
else if (n > 1) errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;
|
|
263
262
|
} else errors.interactionMode = "Unknown interaction mode";
|
|
264
|
-
const choices = Array.isArray(
|
|
263
|
+
const choices = Array.isArray(question.choices) ? question.choices : [];
|
|
265
264
|
if (choices.length < 2) errors.choices = "At least two choices are required";
|
|
266
|
-
const mode =
|
|
265
|
+
const mode = question.choiceMode || "text";
|
|
267
266
|
for (let i = 0; i < choices.length; i++) {
|
|
268
267
|
const c = choices[i];
|
|
269
268
|
if (!c?.id) {
|
|
@@ -287,12 +286,12 @@ var validate = (model = {}, _config = {}) => {
|
|
|
287
286
|
}
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
|
-
const correct =
|
|
289
|
+
const correct = question.correctChoiceId;
|
|
291
290
|
if (!correct || !choices.some((c) => c.id === correct)) errors.correctChoiceId = "Correct choice must match one of the choice ids";
|
|
292
|
-
if (
|
|
293
|
-
if (!!!
|
|
291
|
+
if (question.hasAudio) {
|
|
292
|
+
if (!!!question.audioUrl?.trim()) errors.audioUrl = "A playable audio URL is required when audio is enabled";
|
|
294
293
|
}
|
|
295
|
-
const limits =
|
|
294
|
+
const limits = question?.layoutLimits;
|
|
296
295
|
if (limits && typeof limits === "object") for (const key of [
|
|
297
296
|
"blankStandaloneWidthRem",
|
|
298
297
|
"blankWideWidthRem",
|
|
@@ -330,7 +329,7 @@ var validate = (model = {}, _config = {}) => {
|
|
|
330
329
|
"inlineTemplateMarginTopRem",
|
|
331
330
|
"inlineChoicesMarginTopRem"
|
|
332
331
|
]) {
|
|
333
|
-
if (limits[key] === void 0 || limits[key] === null
|
|
332
|
+
if (limits[key] === void 0 || limits[key] === null) continue;
|
|
334
333
|
const value = Number(limits[key]);
|
|
335
334
|
if (!Number.isFinite(value) || value <= 0) {
|
|
336
335
|
errors.layoutLimits = `${key} must be a positive number when provided`;
|
|
@@ -340,6 +339,6 @@ var validate = (model = {}, _config = {}) => {
|
|
|
340
339
|
return errors;
|
|
341
340
|
};
|
|
342
341
|
//#endregion
|
|
343
|
-
export { countBlankTokens, createCorrectResponseSession, createDefaultModel, getCorrectness, getPartialScore,
|
|
342
|
+
export { countBlankTokens, createCorrectResponseSession, createDefaultModel, getCorrectness, getPartialScore, model, normalize, normalizeSession, outcome, validate };
|
|
344
343
|
|
|
345
344
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/controller/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */\nexport const DEFAULT_LAYOUT_LIMITS = {\n blankStandaloneWidthRem: 7,\n blankWideWidthRem: 10,\n blankUnderlineWidthPx: 2,\n blankUnderlineWideWidthPx: 4,\n horizontalChoiceWidthPx: 170,\n horizontalChoiceWidthVw: 30,\n horizontalChoiceTileMinHeightRem: 11,\n horizontalChoiceContentMinHeightRem: 7.5,\n selectedImageMaxHeightRem: 4,\n choiceImageMaxHeightRem: 5,\n listenButtonSizePx: 128,\n stimulusMinColumnPx: 210,\n textMinColumnPx: 260,\n legendMaxChars: 120,\n choiceGroupGapRem: 0.5,\n choiceRowGapRem: 0.5,\n toggleButtonGapRem: 0.5,\n horizontalChoiceRadioTopMarginRem: 0.5,\n audioBlankTemplateMarginTopRem: 0.8,\n audioBlankTemplateMarginBottomRem: 1.8,\n audioInstructionsMaxWidthPx: 875,\n narrowHorizontalChoiceMaxWidthPx: 230,\n stimulusGridColumnGapRem: 2,\n stimulusGridRowGapRem: 0.7,\n stimulusSentenceMarginTopRem: 0.2,\n stimulusChoicesMarginTopRem: 0.9,\n tokenGridColumnGapRem: 1.5,\n tokenGridRowGapRem: 0.8,\n tokenTemplateMarginTopRem: 0.6,\n tokenInlineTokenGapRem: 0.35,\n tokenChoicesMarginTopRem: 0.2,\n inlineGridColumnGapRem: 1.5,\n inlineGridRowGapRem: 0.65,\n inlineTemplateMarginTopRem: 0.45,\n inlineChoicesMarginTopRem: 0.25,\n} as const;\n\nexport default {\n model: {\n id: '1',\n element: 'mc-populated-blank',\n prompt: '<p><strong>Demo prompt</strong></p>',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n layoutProfilePresets: {},\n layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin: null,\n audioButtonSkinsByLocale: {},\n uiText: {\n answerChoices: 'Answer choices',\n selectedAnswerInSentence: 'Selected answer in sentence',\n showCorrectAnswer: 'Show correct answer',\n hideCorrectAnswer: 'Hide correct answer',\n clickToEnableAutoplay: 'Click to enable audio autoplay',\n audioResourceUnavailable: 'Audio is enabled but no playable audio URL is configured.',\n transcriptLabel: 'Transcript',\n listenLabelEn: 'Listen',\n listenLabelEs: 'Escuchar',\n },\n sentenceHtml: '',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choiceMode: 'text' as const,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n showVisibleTranscript: false,\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: false,\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import defaults, { BLANK_TOKEN, DEFAULT_LAYOUT_LIMITS } from './defaults';\n\nconst isEmptyObject = (value: unknown): boolean =>\n !!value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0;\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: any, session: any) => {\n if (!session || !session.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\nexport const getPartialScore = (_question: any, session: any) => {\n if (!session || isEmptyObject(session) || !session.choiceId) {\n return 0;\n }\n return 1;\n};\n\nexport const isComplete = (question: any, session: any, audioComplete = false) => {\n if (!session || !session.choiceId) {\n return false;\n }\n const autoplayAudioEnabled = !!question?.autoplayAudioEnabled;\n const completeAudioEnabled = !!question?.completeAudioEnabled;\n const requiresAudioCompletion =\n autoplayAudioEnabled && completeAudioEnabled && !!question?.hasAudio;\n if (requiresAudioCompletion && !audioComplete) {\n return false;\n }\n return true;\n};\n\nexport const outcome = (question: any, session: any, env: any) =>\n new Promise((resolve) => {\n if (!session || isEmptyObject(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const correctness = getCorrectness(question, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = correctness === 'correct' ? 1 : 0;\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${question?.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nexport const createDefaultModel = (model: any = {}) => ({\n ...defaults.model,\n ...model,\n layoutLimits: {\n ...DEFAULT_LAYOUT_LIMITS,\n ...(model?.layoutLimits || {}),\n },\n});\n\nexport const normalize = (question: any = {}) => createDefaultModel(question);\n\nexport const normalizeSession = (s: any) => ({ ...s });\n\nconst shouldShuffleChoices = (question: any) => !!question?.shuffle;\n\nconst shouldLockChoices = (question: any, env: any) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nfunction shuffleArray<T>(items: T[]): T[] {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n}\n\nconst getStoredShuffle = (session: any): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (choices: any[], shuffledValues: string[], choiceKey: string) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter(Boolean);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice: any) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\nconst getOrderedChoices = async (question: any, session: any, env: any, updateSession?: any) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || !shouldShuffleChoices(question)) {\n return choices;\n }\n\n if (shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (question: any, session: any, env: any, updateSession?: any) => {\n session = session || {};\n const safeEnv = env || {};\n const normalizedQuestion = normalize(question);\n const choices = await getOrderedChoices(normalizedQuestion, session, safeEnv, updateSession);\n\n const out: any = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n layoutProfilePresets:\n normalizedQuestion.layoutProfilePresets &&\n typeof normalizedQuestion.layoutProfilePresets === 'object'\n ? normalizedQuestion.layoutProfilePresets\n : {},\n layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin:\n normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'\n ? normalizedQuestion.audioButtonSkin\n : null,\n audioButtonSkinsByLocale:\n normalizedQuestion.audioButtonSkinsByLocale &&\n typeof normalizedQuestion.audioButtonSkinsByLocale === 'object'\n ? normalizedQuestion.audioButtonSkinsByLocale\n : {},\n uiText:\n normalizedQuestion.uiText && typeof normalizedQuestion.uiText === 'object'\n ? normalizedQuestion.uiText\n : {},\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n showVisibleTranscript: !!normalizedQuestion.showVisibleTranscript,\n customType: normalizedQuestion.customType || '',\n useFeatureButtonAudio:\n typeof normalizedQuestion.useFeatureButtonAudio === 'boolean'\n ? normalizedQuestion.useFeatureButtonAudio\n : undefined,\n locale: normalizedQuestion.locale || '',\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n };\n\n if (safeEnv.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, session);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: any, env: any) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n resolve({\n id: '1',\n element: 'mc-populated-blank',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\nexport const validate = (model: any = {}, _config: any = {}) => {\n const errors: any = {};\n\n if (model.promptEnabled) {\n const p = model.prompt?.trim() || '';\n if (!p || p === '<p></p>') {\n errors.prompt = 'Prompt is required when prompt is enabled';\n }\n }\n\n const interactionMode = model.interactionMode || 'populate_blank';\n const template = model.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(model.choices) ? model.choices : [];\n if (choices.length < 2) {\n errors.choices = 'At least two choices are required';\n }\n\n const mode = model.choiceMode || 'text';\n for (let i = 0; i < choices.length; i++) {\n const c = choices[i];\n if (!c?.id) {\n errors.choices = `Choice ${i + 1} is missing an id`;\n break;\n }\n if (mode === 'text') {\n const lbl = (c.labelHtml || '').trim();\n if (!lbl || lbl === '<p></p>') {\n errors.choices = `Choice ${i + 1} needs label text`;\n break;\n }\n } else {\n if (!c.imageUrl?.trim()) {\n errors.choices = `Choice ${i + 1} needs an image URL`;\n break;\n }\n if (!c.imageAlt?.trim()) {\n errors.choices = `Choice ${i + 1} needs image alt text`;\n break;\n }\n }\n }\n\n const correct = model.correctChoiceId;\n if (!correct || !choices.some((c: any) => c.id === correct)) {\n errors.correctChoiceId = 'Correct choice must match one of the choice ids';\n }\n\n if (model.hasAudio) {\n const hasAudioUrl = !!model.audioUrl?.trim();\n if (!hasAudioUrl) {\n errors.audioUrl = 'A playable audio URL is required when audio is enabled';\n }\n }\n\n const limits = model?.layoutLimits;\n if (limits && typeof limits === 'object') {\n const numericLimitKeys = [\n 'blankStandaloneWidthRem',\n 'blankWideWidthRem',\n 'blankUnderlineWidthPx',\n 'blankUnderlineWideWidthPx',\n 'horizontalChoiceWidthPx',\n 'horizontalChoiceWidthVw',\n 'horizontalChoiceTileMinHeightRem',\n 'horizontalChoiceContentMinHeightRem',\n 'selectedImageMaxHeightRem',\n 'choiceImageMaxHeightRem',\n 'listenButtonSizePx',\n 'stimulusMinColumnPx',\n 'textMinColumnPx',\n 'legendMaxChars',\n 'choiceGroupGapRem',\n 'choiceRowGapRem',\n 'toggleButtonGapRem',\n 'horizontalChoiceRadioTopMarginRem',\n 'audioBlankTemplateMarginTopRem',\n 'audioBlankTemplateMarginBottomRem',\n 'audioInstructionsMaxWidthPx',\n 'narrowHorizontalChoiceMaxWidthPx',\n 'stimulusGridColumnGapRem',\n 'stimulusGridRowGapRem',\n 'stimulusSentenceMarginTopRem',\n 'stimulusChoicesMarginTopRem',\n 'tokenGridColumnGapRem',\n 'tokenGridRowGapRem',\n 'tokenTemplateMarginTopRem',\n 'tokenInlineTokenGapRem',\n 'tokenChoicesMarginTopRem',\n 'inlineGridColumnGapRem',\n 'inlineGridRowGapRem',\n 'inlineTemplateMarginTopRem',\n 'inlineChoicesMarginTopRem',\n ];\n for (const key of numericLimitKeys) {\n if (limits[key] === undefined || limits[key] === null || limits[key] === '') continue;\n const value = Number(limits[key]);\n if (!Number.isFinite(value) || value <= 0) {\n errors.layoutLimits = `${key} must be a positive number when provided`;\n break;\n }\n }\n }\n\n return errors;\n};\n"],"mappings":";;AACA,IAAa,cAAc;;AAE3B,IAAa,wBAAwB;CACnC,yBAAyB;CACzB,mBAAmB;CACnB,uBAAuB;CACvB,2BAA2B;CAC3B,yBAAyB;CACzB,yBAAyB;CACzB,kCAAkC;CAClC,qCAAqC;CACrC,2BAA2B;CAC3B,yBAAyB;CACzB,oBAAoB;CACpB,qBAAqB;CACrB,iBAAiB;CACjB,gBAAgB;CAChB,mBAAmB;CACnB,iBAAiB;CACjB,oBAAoB;CACpB,mCAAmC;CACnC,gCAAgC;CAChC,mCAAmC;CACnC,6BAA6B;CAC7B,kCAAkC;CAClC,0BAA0B;CAC1B,uBAAuB;CACvB,8BAA8B;CAC9B,6BAA6B;CAC7B,uBAAuB;CACvB,oBAAoB;CACpB,2BAA2B;CAC3B,wBAAwB;CACxB,0BAA0B;CAC1B,wBAAwB;CACxB,qBAAqB;CACrB,4BAA4B;CAC5B,2BAA2B;CAC5B;AAED,IAAA,mBAAe;CACb,OAAO;EACL,IAAI;EACJ,SAAS;EACT,QAAQ;EACR,eAAe;EACf,iBAAiB;EACjB,eAAe;EACf,cAAc;EACd,sBAAsB,EAAE;EACxB,cAAc,EAAE,GAAG,uBAAuB;EAC1C,iBAAiB;EACjB,0BAA0B,EAAE;EAC5B,QAAQ;GACN,eAAe;GACf,0BAA0B;GAC1B,mBAAmB;GACnB,mBAAmB;GACnB,uBAAuB;GACvB,0BAA0B;GAC1B,iBAAiB;GACjB,eAAe;GACf,eAAe;GAChB;EACD,cAAc;EACd,UAAU,oBAAoB,YAAY;EAC1C,YAAY;EACZ,SAAS;GACP;IAAE,IAAI;IAAM,WAAW;IAAmB;GAC1C;IAAE,IAAI;IAAM,WAAW;IAAmB;GAC1C;IAAE,IAAI;IAAM,WAAW;IAAmB;GAC3C;EACD,iBAAiB;EACjB,UAAU;EACV,sBAAsB;EACtB,sBAAsB;EACtB,UAAU;EACV,iBAAiB;EACjB,uBAAuB;EACvB,QAAQ;EACR,qBAAqB;EACrB,4BAA4B;EAC7B;CACD,eAAe,EACb,QAAQ;EACN,OAAO;EACP,UAAU;EACX,EACF;CACF;;;ACxFD,IAAM,iBAAiB,UACrB,CAAC,CAAC,SACF,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,MAAM,IACrB,OAAO,KAAK,MAAiC,CAAC,WAAW;AAE3D,SAAgB,iBAAiB,UAA0B;AACzD,KAAI,CAAC,SAAU,QAAO;AAEtB,SAAQ,SAAS,MADN,iBACe,IAAI,EAAE,EAAE;;AAGpC,IAAa,kBAAkB,UAAe,YAAiB;AAC7D,KAAI,CAAC,WAAW,CAAC,QAAQ,SACvB,QAAO;CAET,MAAM,UAAU,UAAU,mBAAmB;AAC7C,KAAI,QAAQ,aAAa,QACvB,QAAO;AAET,QAAO;;AAGT,IAAa,mBAAmB,WAAgB,YAAiB;AAC/D,KAAI,CAAC,WAAW,cAAc,QAAQ,IAAI,CAAC,QAAQ,SACjD,QAAO;AAET,QAAO;;AAGT,IAAa,cAAc,UAAe,SAAc,gBAAgB,UAAU;AAChF,KAAI,CAAC,WAAW,CAAC,QAAQ,SACvB,QAAO;CAET,MAAM,uBAAuB,CAAC,CAAC,UAAU;CACzC,MAAM,uBAAuB,CAAC,CAAC,UAAU;AAGzC,KADE,wBAAwB,wBAAwB,CAAC,CAAC,UAAU,YAC/B,CAAC,cAC9B,QAAO;AAET,QAAO;;AAGT,IAAa,WAAW,UAAe,SAAc,QACnD,IAAI,SAAS,YAAY;AACvB,KAAI,CAAC,WAAW,cAAc,QAAQ,EAAE;AACtC,UAAQ;GACN,OAAO;GACP,OAAO;GACP,UAAU,CAAC,kDAAkD;GAC9D,CAAC;AACF;;AAGF,WAAU,iBAAiB,QAAQ;CACnC,MAAM,cAAc,eAAe,UAAU,QAAQ;AAErD,KAAI,gBAAgB,cAAc;AAChC,UAAQ;GACN,OAAO;GACP,OAAO;GACP,UAAU,CAAC,kDAAkD;GAC9D,CAAC;AACF;;CAGF,MAAM,QAAQ,gBAAgB,YAAY,IAAI;AAO9C,SAAQ;EAAE;EAAO,OAAO;EAAO,UANd;GACf,SAAS,KAAK,QAAQ,UAAU;GAChC,4BAA4B,QAAQ,SAAS;GAC7C,mBAAmB,UAAU,mBAAmB,OAAO;GACvD,gBAAgB,MAAM;GACvB;EACwC,CAAC;EAC1C;AAEJ,IAAa,sBAAsB,QAAa,EAAE,MAAM;CACtD,GAAG,iBAAS;CACZ,GAAG;CACH,cAAc;EACZ,GAAG;EACH,GAAI,OAAO,gBAAgB,EAAE;EAC9B;CACF;AAED,IAAa,aAAa,WAAgB,EAAE,KAAK,mBAAmB,SAAS;AAE7E,IAAa,oBAAoB,OAAY,EAAE,GAAG,GAAG;AAErD,IAAM,wBAAwB,aAAkB,CAAC,CAAC,UAAU;AAE5D,IAAM,qBAAqB,UAAe,QAAa;AACrD,KAAI,UAAU,gBAAiB,QAAO;AACtC,KAAI,MAAM,iBAAiB,gBAAiB,QAAO;AACnD,QAAO,KAAK,SAAS;;AAGvB,SAAS,aAAgB,OAAiB;CACxC,MAAM,MAAM,CAAC,GAAG,MAAM;AACtB,MAAK,IAAI,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;EACvC,MAAM,IAAI,KAAK,MAAM,KAAK,QAAQ,IAAI,IAAI,GAAG;AAC7C,GAAC,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,GAAG;;AAErC,QAAO;;AAGT,IAAM,oBAAoB,YACxB,MAAM,QAAQ,SAAS,MAAM,eAAe,GACxC,QAAQ,KAAK,iBACb,MAAM,QAAQ,SAAS,eAAe,GACpC,QAAQ,iBACR,EAAE;AAEV,IAAM,uBAAuB,SAAgB,gBAA0B,cAAsB;CAC3F,MAAM,iBAAiB,eACpB,KAAK,UAAU,QAAQ,MAAM,WAAW,SAAS,eAAe,MAAM,CAAC,CACvE,OAAO,QAAQ;AAElB,KAAI,eAAe,WAAW,QAAQ,OACpC,QAAO;CAGT,MAAM,gBAAgB,IAAI,IAAI,eAAe,KAAK,WAAgB,OAAO,WAAW,CAAC;CACrF,MAAM,YAAY,QAAQ,QAAQ,WAAW,CAAC,cAAc,IAAI,SAAS,WAAW,CAAC;AACrF,QAAO,CAAC,GAAG,gBAAgB,GAAG,UAAU;;AAG1C,IAAM,oBAAoB,OAAO,UAAe,SAAc,KAAU,kBAAwB;CAC9F,MAAM,UAAU,MAAM,QAAQ,UAAU,QAAQ,GAAG,CAAC,GAAG,SAAS,QAAQ,GAAG,EAAE;AAC7E,KAAI,CAAC,QAAQ,UAAU,CAAC,qBAAqB,SAAS,CACpD,QAAO;AAGT,KAAI,kBAAkB,UAAU,OAAO,EAAE,CAAC,CACxC,QAAO;CAGT,MAAM,iBAAiB,iBAAiB,QAAQ;AAChD,KAAI,eAAe,OACjB,QAAO,oBAAoB,SAAS,gBAAgB,KAAK;CAG3D,MAAM,kBAAkB,aAAa,QAAQ;AAE7C,KAAI,iBAAiB,OAAO,kBAAkB,cAAc,SAAS,MAAM,SAAS,SAAS;EAC3F,MAAM,cAAc,gBAAgB,KAAK,WAAW,QAAQ,GAAG,CAAC,OAAO,QAAQ;AAC/E,MAAI,YAAY,OACd,OAAM,cAAc,QAAQ,IAAI,QAAQ,SAAS,EAAE,gBAAgB,aAAa,CAAC;;AAIrF,QAAO;;AAGT,IAAa,QAAQ,OAAO,UAAe,SAAc,KAAU,kBAAwB;AACzF,WAAU,WAAW,EAAE;CACvB,MAAM,UAAU,OAAO,EAAE;CACzB,MAAM,qBAAqB,UAAU,SAAS;CAC9C,MAAM,UAAU,MAAM,kBAAkB,oBAAoB,SAAS,SAAS,cAAc;CAE5F,MAAM,MAAW;EACf,QAAQ,mBAAmB,gBAAgB,mBAAmB,SAAS;EACvE,iBAAiB,mBAAmB,mBAAmB;EACvD,eAAe,mBAAmB,iBAAiB;EACnD,cAAc,mBAAmB,gBAAgB;EACjD,sBACE,mBAAmB,wBACnB,OAAO,mBAAmB,yBAAyB,WAC/C,mBAAmB,uBACnB,EAAE;EACR,cAAc,mBAAmB,gBAAgB,EAAE,GAAG,uBAAuB;EAC7E,iBACE,mBAAmB,mBAAmB,OAAO,mBAAmB,oBAAoB,WAChF,mBAAmB,kBACnB;EACN,0BACE,mBAAmB,4BACnB,OAAO,mBAAmB,6BAA6B,WACnD,mBAAmB,2BACnB,EAAE;EACR,QACE,mBAAmB,UAAU,OAAO,mBAAmB,WAAW,WAC9D,mBAAmB,SACnB,EAAE;EACR,cAAc,mBAAmB,gBAAgB;EACjD,UAAU,mBAAmB;EAC7B,YAAY,mBAAmB;EAC/B;EACA,UAAU,mBAAmB;EAC7B,sBAAsB,CAAC,CAAC,mBAAmB;EAC3C,sBAAsB,CAAC,CAAC,mBAAmB;EAC3C,UAAU,mBAAmB,WAAW,mBAAmB,WAAW;EACtE,iBAAiB,mBAAmB,WAAW,mBAAmB,kBAAkB;EACpF,uBAAuB,CAAC,CAAC,mBAAmB;EAC5C,YAAY,mBAAmB,cAAc;EAC7C,uBACE,OAAO,mBAAmB,0BAA0B,YAChD,mBAAmB,wBACnB,KAAA;EACN,QAAQ,mBAAmB,UAAU;EACrC,UAAU,QAAQ,SAAS;EAC3B,MAAM,QAAQ;EACf;AAED,KAAI,QAAQ,SAAS,YAAY;EAC/B,MAAM,cAAc,eAAe,oBAAoB,QAAQ;AAC/D,MAAI,cAAc;AAClB,MAAI,kBAAkB,gBAAgB;AACtC,MAAI,kBAAkB,mBAAmB;;AAG3C,KAAI,QAAQ,SAAS,iBAAiB,QAAQ,SAAS,UAAU,QAAQ,SAAS,YAChF,KAAI,sBAAsB,mBAAmB,6BACzC,mBAAmB,sBACnB;KAEJ,KAAI,sBAAsB;AAG5B,QAAO;;AAGT,IAAa,gCAAgC,UAAe,QAAa;AACvE,QAAO,IAAI,SAAS,YAAY;AAC9B,MAAI,IAAI,SAAS,cAAc,IAAI,SAAS,aAC1C,SAAQ;GACN,IAAI;GACJ,SAAS;GACT,UAAU,UAAU,mBAAmB;GACxC,CAAC;MAEF,SAAQ,KAAK;GAEf;;AAGJ,IAAa,YAAY,QAAa,EAAE,EAAE,UAAe,EAAE,KAAK;CAC9D,MAAM,SAAc,EAAE;AAEtB,KAAI,MAAM,eAAe;EACvB,MAAM,IAAI,MAAM,QAAQ,MAAM,IAAI;AAClC,MAAI,CAAC,KAAK,MAAM,UACd,QAAO,SAAS;;CAIpB,MAAM,kBAAkB,MAAM,mBAAmB;CAEjD,MAAM,IAAI,iBADO,MAAM,YAAY,GACC;AACpC,KAAI,oBAAoB;MAClB,IAAI,EACN,QAAO,WAAW,2BAA2B,YAAY;YAElD,oBAAoB;MACzB,MAAM,EACR,QAAO,WAAW,qCAAqC,YAAY;WAC1D,IAAI,EACb,QAAO,WAAW,kCAAkC,YAAY;OAGlE,QAAO,kBAAkB;CAG3B,MAAM,UAAU,MAAM,QAAQ,MAAM,QAAQ,GAAG,MAAM,UAAU,EAAE;AACjE,KAAI,QAAQ,SAAS,EACnB,QAAO,UAAU;CAGnB,MAAM,OAAO,MAAM,cAAc;AACjC,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,IAAI,QAAQ;AAClB,MAAI,CAAC,GAAG,IAAI;AACV,UAAO,UAAU,UAAU,IAAI,EAAE;AACjC;;AAEF,MAAI,SAAS,QAAQ;GACnB,MAAM,OAAO,EAAE,aAAa,IAAI,MAAM;AACtC,OAAI,CAAC,OAAO,QAAQ,WAAW;AAC7B,WAAO,UAAU,UAAU,IAAI,EAAE;AACjC;;SAEG;AACL,OAAI,CAAC,EAAE,UAAU,MAAM,EAAE;AACvB,WAAO,UAAU,UAAU,IAAI,EAAE;AACjC;;AAEF,OAAI,CAAC,EAAE,UAAU,MAAM,EAAE;AACvB,WAAO,UAAU,UAAU,IAAI,EAAE;AACjC;;;;CAKN,MAAM,UAAU,MAAM;AACtB,KAAI,CAAC,WAAW,CAAC,QAAQ,MAAM,MAAW,EAAE,OAAO,QAAQ,CACzD,QAAO,kBAAkB;AAG3B,KAAI,MAAM;MAEJ,CADgB,CAAC,CAAC,MAAM,UAAU,MAAM,CAE1C,QAAO,WAAW;;CAItB,MAAM,SAAS,OAAO;AACtB,KAAI,UAAU,OAAO,WAAW,SAsC9B,MAAK,MAAM,OArCc;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EACmC;AAClC,MAAI,OAAO,SAAS,KAAA,KAAa,OAAO,SAAS,QAAQ,OAAO,SAAS,GAAI;EAC7E,MAAM,QAAQ,OAAO,OAAO,KAAK;AACjC,MAAI,CAAC,OAAO,SAAS,MAAM,IAAI,SAAS,GAAG;AACzC,UAAO,eAAe,GAAG,IAAI;AAC7B;;;AAKN,QAAO"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/shared/layoutLimits.ts","../../src/controller/defaults.ts","../../src/controller/index.ts"],"sourcesContent":["export type LayoutLimits = {\n blankStandaloneWidthRem: number;\n blankWideWidthRem: number;\n blankUnderlineWidthPx: number;\n blankUnderlineWideWidthPx: number;\n horizontalChoiceWidthPx: number;\n horizontalChoiceWidthVw: number;\n horizontalChoiceTileMinHeightRem: number;\n horizontalChoiceContentMinHeightRem: number;\n selectedImageMaxHeightRem: number;\n choiceImageMaxHeightRem: number;\n listenButtonSizePx: number;\n stimulusMinColumnPx: number;\n textMinColumnPx: number;\n legendMaxChars: number;\n choiceGroupGapRem: number;\n choiceRowGapRem: number;\n toggleButtonGapRem: number;\n horizontalChoiceRadioTopMarginRem: number;\n audioBlankTemplateMarginTopRem: number;\n audioBlankTemplateMarginBottomRem: number;\n audioInstructionsMaxWidthPx: number;\n narrowHorizontalChoiceMaxWidthPx: number;\n stimulusGridColumnGapRem: number;\n stimulusGridRowGapRem: number;\n stimulusSentenceMarginTopRem: number;\n stimulusChoicesMarginTopRem: number;\n tokenGridColumnGapRem: number;\n tokenGridRowGapRem: number;\n tokenTemplateMarginTopRem: number;\n tokenInlineTokenGapRem: number;\n tokenChoicesMarginTopRem: number;\n inlineGridColumnGapRem: number;\n inlineGridRowGapRem: number;\n inlineTemplateMarginTopRem: number;\n inlineChoicesMarginTopRem: number;\n};\n\n/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */\nexport const DEFAULT_LAYOUT_LIMITS: LayoutLimits = {\n blankStandaloneWidthRem: 7,\n blankWideWidthRem: 10,\n blankUnderlineWidthPx: 2,\n blankUnderlineWideWidthPx: 4,\n horizontalChoiceWidthPx: 170,\n horizontalChoiceWidthVw: 30,\n horizontalChoiceTileMinHeightRem: 11,\n horizontalChoiceContentMinHeightRem: 7.5,\n selectedImageMaxHeightRem: 4,\n choiceImageMaxHeightRem: 5,\n listenButtonSizePx: 128,\n stimulusMinColumnPx: 210,\n textMinColumnPx: 260,\n legendMaxChars: 120,\n choiceGroupGapRem: 0.5,\n choiceRowGapRem: 0.5,\n toggleButtonGapRem: 0.5,\n horizontalChoiceRadioTopMarginRem: 0.5,\n audioBlankTemplateMarginTopRem: 0.8,\n audioBlankTemplateMarginBottomRem: 1.8,\n audioInstructionsMaxWidthPx: 875,\n narrowHorizontalChoiceMaxWidthPx: 230,\n stimulusGridColumnGapRem: 2,\n stimulusGridRowGapRem: 0.7,\n stimulusSentenceMarginTopRem: 0.2,\n stimulusChoicesMarginTopRem: 0.9,\n tokenGridColumnGapRem: 1.5,\n tokenGridRowGapRem: 0.8,\n tokenTemplateMarginTopRem: 0.6,\n tokenInlineTokenGapRem: 0.35,\n tokenChoicesMarginTopRem: 0.2,\n inlineGridColumnGapRem: 1.5,\n inlineGridRowGapRem: 0.65,\n inlineTemplateMarginTopRem: 0.45,\n inlineChoicesMarginTopRem: 0.25,\n};\n","import { DEFAULT_LAYOUT_LIMITS } from '../shared/layoutLimits';\n\nexport { DEFAULT_LAYOUT_LIMITS };\n\n/** Token authors must include exactly once in `template` (HTML). */\nexport const BLANK_TOKEN = '{{blank}}';\n\nexport default {\n model: {\n id: '1',\n element: 'mc-populated-blank',\n prompt: '<p><strong>Demo prompt</strong></p>',\n promptEnabled: true,\n interactionMode: 'populate_blank' as const,\n layoutProfile: '',\n choiceLayout: '',\n layoutProfilePresets: {},\n layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin: null,\n audioButtonSkinsByLocale: {},\n uiText: {\n answerChoices: 'Answer choices',\n selectedAnswerInSentence: 'blank',\n showCorrectAnswer: 'Show correct answer',\n hideCorrectAnswer: 'Hide correct answer',\n clickToEnableAutoplay: 'Click to enable audio autoplay',\n audioResourceUnavailable: 'Audio is enabled but no playable audio URL is configured.',\n transcriptLabel: 'Transcript',\n listenLabelEn: 'Listen',\n listenLabelEs: 'Escuchar',\n listenSilentAlt: 'Repeat instructions',\n listenPlayingAlt: 'Instructions are playing',\n listenSilentAltEs: 'Escuchar. Repetir las instrucciones.',\n listenPlayingAltEs: 'Escuchar. Estas son las instrucciones.',\n },\n sentenceHtml: '',\n template: `<p>The answer is ${BLANK_TOKEN}.</p>`,\n choiceMode: 'text' as const,\n choices: [\n { id: 'c1', labelHtml: '<p>Option A</p>' },\n { id: 'c2', labelHtml: '<p>Option B</p>' },\n { id: 'c3', labelHtml: '<p>Option C</p>' },\n ],\n correctChoiceId: 'c2',\n hasAudio: false,\n autoplayAudioEnabled: false,\n completeAudioEnabled: false,\n audioUrl: '',\n audioTranscript: '',\n showVisibleTranscript: false,\n locale: '',\n teacherInstructions: '',\n teacherInstructionsEnabled: false,\n },\n configuration: {\n prompt: {\n label: 'Prompt',\n settings: true,\n },\n },\n};\n","import defaults, { BLANK_TOKEN, DEFAULT_LAYOUT_LIMITS } from './defaults';\nimport type {\n McpbChoice,\n McpbQuestion,\n McpbSession,\n McpbEnv,\n McpbCorrectness,\n} from '../shared/types';\n\nconst isEmptyObject = (value: unknown): boolean =>\n !!value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0;\n\nexport function countBlankTokens(template: string): number {\n if (!template) return 0;\n const re = /\\{\\{blank\\}\\}/g;\n return (template.match(re) || []).length;\n}\n\nexport const getCorrectness = (question: McpbQuestion, session: McpbSession): McpbCorrectness => {\n if (!session?.choiceId) {\n return 'unanswered';\n }\n const correct = question?.correctChoiceId || '';\n if (session.choiceId === correct) {\n return 'correct';\n }\n return 'incorrect';\n};\n\nexport const getPartialScore = (_question: McpbQuestion, session: McpbSession) => {\n if (!session || isEmptyObject(session) || !session.choiceId) {\n return 0;\n }\n return 1;\n};\n\nexport const outcome = (question: McpbQuestion, session: McpbSession, env: McpbEnv) =>\n new Promise((resolve) => {\n if (!session || isEmptyObject(session)) {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n session = normalizeSession(session);\n const correctness = getCorrectness(question, session);\n\n if (correctness === 'unanswered') {\n resolve({\n score: 0,\n empty: true,\n traceLog: ['Student did not select any answers. Score is 0.'],\n });\n return;\n }\n\n const score = correctness === 'correct' ? 1 : 0;\n const traceLog = [\n `Mode: ${env?.mode || 'unknown'}.`,\n `Student selected choice: ${session.choiceId}.`,\n `Correct choice: ${question?.correctChoiceId || 'none'}.`,\n `Final score: ${score}.`,\n ];\n resolve({ score, empty: false, traceLog });\n });\n\nexport const createDefaultModel = (model: McpbQuestion = {}) => ({\n ...defaults.model,\n ...model,\n layoutLimits: {\n ...DEFAULT_LAYOUT_LIMITS,\n ...(model?.layoutLimits || {}),\n },\n});\n\nexport const normalize = (question: McpbQuestion = {}) => createDefaultModel(question);\n\nexport const normalizeSession = (s: McpbSession): McpbSession => ({ ...s });\n\nconst shouldShuffleChoices = (question: McpbQuestion) => !!question?.shuffle;\n\nconst shouldLockChoices = (question: McpbQuestion, env: McpbEnv) => {\n if (question?.lockChoiceOrder) return true;\n if (env?.['@pie-element']?.lockChoiceOrder) return true;\n return env?.role === 'instructor';\n};\n\nfunction shuffleArray<T>(items: T[]): T[] {\n const out = [...items];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [out[i], out[j]] = [out[j], out[i]];\n }\n return out;\n}\n\nconst getStoredShuffle = (session: McpbSession): string[] =>\n Array.isArray(session?.data?.shuffledValues)\n ? session.data.shuffledValues\n : Array.isArray(session?.shuffledValues)\n ? session.shuffledValues\n : [];\n\nconst applyShuffledValues = (\n choices: McpbChoice[],\n shuffledValues: string[],\n choiceKey: keyof McpbChoice\n) => {\n const orderedChoices = shuffledValues\n .map((value) => choices.find((choice) => choice?.[choiceKey] === value))\n .filter((c): c is McpbChoice => !!c);\n\n if (orderedChoices.length === choices.length) {\n return orderedChoices;\n }\n\n const orderedValues = new Set(orderedChoices.map((choice) => choice[choiceKey]));\n const leftovers = choices.filter((choice) => !orderedValues.has(choice?.[choiceKey]));\n return [...orderedChoices, ...leftovers];\n};\n\ntype UpdateSessionFn = (\n id: string,\n element: string,\n data: { shuffledValues: string[] }\n) => Promise<void>;\n\nconst getOrderedChoices = async (\n question: McpbQuestion,\n session: McpbSession,\n env: McpbEnv,\n updateSession?: UpdateSessionFn\n) => {\n const choices = Array.isArray(question?.choices) ? [...question.choices] : [];\n if (!choices.length || !shouldShuffleChoices(question)) {\n return choices;\n }\n\n if (shouldLockChoices(question, env || {})) {\n return choices;\n }\n\n const shuffledValues = getStoredShuffle(session);\n if (shuffledValues.length) {\n return applyShuffledValues(choices, shuffledValues, 'id');\n }\n\n const shuffledChoices = shuffleArray(choices);\n\n if (updateSession && typeof updateSession === 'function' && session?.id && session?.element) {\n const shuffledIds = shuffledChoices.map((choice) => choice?.id).filter(Boolean);\n if (shuffledIds.length) {\n await updateSession(session.id, session.element, { shuffledValues: shuffledIds });\n }\n }\n\n return shuffledChoices;\n};\n\nexport const model = async (\n question: McpbQuestion,\n session: McpbSession | null,\n env: McpbEnv | null,\n updateSession?: UpdateSessionFn\n) => {\n const safeSession: McpbSession = session || {};\n const safeEnv: McpbEnv = env || {};\n const normalizedQuestion = normalize(question);\n const choices = await getOrderedChoices(normalizedQuestion, safeSession, safeEnv, updateSession);\n\n const out: Record<string, unknown> = {\n prompt: normalizedQuestion.promptEnabled ? normalizedQuestion.prompt : null,\n interactionMode: normalizedQuestion.interactionMode || 'populate_blank',\n layoutProfile: normalizedQuestion.layoutProfile || '',\n choiceLayout: normalizedQuestion.choiceLayout || '',\n layoutProfilePresets:\n normalizedQuestion.layoutProfilePresets &&\n typeof normalizedQuestion.layoutProfilePresets === 'object'\n ? normalizedQuestion.layoutProfilePresets\n : {},\n layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },\n audioButtonSkin:\n normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'\n ? normalizedQuestion.audioButtonSkin\n : null,\n audioButtonSkinsByLocale:\n normalizedQuestion.audioButtonSkinsByLocale &&\n typeof normalizedQuestion.audioButtonSkinsByLocale === 'object'\n ? normalizedQuestion.audioButtonSkinsByLocale\n : {},\n uiText:\n normalizedQuestion.uiText && typeof normalizedQuestion.uiText === 'object'\n ? normalizedQuestion.uiText\n : {},\n sentenceHtml: normalizedQuestion.sentenceHtml || null,\n template: normalizedQuestion.template,\n choiceMode: normalizedQuestion.choiceMode,\n choices,\n hasAudio: normalizedQuestion.hasAudio,\n autoplayAudioEnabled: !!normalizedQuestion.autoplayAudioEnabled,\n completeAudioEnabled: !!normalizedQuestion.completeAudioEnabled,\n audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,\n audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,\n showVisibleTranscript: !!normalizedQuestion.showVisibleTranscript,\n customType: normalizedQuestion.customType || '',\n useFeatureButtonAudio:\n typeof normalizedQuestion.useFeatureButtonAudio === 'boolean'\n ? normalizedQuestion.useFeatureButtonAudio\n : undefined,\n locale: normalizedQuestion.locale || '',\n disabled: safeEnv.mode !== 'gather',\n mode: safeEnv.mode,\n };\n\n if (safeEnv.mode === 'evaluate') {\n const correctness = getCorrectness(normalizedQuestion, safeSession);\n out.correctness = correctness;\n out.responseCorrect = correctness === 'correct';\n out.correctChoiceId = normalizedQuestion.correctChoiceId;\n }\n\n if (safeEnv.role === 'instructor' && (safeEnv.mode === 'view' || safeEnv.mode === 'evaluate')) {\n out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled\n ? normalizedQuestion.teacherInstructions\n : null;\n } else {\n out.teacherInstructions = null;\n }\n\n return out;\n};\n\nexport const createCorrectResponseSession = (question: McpbQuestion, env: McpbEnv) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n resolve({\n id: '1',\n element: 'mc-populated-blank',\n choiceId: question?.correctChoiceId || '',\n });\n } else {\n resolve(null);\n }\n });\n};\n\nexport const validate = (question: McpbQuestion = {}, _config: Record<string, unknown> = {}) => {\n const errors: Record<string, string> = {};\n\n if (question.promptEnabled) {\n const p = question.prompt?.trim() || '';\n if (!p || p === '<p></p>') {\n errors.prompt = 'Prompt is required when prompt is enabled';\n }\n }\n\n const interactionMode = question.interactionMode || 'populate_blank';\n const template = question.template || '';\n const n = countBlankTokens(template);\n if (interactionMode === 'audio_mc_only') {\n if (n > 0) {\n errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;\n }\n } else if (interactionMode === 'populate_blank') {\n if (n === 0) {\n errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;\n } else if (n > 1) {\n errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;\n }\n } else {\n errors.interactionMode = 'Unknown interaction mode';\n }\n\n const choices = Array.isArray(question.choices) ? question.choices : [];\n if (choices.length < 2) {\n errors.choices = 'At least two choices are required';\n }\n\n const mode = question.choiceMode || 'text';\n for (let i = 0; i < choices.length; i++) {\n const c = choices[i];\n if (!c?.id) {\n errors.choices = `Choice ${i + 1} is missing an id`;\n break;\n }\n if (mode === 'text') {\n const lbl = (c.labelHtml || '').trim();\n if (!lbl || lbl === '<p></p>') {\n errors.choices = `Choice ${i + 1} needs label text`;\n break;\n }\n } else {\n if (!c.imageUrl?.trim()) {\n errors.choices = `Choice ${i + 1} needs an image URL`;\n break;\n }\n if (!c.imageAlt?.trim()) {\n errors.choices = `Choice ${i + 1} needs image alt text`;\n break;\n }\n }\n }\n\n const correct = question.correctChoiceId;\n if (!correct || !choices.some((c) => c.id === correct)) {\n errors.correctChoiceId = 'Correct choice must match one of the choice ids';\n }\n\n if (question.hasAudio) {\n const hasAudioUrl = !!question.audioUrl?.trim();\n if (!hasAudioUrl) {\n errors.audioUrl = 'A playable audio URL is required when audio is enabled';\n }\n }\n\n const limits = question?.layoutLimits;\n if (limits && typeof limits === 'object') {\n const numericLimitKeys = [\n 'blankStandaloneWidthRem',\n 'blankWideWidthRem',\n 'blankUnderlineWidthPx',\n 'blankUnderlineWideWidthPx',\n 'horizontalChoiceWidthPx',\n 'horizontalChoiceWidthVw',\n 'horizontalChoiceTileMinHeightRem',\n 'horizontalChoiceContentMinHeightRem',\n 'selectedImageMaxHeightRem',\n 'choiceImageMaxHeightRem',\n 'listenButtonSizePx',\n 'stimulusMinColumnPx',\n 'textMinColumnPx',\n 'legendMaxChars',\n 'choiceGroupGapRem',\n 'choiceRowGapRem',\n 'toggleButtonGapRem',\n 'horizontalChoiceRadioTopMarginRem',\n 'audioBlankTemplateMarginTopRem',\n 'audioBlankTemplateMarginBottomRem',\n 'audioInstructionsMaxWidthPx',\n 'narrowHorizontalChoiceMaxWidthPx',\n 'stimulusGridColumnGapRem',\n 'stimulusGridRowGapRem',\n 'stimulusSentenceMarginTopRem',\n 'stimulusChoicesMarginTopRem',\n 'tokenGridColumnGapRem',\n 'tokenGridRowGapRem',\n 'tokenTemplateMarginTopRem',\n 'tokenInlineTokenGapRem',\n 'tokenChoicesMarginTopRem',\n 'inlineGridColumnGapRem',\n 'inlineGridRowGapRem',\n 'inlineTemplateMarginTopRem',\n 'inlineChoicesMarginTopRem',\n ] as const;\n for (const key of numericLimitKeys) {\n if (limits[key] === undefined || limits[key] === null) continue;\n const value = Number(limits[key]);\n if (!Number.isFinite(value) || value <= 0) {\n errors.layoutLimits = `${key} must be a positive number when provided`;\n break;\n }\n }\n }\n\n return errors;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type AudioMode = 'feature-button' | 'controls' | 'error' | 'none';
|
|
2
|
+
export declare function computeAudioMode(params: {
|
|
3
|
+
hasAudio: boolean;
|
|
4
|
+
audioUrl: string | undefined;
|
|
5
|
+
useFeatureButtonAudio: boolean;
|
|
6
|
+
}): AudioMode;
|
|
7
|
+
//# sourceMappingURL=computeAudioMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computeAudioMode.d.ts","sourceRoot":"","sources":["../../src/delivery/computeAudioMode.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,qBAAqB,EAAE,OAAO,CAAC;CAChC,GAAG,SAAS,CAIZ"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ChoiceCorrectnessParams {
|
|
2
|
+
isEvaluateMode: boolean;
|
|
3
|
+
correctChoiceId: string;
|
|
4
|
+
selectedId: string;
|
|
5
|
+
showCorrectAnswer: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Computes which choice ids should display a correctness badge and what kind.
|
|
9
|
+
*
|
|
10
|
+
* Reveal mode (showCorrectAnswer=true) shows only the canonical correct answer —
|
|
11
|
+
* the student's wrong selection is deliberately suppressed so the UI only
|
|
12
|
+
* highlights what to remember, not what to regret.
|
|
13
|
+
*
|
|
14
|
+
* Unanswered-in-evaluate (selectedId='') treats the correct choice as missed
|
|
15
|
+
* (incorrect badge) rather than triggering the two-badge wrong-answer path.
|
|
16
|
+
*/
|
|
17
|
+
export declare function computeChoiceCorrectness(params: ChoiceCorrectnessParams): Map<string, 'correct' | 'incorrect'>;
|
|
18
|
+
//# sourceMappingURL=computeChoiceCorrectness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computeChoiceCorrectness.d.ts","sourceRoot":"","sources":["../../src/delivery/computeChoiceCorrectness.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,uBAAuB,GAC9B,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC,CAsBtC"}
|