@pie-element/mc-populated-blank 0.3.0-next.4 → 0.3.0-next.5
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/dist/author/index.js +2260 -2213
- package/dist/author/index.js.map +1 -1
- package/dist/browser/author/index.js +7 -7
- package/dist/browser/{client-BAfWqkS6.js → client-BmZWrsT3.js} +866 -836
- package/dist/browser/client-BmZWrsT3.js.map +1 -0
- package/dist/browser/controller/index.js +1 -0
- package/dist/browser/controller/index.js.map +1 -1
- package/dist/browser/delivery/index.js +387 -388
- package/dist/browser/delivery/index.js.map +1 -1
- package/dist/browser/print/index.js +78 -80
- package/dist/browser/print/index.js.map +1 -1
- package/dist/controller/defaults.d.ts +1 -0
- package/dist/controller/index.js +1 -0
- package/dist/controller/index.js.map +1 -1
- package/dist/delivery/index.js +1751 -1688
- package/dist/delivery/index.js.map +1 -1
- package/dist/index.iife.js +6 -6
- package/dist/index.js +1751 -1688
- package/dist/index.js.map +1 -1
- package/dist/print/index.js +1405 -1342
- package/dist/print/index.js.map +1 -1
- package/dist/shared/types.d.ts +1 -0
- package/module/print.js +897 -869
- package/module/print.js.map +1 -1
- package/package.json +2 -2
- package/dist/browser/client-BAfWqkS6.js.map +0 -1
|
@@ -1,116 +1,114 @@
|
|
|
1
|
-
import { A as e, C as t,
|
|
1
|
+
import { A as e, C as t, D as n, E as r, F as i, M as a, N as o, P as s, S as c, T as l, _ as u, a as d, b as f, d as p, f as m, g as h, h as g, j as _, k as v, m as y, n as b, p as x, t as S, u as C, v as w, w as T, x as E } from "../client-BmZWrsT3.js";
|
|
2
2
|
//#region src/print/Print.svelte
|
|
3
|
-
var
|
|
3
|
+
var D = w("<div class=\"mb-4 prose prose-sm svelte-1fx5wwa\"></div>"), O = w("<div class=\"mb-2 prose prose-sm svelte-1fx5wwa\"></div>"), k = w("<img class=\"max-h-14 object-contain\"/>"), A = w("<div class=\"mb-4 prose prose-sm svelte-1fx5wwa\"><!> <span class=\"inline-block min-w-[6rem] border-b-2 border-gray-600 print:border-black px-1\"><!></span> <!></div>"), j = w("<div><strong>Audio:</strong> </div>"), M = w("<div><strong>Transcript:</strong> </div>"), N = w("<div class=\"text-sm mb-4\"><!> <!></div>"), ee = w("<span class=\"font-medium\"> </span> <!>", 1), te = w("<span class=\"font-medium\">(key)</span>"), ne = w("<!> <!>", 1), P = w("<li><!></li>"), F = w("<div class=\"p-4 print:p-0\"><!> <!> <!> <!> <ul class=\"text-sm list-disc pl-5\"></ul></div>"), I = {
|
|
4
4
|
hash: "svelte-1fx5wwa",
|
|
5
5
|
code: ".prose.svelte-1fx5wwa p {margin:0.5em 0;}.prose.svelte-1fx5wwa strong {font-weight:600;}"
|
|
6
6
|
};
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
let
|
|
7
|
+
function L(S, w) {
|
|
8
|
+
s(w, !0), C(S, I);
|
|
9
|
+
let L = b(w, "model", 7, null), R = e(""), z = e("");
|
|
10
10
|
t(() => {
|
|
11
|
-
|
|
11
|
+
v(R, L()?.prompt || "", !0), v(z, L()?.template || "", !0);
|
|
12
12
|
});
|
|
13
|
-
let
|
|
14
|
-
let e =
|
|
13
|
+
let B = a(() => {
|
|
14
|
+
let e = E(z).indexOf("{{blank}}");
|
|
15
15
|
return e < 0 ? {
|
|
16
|
-
before:
|
|
16
|
+
before: E(z),
|
|
17
17
|
after: ""
|
|
18
18
|
} : {
|
|
19
|
-
before:
|
|
20
|
-
after:
|
|
19
|
+
before: E(z).slice(0, e),
|
|
20
|
+
after: E(z).slice(e + 9)
|
|
21
21
|
};
|
|
22
|
-
}),
|
|
23
|
-
var
|
|
22
|
+
}), V = a(() => Array.isArray(L()?.choices) ? L().choices : []), H = a(() => L()?.choices?.find((e) => e.id === L()?.correctChoiceId)), U = a(() => L()?.choiceMode || "text"), W = a(() => L()?.interactionMode === "audio_mc_only"), G = a(() => L()?.printAnswerKey !== !1);
|
|
23
|
+
var K = {
|
|
24
24
|
get model() {
|
|
25
|
-
return
|
|
25
|
+
return L();
|
|
26
26
|
},
|
|
27
|
-
set model(
|
|
28
|
-
|
|
27
|
+
set model(e = null) {
|
|
28
|
+
L(e), _();
|
|
29
29
|
}
|
|
30
|
-
},
|
|
31
|
-
var t =
|
|
32
|
-
|
|
30
|
+
}, q = F(), J = T(q), Y = (e) => {
|
|
31
|
+
var t = D();
|
|
32
|
+
p(t, () => E(R), !0), i(t), h(e, t);
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
y(J, (e) => {
|
|
35
|
+
E(R) && L()?.promptEnabled !== !1 && e(Y);
|
|
36
36
|
});
|
|
37
|
-
var X = n(
|
|
38
|
-
var t =
|
|
39
|
-
|
|
37
|
+
var X = n(J, 2), re = (e) => {
|
|
38
|
+
var t = O();
|
|
39
|
+
p(t, () => L().sentenceHtml, !0), i(t), h(e, t);
|
|
40
40
|
};
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
y(X, (e) => {
|
|
42
|
+
L()?.sentenceHtml && e(re);
|
|
43
43
|
});
|
|
44
|
-
var Z = n(X, 2),
|
|
45
|
-
var t =
|
|
46
|
-
|
|
47
|
-
var
|
|
48
|
-
var t =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}),
|
|
52
|
-
},
|
|
53
|
-
var t =
|
|
54
|
-
|
|
44
|
+
var Z = n(X, 2), ie = (e) => {
|
|
45
|
+
var t = A(), r = T(t);
|
|
46
|
+
p(r, () => E(B).before);
|
|
47
|
+
var a = n(r, 2), o = T(a), s = (e) => {
|
|
48
|
+
var t = k();
|
|
49
|
+
c(() => {
|
|
50
|
+
d(t, "src", E(H).imageUrl), d(t, "alt", E(H).imageAlt || "");
|
|
51
|
+
}), h(e, t);
|
|
52
|
+
}, m = (e) => {
|
|
53
|
+
var t = u();
|
|
54
|
+
p(l(t), () => E(H).labelHtml), h(e, t);
|
|
55
55
|
}, g = (e) => {
|
|
56
|
-
|
|
56
|
+
h(e, f("________"));
|
|
57
57
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}),
|
|
58
|
+
y(o, (e) => {
|
|
59
|
+
E(G) && E(U) === "image" && E(H)?.imageUrl ? e(s) : E(G) && E(H)?.labelHtml ? e(m, 1) : e(g, -1);
|
|
60
|
+
}), i(a), p(n(a, 2), () => E(B).after), i(t), h(e, t);
|
|
61
61
|
};
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
y(Z, (e) => {
|
|
63
|
+
E(W) || e(ie);
|
|
64
64
|
});
|
|
65
|
-
var Q = n(Z, 2),
|
|
66
|
-
var t =
|
|
67
|
-
var t =
|
|
68
|
-
|
|
65
|
+
var Q = n(Z, 2), ae = (e) => {
|
|
66
|
+
var t = N(), r = T(t), a = (e) => {
|
|
67
|
+
var t = j(), r = n(T(t));
|
|
68
|
+
i(t), c(() => g(r, ` ${L().audioUrl ?? ""}`)), h(e, t);
|
|
69
69
|
};
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
y(r, (e) => {
|
|
71
|
+
L()?.audioUrl && e(a);
|
|
72
72
|
});
|
|
73
|
-
var
|
|
74
|
-
var t =
|
|
75
|
-
|
|
73
|
+
var o = n(r, 2), s = (e) => {
|
|
74
|
+
var t = M(), r = n(T(t));
|
|
75
|
+
i(t), c(() => g(r, ` ${L().audioTranscript ?? ""}`)), h(e, t);
|
|
76
76
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}),
|
|
77
|
+
y(o, (e) => {
|
|
78
|
+
L()?.audioTranscript && e(s);
|
|
79
|
+
}), i(t), h(e, t);
|
|
80
80
|
};
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
y(Q, (e) => {
|
|
82
|
+
L()?.hasAudio && e(ae);
|
|
83
83
|
});
|
|
84
84
|
var $ = n(Q, 2);
|
|
85
|
-
return
|
|
86
|
-
var
|
|
87
|
-
var
|
|
88
|
-
|
|
89
|
-
var l = n(i, 2), u = (e) => {
|
|
90
|
-
m(e, d("(key)"));
|
|
85
|
+
return m($, 21, () => E(V), x, (e, t) => {
|
|
86
|
+
var a = P(), o = T(a), s = (e) => {
|
|
87
|
+
var i = ee(), a = l(i), o = r(a, !0), s = n(a, 2), u = (e) => {
|
|
88
|
+
h(e, f("(key)"));
|
|
91
89
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}),
|
|
95
|
-
},
|
|
96
|
-
var r =
|
|
97
|
-
|
|
90
|
+
y(s, (e) => {
|
|
91
|
+
E(G) && E(t).id === L()?.correctChoiceId && e(u);
|
|
92
|
+
}), c(() => g(o, E(t).id)), h(e, i);
|
|
93
|
+
}, u = (e) => {
|
|
94
|
+
var r = ne(), i = l(r);
|
|
95
|
+
p(i, () => E(t).labelHtml || "");
|
|
98
96
|
var a = n(i, 2), o = (e) => {
|
|
99
|
-
|
|
97
|
+
h(e, te());
|
|
100
98
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}),
|
|
99
|
+
y(a, (e) => {
|
|
100
|
+
E(G) && E(t).id === L()?.correctChoiceId && e(o);
|
|
101
|
+
}), h(e, r);
|
|
104
102
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}),
|
|
108
|
-
}),
|
|
103
|
+
y(o, (e) => {
|
|
104
|
+
E(U) === "image" ? e(s) : e(u, -1);
|
|
105
|
+
}), i(a), h(e, a);
|
|
106
|
+
}), i($), i(q), h(S, q), o(K);
|
|
109
107
|
}
|
|
110
|
-
|
|
108
|
+
S(L, { model: { type: "Object" } }, [], []);
|
|
111
109
|
//#endregion
|
|
112
110
|
//#region src/print/index.ts
|
|
113
|
-
var
|
|
111
|
+
var R = L.element, z = class extends R {
|
|
114
112
|
_model = null;
|
|
115
113
|
set model(e) {
|
|
116
114
|
this._model = e, super.model = e;
|
|
@@ -120,6 +118,6 @@ var z = R.element, B = class extends z {
|
|
|
120
118
|
}
|
|
121
119
|
};
|
|
122
120
|
//#endregion
|
|
123
|
-
export {
|
|
121
|
+
export { z as default };
|
|
124
122
|
|
|
125
123
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/print/Print.svelte","../../../src/print/index.ts"],"sourcesContent":["<svelte:options\n customElement={{\n shadow: 'none',\n props: {\n model: { type: 'Object' },\n },\n }}\n/>\n\n<script lang=\"ts\">\nconst BLANK_TOKEN = '{{blank}}';\n\nlet { model = null }: { model?: any } = $props();\n\nlet prompt = $state('');\nlet template = $state('');\n$effect(() => {\n prompt = model?.prompt || '';\n template = model?.template || '';\n});\n\nconst parts = $derived.by(() => {\n const idx = template.indexOf(BLANK_TOKEN);\n if (idx < 0) return { before: template, after: '' };\n return { before: template.slice(0, idx), after: template.slice(idx + BLANK_TOKEN.length) };\n});\n\nconst choices = $derived(Array.isArray(model?.choices) ? model.choices : []);\nconst correct = $derived(model?.choices?.find((c: any) => c.id === model?.correctChoiceId));\nconst mode = $derived(model?.choiceMode || 'text');\nconst isAudioOnlyMode = $derived(model?.interactionMode === 'audio_mc_only');\nconst showAnswerKey = $derived(model?.printAnswerKey !== false);\n</script>\n\n<div class=\"p-4 print:p-0\">\n {#if prompt && model?.promptEnabled !== false}\n <div class=\"mb-4 prose prose-sm\">{@html prompt}</div>\n {/if}\n\n {#if model?.sentenceHtml}\n <div class=\"mb-2 prose prose-sm\">{@html model.sentenceHtml}</div>\n {/if}\n\n {#if !isAudioOnlyMode}\n <div class=\"mb-4 prose prose-sm\">\n {@html parts.before}\n <span class=\"inline-block min-w-[6rem] border-b-2 border-gray-600 print:border-black px-1\">\n {#if showAnswerKey && mode === 'image' && correct?.imageUrl}\n <img src={correct.imageUrl} alt={correct.imageAlt || ''} class=\"max-h-14 object-contain\" />\n {:else if showAnswerKey && correct?.labelHtml}\n {@html correct.labelHtml}\n {:else}\n ________\n {/if}\n </span>\n {@html parts.after}\n </div>\n {/if}\n\n {#if model?.hasAudio}\n <div class=\"text-sm mb-4\">\n {#if model?.audioUrl}\n <div><strong>Audio:</strong> {model.audioUrl}</div>\n {/if}\n {#if model?.audioTranscript}\n <div><strong>Transcript:</strong> {model.audioTranscript}</div>\n {/if}\n </div>\n {/if}\n\n <ul class=\"text-sm list-disc pl-5\">\n {#each choices as c}\n <li>\n {#if mode === 'image'}\n <span class=\"font-medium\">{c.id}</span>\n {#if showAnswerKey && c.id === model?.correctChoiceId}(key){/if}\n {:else}\n {@html c.labelHtml || ''}\n {#if showAnswerKey && c.id === model?.correctChoiceId}\n <span class=\"font-medium\"> (key)</span>\n {/if}\n {/if}\n </li>\n {/each}\n </ul>\n</div>\n\n<style>\n .prose :global(p) {\n margin: 0.5em 0;\n }\n .prose :global(strong) {\n font-weight: 600;\n }\n</style>\n","import PrintComponent from './Print.svelte';\n\nconst SvelteElementClass = (PrintComponent as any).element;\n\nclass McPopulatedBlankPrint extends SvelteElementClass {\n private _model: any = null;\n\n set model(m: any) {\n this._model = m;\n super.model = m;\n }\n\n get model() {\n return this._model;\n }\n}\n\nexport default McPopulatedBlankPrint;\n"],"mappings":";;;;;;iBASA;;CACA,IAEI,IAAO,EAAA,GAAA,SAAA,GAAG,IAAI,GAEd,IAAS,EAAO,EAAE,GAClB,IAAW,EAAO,EAAE;CACxB,QAAc;IACZ,GAAS,EAAK,CAAA,EAAE,UAAU,IAAE,EAAA,KAC5B,GAAW,EAAK,CAAA,EAAE,YAAY,IAAE,EAAA;CAClC,CAAC;CAED,IAAM,IAAK,QAAqB;EAC9B,IAAM,IAAG,EAAG,CAAQ,CAAA,CAAC,QAAQ,WAAW;EAExC,OADI,IAAM,IAAS;GAAG,QAAM,EAAE,CAAQ;GAAE,OAAO;EAAE,IAC3C;GAAG,QAAM,EAAE,CAAQ,CAAA,CAAC,MAAM,GAAG,CAAG;GAAG,OAAK,EAAE,CAAQ,CAAA,CAAC,MAAM,IAAM,CAAkB;;CACzF,CAAC,GAEK,IAAO,QAAY,MAAM,QAAQ,EAAK,CAAA,EAAE,OAAO,IAAI,EAAK,CAAA,CAAC,UAAO,CAAA,CAAA,GAChE,IAAO,QAAY,EAAK,CAAA,EAAE,SAAS,MAAM,MAAW,EAAE,OAAO,EAAK,CAAA,EAAE,eAAe,CAAA,GACnF,IAAI,QAAY,EAAK,CAAA,EAAE,cAAc,MAAM,GAC3C,IAAe,QAAY,EAAK,CAAA,EAAE,oBAAoB,eAAe,GACrE,IAAa,QAAY,EAAK,CAAA,EAAE,mBAAmB,EAAK;;;;;gBAnBhD,MAAI;;;IAsBjB,IAAG,EAAA,SAAH,CAAG;MAEC,IAAG,EAAA;IAAH,SAAG,EAAoC,CAAM,GAAA,EAAA,KAA7C,CAAG,QAAH,CAAG;;;IADD,CAAM,KAAI,EAAK,CAAA,EAAE,kBAAkB,MAAK,EAAA,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/print/Print.svelte","../../../src/print/index.ts"],"sourcesContent":["<svelte:options\n customElement={{\n shadow: 'none',\n props: {\n model: { type: 'Object' },\n },\n }}\n/>\n\n<script lang=\"ts\">\nconst BLANK_TOKEN = '{{blank}}';\n\nlet { model = null }: { model?: any } = $props();\n\nlet prompt = $state('');\nlet template = $state('');\n$effect(() => {\n prompt = model?.prompt || '';\n template = model?.template || '';\n});\n\nconst parts = $derived.by(() => {\n const idx = template.indexOf(BLANK_TOKEN);\n if (idx < 0) return { before: template, after: '' };\n return { before: template.slice(0, idx), after: template.slice(idx + BLANK_TOKEN.length) };\n});\n\nconst choices = $derived(Array.isArray(model?.choices) ? model.choices : []);\nconst correct = $derived(model?.choices?.find((c: any) => c.id === model?.correctChoiceId));\nconst mode = $derived(model?.choiceMode || 'text');\nconst isAudioOnlyMode = $derived(model?.interactionMode === 'audio_mc_only');\nconst showAnswerKey = $derived(model?.printAnswerKey !== false);\n</script>\n\n<div class=\"p-4 print:p-0\">\n {#if prompt && model?.promptEnabled !== false}\n <div class=\"mb-4 prose prose-sm\">{@html prompt}</div>\n {/if}\n\n {#if model?.sentenceHtml}\n <div class=\"mb-2 prose prose-sm\">{@html model.sentenceHtml}</div>\n {/if}\n\n {#if !isAudioOnlyMode}\n <div class=\"mb-4 prose prose-sm\">\n {@html parts.before}\n <span class=\"inline-block min-w-[6rem] border-b-2 border-gray-600 print:border-black px-1\">\n {#if showAnswerKey && mode === 'image' && correct?.imageUrl}\n <img src={correct.imageUrl} alt={correct.imageAlt || ''} class=\"max-h-14 object-contain\" />\n {:else if showAnswerKey && correct?.labelHtml}\n {@html correct.labelHtml}\n {:else}\n ________\n {/if}\n </span>\n {@html parts.after}\n </div>\n {/if}\n\n {#if model?.hasAudio}\n <div class=\"text-sm mb-4\">\n {#if model?.audioUrl}\n <div><strong>Audio:</strong> {model.audioUrl}</div>\n {/if}\n {#if model?.audioTranscript}\n <div><strong>Transcript:</strong> {model.audioTranscript}</div>\n {/if}\n </div>\n {/if}\n\n <ul class=\"text-sm list-disc pl-5\">\n {#each choices as c}\n <li>\n {#if mode === 'image'}\n <span class=\"font-medium\">{c.id}</span>\n {#if showAnswerKey && c.id === model?.correctChoiceId}(key){/if}\n {:else}\n {@html c.labelHtml || ''}\n {#if showAnswerKey && c.id === model?.correctChoiceId}\n <span class=\"font-medium\"> (key)</span>\n {/if}\n {/if}\n </li>\n {/each}\n </ul>\n</div>\n\n<style>\n .prose :global(p) {\n margin: 0.5em 0;\n }\n .prose :global(strong) {\n font-weight: 600;\n }\n</style>\n","import PrintComponent from './Print.svelte';\n\nconst SvelteElementClass = (PrintComponent as any).element;\n\nclass McPopulatedBlankPrint extends SvelteElementClass {\n private _model: any = null;\n\n set model(m: any) {\n this._model = m;\n super.model = m;\n }\n\n get model() {\n return this._model;\n }\n}\n\nexport default McPopulatedBlankPrint;\n"],"mappings":"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"}
|
package/dist/controller/index.js
CHANGED
|
@@ -57,6 +57,7 @@ var defaults_default = {
|
|
|
57
57
|
uiText: {
|
|
58
58
|
answerChoices: "Answer choices",
|
|
59
59
|
selectedAnswerInSentence: "blank",
|
|
60
|
+
blankPreSelectionHint: "The answer you choose will appear on the blank line above.",
|
|
60
61
|
showCorrectAnswer: "Show correct answer",
|
|
61
62
|
hideCorrectAnswer: "Hide correct answer",
|
|
62
63
|
clickToEnableAutoplay: "Click to enable audio autoplay",
|
|
@@ -1 +1 @@
|
|
|
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 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 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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"}
|
|
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 blankPreSelectionHint: 'The answer you choose will appear on the blank line above.',\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 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 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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"}
|