@pie-element/mc-populated-blank 0.2.9 → 0.2.11
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 +20 -10
- 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 +30 -30
- package/dist/controller/index.js.map +1 -1
- package/dist/controller/index.test.d.ts +63 -0
- package/dist/controller/index.test.d.ts.map +1 -0
- package/dist/delivery/AudioPlayer.test.d.ts +2 -0
- package/dist/delivery/AudioPlayer.test.d.ts.map +1 -0
- package/dist/delivery/ChoiceRow.test.d.ts +2 -0
- package/dist/delivery/ChoiceRow.test.d.ts.map +1 -0
- package/dist/delivery/ClozeMarker.test.d.ts +2 -0
- package/dist/delivery/ClozeMarker.test.d.ts.map +1 -0
- package/dist/delivery/McPopulatedBlank.transcript.test.d.ts +2 -0
- package/dist/delivery/McPopulatedBlank.transcript.test.d.ts.map +1 -0
- package/dist/delivery/computeAudioMode.d.ts +7 -0
- package/dist/delivery/computeAudioMode.d.ts.map +1 -0
- package/dist/delivery/computeAudioMode.test.d.ts +2 -0
- package/dist/delivery/computeAudioMode.test.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/computeChoiceCorrectness.test.d.ts +19 -0
- package/dist/delivery/computeChoiceCorrectness.test.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/computeDisplayState.test.d.ts +38 -0
- package/dist/delivery/computeDisplayState.test.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/computeLayoutProfile.test.d.ts +2 -0
- package/dist/delivery/computeLayoutProfile.test.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/computeLayoutStyle.test.d.ts +2 -0
- package/dist/delivery/computeLayoutStyle.test.d.ts.map +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1650 -881
- package/dist/delivery/index.js.map +1 -1
- package/dist/delivery/variant-css-map.d.ts +10 -0
- package/dist/delivery/variant-css-map.d.ts.map +1 -0
- package/dist/index.iife.d.ts +151 -10
- package/dist/index.iife.d.ts.map +1 -1
- package/dist/index.iife.js +409 -16
- package/dist/index.js +1650 -881
- 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 +4 -2
- package/src/controller/defaults.ts +9 -39
- package/src/controller/index.test.ts +428 -0
- package/src/controller/index.ts +64 -50
- package/src/delivery/AudioPlayer.svelte +246 -0
- package/src/delivery/AudioPlayer.test.ts +149 -0
- package/src/delivery/ChoiceRow.svelte +222 -0
- package/src/delivery/ChoiceRow.test.ts +179 -0
- package/src/delivery/ClozeMarker.svelte +83 -0
- package/src/delivery/ClozeMarker.test.ts +128 -0
- package/src/delivery/McPopulatedBlank.svelte +234 -709
- package/src/delivery/McPopulatedBlank.transcript.test.ts +154 -0
- package/src/delivery/computeAudioMode.test.ts +40 -0
- package/src/delivery/computeAudioMode.ts +11 -0
- package/src/delivery/computeChoiceCorrectness.test.ts +120 -0
- package/src/delivery/computeChoiceCorrectness.ts +42 -0
- package/src/delivery/computeDisplayState.test.ts +274 -0
- package/src/delivery/computeDisplayState.ts +101 -0
- package/src/delivery/computeLayoutProfile.test.ts +163 -0
- package/src/delivery/computeLayoutProfile.ts +72 -0
- package/src/delivery/computeLayoutStyle.test.ts +168 -0
- package/src/delivery/computeLayoutStyle.ts +128 -0
- package/src/delivery/cqt-css/sel-r1-base.css +59 -0
- package/src/delivery/cqt-css/sel-r1-g-stem.css +14 -0
- package/src/delivery/cqt-css/sel-r1-gg-plus.css +12 -0
- package/src/delivery/cqt-css/sel-r1-ggplus.css +17 -0
- package/src/delivery/cqt-css/sel-r1-gplusggg.css +25 -0
- package/src/delivery/cqt-css/sel-r1-plusggg.css +17 -0
- package/src/delivery/cqt-css/sel-r1-s3.css +85 -0
- package/src/delivery/cqt-css/sel-vic.css +99 -0
- package/src/delivery/cqt-css/sr-vic.css +63 -0
- package/src/delivery/index.ts +14 -23
- package/src/delivery/variant-css-map.ts +124 -0
- package/src/shared/layoutLimits.ts +76 -0
- package/src/shared/types.ts +84 -0
- package/src/types/css-raw.d.ts +4 -0
|
@@ -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",
|
|
@@ -221,13 +220,14 @@ var model = async (question, session, env, updateSession) => {
|
|
|
221
220
|
audioUrl: normalizedQuestion.hasAudio ? normalizedQuestion.audioUrl : null,
|
|
222
221
|
audioTranscript: normalizedQuestion.hasAudio ? normalizedQuestion.audioTranscript : null,
|
|
223
222
|
showVisibleTranscript: !!normalizedQuestion.showVisibleTranscript,
|
|
223
|
+
customType: normalizedQuestion.customType || "",
|
|
224
224
|
useFeatureButtonAudio: typeof normalizedQuestion.useFeatureButtonAudio === "boolean" ? normalizedQuestion.useFeatureButtonAudio : void 0,
|
|
225
225
|
locale: normalizedQuestion.locale || "",
|
|
226
226
|
disabled: safeEnv.mode !== "gather",
|
|
227
227
|
mode: safeEnv.mode
|
|
228
228
|
};
|
|
229
229
|
if (safeEnv.mode === "evaluate") {
|
|
230
|
-
const correctness = getCorrectness(normalizedQuestion,
|
|
230
|
+
const correctness = getCorrectness(normalizedQuestion, safeSession);
|
|
231
231
|
out.correctness = correctness;
|
|
232
232
|
out.responseCorrect = correctness === "correct";
|
|
233
233
|
out.correctChoiceId = normalizedQuestion.correctChoiceId;
|
|
@@ -246,23 +246,23 @@ var createCorrectResponseSession = (question, env) => {
|
|
|
246
246
|
else resolve(null);
|
|
247
247
|
});
|
|
248
248
|
};
|
|
249
|
-
var validate = (
|
|
249
|
+
var validate = (question = {}, _config = {}) => {
|
|
250
250
|
const errors = {};
|
|
251
|
-
if (
|
|
252
|
-
const p =
|
|
251
|
+
if (question.promptEnabled) {
|
|
252
|
+
const p = question.prompt?.trim() || "";
|
|
253
253
|
if (!p || p === "<p></p>") errors.prompt = "Prompt is required when prompt is enabled";
|
|
254
254
|
}
|
|
255
|
-
const interactionMode =
|
|
256
|
-
const n = countBlankTokens(
|
|
255
|
+
const interactionMode = question.interactionMode || "populate_blank";
|
|
256
|
+
const n = countBlankTokens(question.template || "");
|
|
257
257
|
if (interactionMode === "audio_mc_only") {
|
|
258
258
|
if (n > 0) errors.template = `Template cannot contain ${BLANK_TOKEN} in audio-only mode`;
|
|
259
259
|
} else if (interactionMode === "populate_blank") {
|
|
260
260
|
if (n === 0) errors.template = `Template must contain exactly one ${BLANK_TOKEN} placeholder`;
|
|
261
261
|
else if (n > 1) errors.template = `Template must contain only one ${BLANK_TOKEN} placeholder`;
|
|
262
262
|
} else errors.interactionMode = "Unknown interaction mode";
|
|
263
|
-
const choices = Array.isArray(
|
|
263
|
+
const choices = Array.isArray(question.choices) ? question.choices : [];
|
|
264
264
|
if (choices.length < 2) errors.choices = "At least two choices are required";
|
|
265
|
-
const mode =
|
|
265
|
+
const mode = question.choiceMode || "text";
|
|
266
266
|
for (let i = 0; i < choices.length; i++) {
|
|
267
267
|
const c = choices[i];
|
|
268
268
|
if (!c?.id) {
|
|
@@ -286,12 +286,12 @@ var validate = (model = {}, _config = {}) => {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
|
-
const correct =
|
|
289
|
+
const correct = question.correctChoiceId;
|
|
290
290
|
if (!correct || !choices.some((c) => c.id === correct)) errors.correctChoiceId = "Correct choice must match one of the choice ids";
|
|
291
|
-
if (
|
|
292
|
-
if (!!!
|
|
291
|
+
if (question.hasAudio) {
|
|
292
|
+
if (!!!question.audioUrl?.trim()) errors.audioUrl = "A playable audio URL is required when audio is enabled";
|
|
293
293
|
}
|
|
294
|
-
const limits =
|
|
294
|
+
const limits = question?.layoutLimits;
|
|
295
295
|
if (limits && typeof limits === "object") for (const key of [
|
|
296
296
|
"blankStandaloneWidthRem",
|
|
297
297
|
"blankWideWidthRem",
|
|
@@ -329,7 +329,7 @@ var validate = (model = {}, _config = {}) => {
|
|
|
329
329
|
"inlineTemplateMarginTopRem",
|
|
330
330
|
"inlineChoicesMarginTopRem"
|
|
331
331
|
]) {
|
|
332
|
-
if (limits[key] === void 0 || limits[key] === null
|
|
332
|
+
if (limits[key] === void 0 || limits[key] === null) continue;
|
|
333
333
|
const value = Number(limits[key]);
|
|
334
334
|
if (!Number.isFinite(value) || value <= 0) {
|
|
335
335
|
errors.layoutLimits = `${key} must be a positive number when provided`;
|
|
@@ -339,6 +339,6 @@ var validate = (model = {}, _config = {}) => {
|
|
|
339
339
|
return errors;
|
|
340
340
|
};
|
|
341
341
|
//#endregion
|
|
342
|
-
export { countBlankTokens, createCorrectResponseSession, createDefaultModel, getCorrectness, getPartialScore,
|
|
342
|
+
export { countBlankTokens, createCorrectResponseSession, createDefaultModel, getCorrectness, getPartialScore, model, normalize, normalizeSession, outcome, validate };
|
|
343
343
|
|
|
344
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 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":"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"}
|
|
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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"}
|