@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.
Files changed (91) hide show
  1. package/README.md +20 -10
  2. package/dist/author/index.js +186 -152
  3. package/dist/author/index.js.map +1 -1
  4. package/dist/controller/defaults.d.ts +41 -73
  5. package/dist/controller/defaults.d.ts.map +1 -1
  6. package/dist/controller/index.d.ts +154 -10
  7. package/dist/controller/index.d.ts.map +1 -1
  8. package/dist/controller/index.js +30 -30
  9. package/dist/controller/index.js.map +1 -1
  10. package/dist/controller/index.test.d.ts +63 -0
  11. package/dist/controller/index.test.d.ts.map +1 -0
  12. package/dist/delivery/AudioPlayer.test.d.ts +2 -0
  13. package/dist/delivery/AudioPlayer.test.d.ts.map +1 -0
  14. package/dist/delivery/ChoiceRow.test.d.ts +2 -0
  15. package/dist/delivery/ChoiceRow.test.d.ts.map +1 -0
  16. package/dist/delivery/ClozeMarker.test.d.ts +2 -0
  17. package/dist/delivery/ClozeMarker.test.d.ts.map +1 -0
  18. package/dist/delivery/McPopulatedBlank.transcript.test.d.ts +2 -0
  19. package/dist/delivery/McPopulatedBlank.transcript.test.d.ts.map +1 -0
  20. package/dist/delivery/computeAudioMode.d.ts +7 -0
  21. package/dist/delivery/computeAudioMode.d.ts.map +1 -0
  22. package/dist/delivery/computeAudioMode.test.d.ts +2 -0
  23. package/dist/delivery/computeAudioMode.test.d.ts.map +1 -0
  24. package/dist/delivery/computeChoiceCorrectness.d.ts +18 -0
  25. package/dist/delivery/computeChoiceCorrectness.d.ts.map +1 -0
  26. package/dist/delivery/computeChoiceCorrectness.test.d.ts +19 -0
  27. package/dist/delivery/computeChoiceCorrectness.test.d.ts.map +1 -0
  28. package/dist/delivery/computeDisplayState.d.ts +47 -0
  29. package/dist/delivery/computeDisplayState.d.ts.map +1 -0
  30. package/dist/delivery/computeDisplayState.test.d.ts +38 -0
  31. package/dist/delivery/computeDisplayState.test.d.ts.map +1 -0
  32. package/dist/delivery/computeLayoutProfile.d.ts +18 -0
  33. package/dist/delivery/computeLayoutProfile.d.ts.map +1 -0
  34. package/dist/delivery/computeLayoutProfile.test.d.ts +2 -0
  35. package/dist/delivery/computeLayoutProfile.test.d.ts.map +1 -0
  36. package/dist/delivery/computeLayoutStyle.d.ts +27 -0
  37. package/dist/delivery/computeLayoutStyle.d.ts.map +1 -0
  38. package/dist/delivery/computeLayoutStyle.test.d.ts +2 -0
  39. package/dist/delivery/computeLayoutStyle.test.d.ts.map +1 -0
  40. package/dist/delivery/index.d.ts.map +1 -1
  41. package/dist/delivery/index.js +1650 -881
  42. package/dist/delivery/index.js.map +1 -1
  43. package/dist/delivery/variant-css-map.d.ts +10 -0
  44. package/dist/delivery/variant-css-map.d.ts.map +1 -0
  45. package/dist/index.iife.d.ts +151 -10
  46. package/dist/index.iife.d.ts.map +1 -1
  47. package/dist/index.iife.js +409 -16
  48. package/dist/index.js +1650 -881
  49. package/dist/index.js.map +1 -1
  50. package/dist/print/index.js +194 -166
  51. package/dist/print/index.js.map +1 -1
  52. package/dist/shared/layoutLimits.d.ts +40 -0
  53. package/dist/shared/layoutLimits.d.ts.map +1 -0
  54. package/dist/shared/types.d.ts +82 -0
  55. package/dist/shared/types.d.ts.map +1 -0
  56. package/package.json +4 -2
  57. package/src/controller/defaults.ts +9 -39
  58. package/src/controller/index.test.ts +428 -0
  59. package/src/controller/index.ts +64 -50
  60. package/src/delivery/AudioPlayer.svelte +246 -0
  61. package/src/delivery/AudioPlayer.test.ts +149 -0
  62. package/src/delivery/ChoiceRow.svelte +222 -0
  63. package/src/delivery/ChoiceRow.test.ts +179 -0
  64. package/src/delivery/ClozeMarker.svelte +83 -0
  65. package/src/delivery/ClozeMarker.test.ts +128 -0
  66. package/src/delivery/McPopulatedBlank.svelte +234 -709
  67. package/src/delivery/McPopulatedBlank.transcript.test.ts +154 -0
  68. package/src/delivery/computeAudioMode.test.ts +40 -0
  69. package/src/delivery/computeAudioMode.ts +11 -0
  70. package/src/delivery/computeChoiceCorrectness.test.ts +120 -0
  71. package/src/delivery/computeChoiceCorrectness.ts +42 -0
  72. package/src/delivery/computeDisplayState.test.ts +274 -0
  73. package/src/delivery/computeDisplayState.ts +101 -0
  74. package/src/delivery/computeLayoutProfile.test.ts +163 -0
  75. package/src/delivery/computeLayoutProfile.ts +72 -0
  76. package/src/delivery/computeLayoutStyle.test.ts +168 -0
  77. package/src/delivery/computeLayoutStyle.ts +128 -0
  78. package/src/delivery/cqt-css/sel-r1-base.css +59 -0
  79. package/src/delivery/cqt-css/sel-r1-g-stem.css +14 -0
  80. package/src/delivery/cqt-css/sel-r1-gg-plus.css +12 -0
  81. package/src/delivery/cqt-css/sel-r1-ggplus.css +17 -0
  82. package/src/delivery/cqt-css/sel-r1-gplusggg.css +25 -0
  83. package/src/delivery/cqt-css/sel-r1-plusggg.css +17 -0
  84. package/src/delivery/cqt-css/sel-r1-s3.css +85 -0
  85. package/src/delivery/cqt-css/sel-vic.css +99 -0
  86. package/src/delivery/cqt-css/sr-vic.css +63 -0
  87. package/src/delivery/index.ts +14 -23
  88. package/src/delivery/variant-css-map.ts +124 -0
  89. package/src/shared/layoutLimits.ts +76 -0
  90. package/src/shared/types.ts +84 -0
  91. 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: 7;
53
- blankWideWidthRem: 10;
54
- blankUnderlineWidthPx: 2;
55
- blankUnderlineWideWidthPx: 4;
56
- horizontalChoiceWidthPx: 170;
57
- horizontalChoiceWidthVw: 30;
58
- horizontalChoiceTileMinHeightRem: 11;
59
- horizontalChoiceContentMinHeightRem: 7.5;
60
- selectedImageMaxHeightRem: 4;
61
- choiceImageMaxHeightRem: 5;
62
- listenButtonSizePx: 128;
63
- stimulusMinColumnPx: 210;
64
- textMinColumnPx: 260;
65
- legendMaxChars: 120;
66
- choiceGroupGapRem: 0.5;
67
- choiceRowGapRem: 0.5;
68
- toggleButtonGapRem: 0.5;
69
- horizontalChoiceRadioTopMarginRem: 0.5;
70
- audioBlankTemplateMarginTopRem: 0.8;
71
- audioBlankTemplateMarginBottomRem: 1.8;
72
- audioInstructionsMaxWidthPx: 875;
73
- narrowHorizontalChoiceMaxWidthPx: 230;
74
- stimulusGridColumnGapRem: 2;
75
- stimulusGridRowGapRem: 0.7;
76
- stimulusSentenceMarginTopRem: 0.2;
77
- stimulusChoicesMarginTopRem: 0.9;
78
- tokenGridColumnGapRem: 1.5;
79
- tokenGridRowGapRem: 0.8;
80
- tokenTemplateMarginTopRem: 0.6;
81
- tokenInlineTokenGapRem: 0.35;
82
- tokenChoicesMarginTopRem: 0.2;
83
- inlineGridColumnGapRem: 1.5;
84
- inlineGridRowGapRem: 0.65;
85
- inlineTemplateMarginTopRem: 0.45;
86
- inlineChoicesMarginTopRem: 0.25;
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,oEAAoE;AACpE,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCxB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEX,wBAiDE"}
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: any, session: any) => "unanswered" | "correct" | "incorrect";
3
- export declare const getPartialScore: (_question: any, session: any) => 0 | 1;
4
- export declare const isComplete: (question: any, session: any, audioComplete?: boolean) => boolean;
5
- export declare const outcome: (question: any, session: any, env: any) => Promise<unknown>;
6
- export declare const createDefaultModel: (model?: any) => any;
7
- export declare const normalize: (question?: any) => any;
8
- export declare const normalizeSession: (s: any) => any;
9
- export declare const model: (question: any, session: any, env: any, updateSession?: any) => Promise<any>;
10
- export declare const createCorrectResponseSession: (question: any, env: any) => Promise<unknown>;
11
- export declare const validate: (model?: any, _config?: any) => any;
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":"AAQA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,eAAO,MAAM,cAAc,GAAI,UAAU,GAAG,EAAE,SAAS,GAAG,2CASzD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,WAAW,GAAG,EAAE,SAAS,GAAG,UAK3D,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,uBAAqB,YAY5E,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK,GAAG,qBA+BzD,CAAC;AAEL,eAAO,MAAM,kBAAkB,GAAI,QAAO,GAAQ,QAOhD,CAAC;AAEH,eAAO,MAAM,SAAS,GAAI,WAAU,GAAQ,QAAiC,CAAC;AAE9E,eAAO,MAAM,gBAAgB,GAAI,GAAG,GAAG,QAAe,CAAC;AAmEvD,eAAO,MAAM,KAAK,GAAU,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK,GAAG,EAAE,gBAAgB,GAAG,iBAiErF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,UAAU,GAAG,EAAE,KAAK,GAAG,qBAYnE,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,QAAO,GAAQ,EAAE,UAAS,GAAQ,QAuH1D,CAAC"}
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"}
@@ -1,6 +1,4 @@
1
- //#region src/controller/defaults.ts
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: "Selected answer in sentence",
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 || !session.choiceId) return "unanswered";
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(Boolean);
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
- session = session || {};
199
+ const safeSession = session || {};
201
200
  const safeEnv = env || {};
202
201
  const normalizedQuestion = normalize(question);
203
- const choices = await getOrderedChoices(normalizedQuestion, session, safeEnv, updateSession);
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, session);
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 = (model = {}, _config = {}) => {
249
+ var validate = (question = {}, _config = {}) => {
250
250
  const errors = {};
251
- if (model.promptEnabled) {
252
- const p = model.prompt?.trim() || "";
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 = model.interactionMode || "populate_blank";
256
- const n = countBlankTokens(model.template || "");
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(model.choices) ? model.choices : [];
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 = model.choiceMode || "text";
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 = model.correctChoiceId;
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 (model.hasAudio) {
292
- if (!!!model.audioUrl?.trim()) errors.audioUrl = "A playable audio URL is required when audio is enabled";
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 = model?.layoutLimits;
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 || limits[key] === "") continue;
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, isComplete, model, normalize, normalizeSession, outcome, validate };
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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"}