@pie-element/mc-populated-blank 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/package.json +3 -14
  2. package/dist/controller/index.test.d.ts +0 -63
  3. package/dist/controller/index.test.d.ts.map +0 -1
  4. package/dist/delivery/AudioPlayer.test.d.ts +0 -2
  5. package/dist/delivery/AudioPlayer.test.d.ts.map +0 -1
  6. package/dist/delivery/ChoiceRow.test.d.ts +0 -2
  7. package/dist/delivery/ChoiceRow.test.d.ts.map +0 -1
  8. package/dist/delivery/ClozeMarker.test.d.ts +0 -2
  9. package/dist/delivery/ClozeMarker.test.d.ts.map +0 -1
  10. package/dist/delivery/McPopulatedBlank.transcript.test.d.ts +0 -2
  11. package/dist/delivery/McPopulatedBlank.transcript.test.d.ts.map +0 -1
  12. package/dist/delivery/computeAudioMode.test.d.ts +0 -2
  13. package/dist/delivery/computeAudioMode.test.d.ts.map +0 -1
  14. package/dist/delivery/computeChoiceCorrectness.test.d.ts +0 -19
  15. package/dist/delivery/computeChoiceCorrectness.test.d.ts.map +0 -1
  16. package/dist/delivery/computeDisplayState.test.d.ts +0 -38
  17. package/dist/delivery/computeDisplayState.test.d.ts.map +0 -1
  18. package/dist/delivery/computeLayoutProfile.test.d.ts +0 -2
  19. package/dist/delivery/computeLayoutProfile.test.d.ts.map +0 -1
  20. package/dist/delivery/computeLayoutStyle.test.d.ts +0 -2
  21. package/dist/delivery/computeLayoutStyle.test.d.ts.map +0 -1
  22. package/src/author/Author.svelte +0 -41
  23. package/src/author/index.ts +0 -28
  24. package/src/controller/defaults.ts +0 -61
  25. package/src/controller/index.test.ts +0 -428
  26. package/src/controller/index.ts +0 -372
  27. package/src/delivery/AudioPlayer.svelte +0 -246
  28. package/src/delivery/AudioPlayer.test.ts +0 -149
  29. package/src/delivery/ChoiceRow.svelte +0 -222
  30. package/src/delivery/ChoiceRow.test.ts +0 -179
  31. package/src/delivery/ClozeMarker.svelte +0 -83
  32. package/src/delivery/ClozeMarker.test.ts +0 -128
  33. package/src/delivery/McPopulatedBlank.svelte +0 -739
  34. package/src/delivery/McPopulatedBlank.transcript.test.ts +0 -154
  35. package/src/delivery/computeAudioMode.test.ts +0 -40
  36. package/src/delivery/computeAudioMode.ts +0 -11
  37. package/src/delivery/computeChoiceCorrectness.test.ts +0 -120
  38. package/src/delivery/computeChoiceCorrectness.ts +0 -42
  39. package/src/delivery/computeDisplayState.test.ts +0 -274
  40. package/src/delivery/computeDisplayState.ts +0 -101
  41. package/src/delivery/computeLayoutProfile.test.ts +0 -163
  42. package/src/delivery/computeLayoutProfile.ts +0 -72
  43. package/src/delivery/computeLayoutStyle.test.ts +0 -168
  44. package/src/delivery/computeLayoutStyle.ts +0 -128
  45. package/src/delivery/cqt-css/sel-r1-base.css +0 -59
  46. package/src/delivery/cqt-css/sel-r1-g-stem.css +0 -14
  47. package/src/delivery/cqt-css/sel-r1-gg-plus.css +0 -12
  48. package/src/delivery/cqt-css/sel-r1-ggplus.css +0 -17
  49. package/src/delivery/cqt-css/sel-r1-gplusggg.css +0 -25
  50. package/src/delivery/cqt-css/sel-r1-plusggg.css +0 -17
  51. package/src/delivery/cqt-css/sel-r1-s3.css +0 -85
  52. package/src/delivery/cqt-css/sel-vic.css +0 -99
  53. package/src/delivery/cqt-css/sr-vic.css +0 -63
  54. package/src/delivery/index.ts +0 -94
  55. package/src/delivery/variant-css-map.ts +0 -124
  56. package/src/index.iife.ts +0 -13
  57. package/src/index.ts +0 -1
  58. package/src/print/Print.svelte +0 -95
  59. package/src/print/index.ts +0 -18
  60. package/src/shared/layoutLimits.ts +0 -76
  61. package/src/shared/types.ts +0 -84
  62. package/src/types/css-raw.d.ts +0 -4
@@ -1,739 +0,0 @@
1
- <svelte:options
2
- customElement={{
3
- shadow: 'none',
4
- props: {
5
- model: { type: 'Object' },
6
- session: { type: 'Object' },
7
- options: { type: 'Object' },
8
- },
9
- }}
10
- />
11
-
12
- <script lang="ts">
13
- import { color } from '@pie-lib/styling-svelte';
14
- import { forwardSessionChange, resolveDeliveryHost } from '@pie-lib/delivery-events-svelte';
15
- import AudioPlayer from './AudioPlayer.svelte';
16
- import ClozeMarker from './ClozeMarker.svelte';
17
- import ChoiceRow from './ChoiceRow.svelte';
18
- import { computeChoiceCorrectness } from './computeChoiceCorrectness';
19
- import { computeLayoutProfile } from './computeLayoutProfile';
20
- import { computeLayoutStyle, DEFAULT_LAYOUT_LIMITS } from './computeLayoutStyle';
21
- import {
22
- computeFeatureAudioSkin,
23
- computeDisplayChoiceId,
24
- computeResultText,
25
- computeLegendText,
26
- } from './computeDisplayState';
27
- import {
28
- ensureVariantCssInjected,
29
- getVariantCssConfig,
30
- getVariantRootClass,
31
- } from './variant-css-map';
32
-
33
- const BLANK_TOKEN = '{{blank}}';
34
- const DEFAULT_UI_TEXT = {
35
- answerChoices: 'Answer choices',
36
- selectedAnswerInSentence: 'Selected answer in sentence',
37
- showCorrectAnswer: 'Show correct answer',
38
- hideCorrectAnswer: 'Hide correct answer',
39
- clickToEnableAutoplay: 'Click to enable audio autoplay',
40
- audioResourceUnavailable: 'Audio is enabled but no playable audio URL is configured.',
41
- transcriptLabel: 'Transcript',
42
- } as const;
43
-
44
- let { model, session } = $props<{ model?: any; session?: any; options?: any }>();
45
- let localChoiceId = $state('');
46
- let toggleCorrectAnswerButtonEl = $state<HTMLButtonElement | null>(null);
47
- let choicesGroupEl = $state<HTMLDivElement | null>(null);
48
- let rootEl = $state<HTMLDivElement | null>(null);
49
- let ancestorHasTranscriptClass = $state(false);
50
- const instanceId = `mc-populated-blank-${Math.random().toString(36).slice(2, 10)}`;
51
-
52
- // ---------------------------------------------------------------------------
53
- // Cluster: modeFlags — delivery mode, correctness, and answer-reveal state
54
- // Feeds: shouldShowCorrectAnswerToggle, choiceState, a11y
55
- // ---------------------------------------------------------------------------
56
- const deliveryMode = $derived((model?.mode || model?.env?.mode || '').toString());
57
- const isEvaluateMode = $derived(deliveryMode === 'evaluate');
58
- const correctness = $derived(model?.correctness);
59
- const isResponseCorrect = $derived(
60
- typeof model?.responseCorrect === 'boolean' ? model.responseCorrect : correctness === 'correct'
61
- );
62
- const isCorrect = $derived(correctness === 'correct');
63
- const isIncorrect = $derived(correctness === 'incorrect');
64
-
65
- let showCorrectAnswer = $state(false);
66
- const shouldShowCorrectAnswerToggle = $derived(
67
- isEvaluateMode && !isResponseCorrect && !!model?.correctChoiceId
68
- );
69
-
70
- // ---------------------------------------------------------------------------
71
- // Cluster: layoutConfig — profile limits, blank sizing, choice layout, audio mode
72
- // Feeds: layout (rootStyle, blankWidth, blankBorderWidth, legendMaxChars), audioMode, choiceState
73
- // ---------------------------------------------------------------------------
74
- const layoutProfile = $derived(model?.layoutProfile || '');
75
- const layoutProfileFlags = $derived(
76
- computeLayoutProfile({
77
- interactionMode: model?.interactionMode,
78
- template: model?.template,
79
- choiceLayout: model?.choiceLayout,
80
- layoutProfile,
81
- hasAudio: model?.hasAudio,
82
- useFeatureButtonAudio: model?.useFeatureButtonAudio,
83
- })
84
- );
85
- const isAudioOnlyMode = $derived(layoutProfileFlags.isAudioOnlyMode);
86
- const isBlankOnlyTemplate = $derived(layoutProfileFlags.isBlankOnlyTemplate);
87
- const isHorizontalChoices = $derived(layoutProfileFlags.isHorizontalChoices);
88
- const hasInlineSentenceAudioLayout = $derived(layoutProfileFlags.hasInlineSentenceAudioLayout);
89
- const useFeatureButtonAudio = $derived(layoutProfileFlags.useFeatureButtonAudio);
90
- const showVisibleTranscript = $derived(ancestorHasTranscriptClass);
91
- const correctAnswerStyleVars = $derived.by(() =>
92
- [
93
- `--pie-correct-answer-toggle-label-color:${color.text()}`,
94
- `--pie-correct-answer-toggle-icon-open-bg:${color.tertiaryLight()}`,
95
- `--pie-correct-answer-toggle-icon-closed-bg:${color.backgroundDark()}`,
96
- `--pie-correct-answer-toggle-icon-glyph-color:${color.tertiary()}`,
97
- `--pie-correct-answer-choice-hover-bg:${color.backgroundDark()}`,
98
- `--pie-correct-answer-choice-selected-bg:${color.secondaryBackground()}`,
99
- `--pie-correct-answer-choice-correct-bg:${color.correctSecondary()}`,
100
- `--pie-correct-answer-choice-incorrect-bg:${color.incorrectSecondary()}`,
101
- `--pie-correct-answer-choice-correct-border:${color.correctTertiary()}`,
102
- `--pie-correct-answer-choice-incorrect-border:${color.incorrectWithIcon()}`,
103
- `--pie-correct-answer-feedback-correct-bg:${color.correctWithIcon()}`,
104
- `--pie-correct-answer-feedback-incorrect-bg:${color.incorrectWithIcon()}`,
105
- `--pie-correct-answer-feedback-glyph-color:${color.white()}`,
106
- ].join(';')
107
- );
108
- const layout = $derived(
109
- computeLayoutStyle({
110
- layoutProfile,
111
- isBlankOnlyTemplate,
112
- configuredLimits: model?.layoutLimits,
113
- customProfilePresets: model?.layoutProfilePresets,
114
- correctAnswerStyleVars,
115
- })
116
- );
117
-
118
- // ---------------------------------------------------------------------------
119
- // Cluster: choiceState — selection, display choice, per-choice correctness, result text
120
- // Feeds: template blank display, choice rendering, fieldset, result sr-only text
121
- // ---------------------------------------------------------------------------
122
- const choices = $derived(Array.isArray(model?.choices) ? model.choices : []);
123
- const choiceMode = $derived(model?.choiceMode || 'text');
124
- const selectedId = $derived(session?.choiceId || localChoiceId || '');
125
- const radioGroupName = $derived(`${instanceId}-choice-group-${model?.id || '1'}`);
126
- const displayChoiceId = $derived(
127
- computeDisplayChoiceId({
128
- selectedId,
129
- isEvaluateMode,
130
- showCorrectAnswer,
131
- alwaysShowCorrect: !!model?.alwaysShowCorrect,
132
- correctChoiceId: String(model?.correctChoiceId || ''),
133
- })
134
- );
135
- const displayChoice = $derived.by(() => choices.find((c: any) => c.id === displayChoiceId));
136
- const displayChoiceLabelHtml = $derived.by(() => String(displayChoice?.labelHtml || ''));
137
- const resultText = $derived(
138
- computeResultText({ isEvaluateMode, showCorrectAnswer, isCorrect, isIncorrect, selectedId })
139
- );
140
- const choiceCorrectnessById = $derived(
141
- computeChoiceCorrectness({
142
- isEvaluateMode,
143
- correctChoiceId: String(model?.correctChoiceId || ''),
144
- selectedId: String(selectedId || ''),
145
- showCorrectAnswer,
146
- })
147
- );
148
-
149
- // ---------------------------------------------------------------------------
150
- // Cluster: a11y — stable IDs, aria labelling, described-by relationships
151
- // Feeds: fieldset legend, radiogroup labelling, template described-by
152
- // ---------------------------------------------------------------------------
153
- const uiText = $derived.by(() => ({
154
- ...DEFAULT_UI_TEXT,
155
- ...(model?.uiText || {}),
156
- }));
157
- const promptId = $derived(`${instanceId}-prompt`);
158
- const transcriptId = $derived(`${instanceId}-transcript`);
159
- const legendId = $derived(`${instanceId}-choices-legend`);
160
- const resultId = $derived(`${instanceId}-result`);
161
- const legendText = $derived(
162
- computeLegendText({
163
- prompt: model?.prompt || '',
164
- legendMaxChars: layout.legendMaxChars,
165
- answerChoicesLabel: uiText.answerChoices,
166
- })
167
- );
168
- const choicesGroupLabelledBy = $derived(model?.prompt ? promptId : undefined);
169
- const choicesGroupAriaLabel = $derived.by(() => {
170
- if (model?.prompt) return undefined;
171
- const explicit = String(model?.choiceGroupLabel || '').trim();
172
- if (explicit) return explicit;
173
- return legendText;
174
- });
175
- const templateDescribedBy = $derived.by(() => {
176
- const ids: string[] = [];
177
- if (model?.prompt) ids.push(promptId);
178
- if (model?.hasAudio && model?.audioTranscript) ids.push(transcriptId);
179
- return ids.length ? ids.join(' ') : undefined;
180
- });
181
-
182
- // ---------------------------------------------------------------------------
183
- // Misc — locale, audio error, template parsing, variant CSS, style strings
184
- // ---------------------------------------------------------------------------
185
- const lang = $derived.by(() => {
186
- const locale = model?.locale || '';
187
- return locale ? locale.slice(0, 2) : 'en';
188
- });
189
- const variantCssConfig = $derived(getVariantCssConfig(model?.customType));
190
- const variantRootClass = $derived(getVariantRootClass(model?.customType));
191
- const templateParts = $derived.by(() => {
192
- const t = model?.template || '';
193
- const idx = t.indexOf(BLANK_TOKEN);
194
- if (idx < 0) return { before: t, after: '' };
195
- return {
196
- before: t.slice(0, idx),
197
- after: t.slice(idx + BLANK_TOKEN.length),
198
- };
199
- });
200
-
201
- function emitSession(updatedSession: any, sourceEl?: HTMLElement | null) {
202
- forwardSessionChange({
203
- sourceEl,
204
- fallbackSelector: 'mc-populated-blank',
205
- component: 'mc-populated-blank',
206
- session: updatedSession,
207
- complete: !!updatedSession?.choiceId,
208
- });
209
- }
210
-
211
- function onRadioChange(e: Event) {
212
- const input = e.target as HTMLInputElement;
213
- if (!input.checked) return;
214
- const choiceId = input.value;
215
- localChoiceId = choiceId;
216
- const updatedSession = {
217
- ...session,
218
- id: session?.id || model?.id || '1',
219
- element: 'mc-populated-blank',
220
- choiceId,
221
- };
222
- emitSession(updatedSession, input);
223
- }
224
-
225
- function toggleCorrectAnswer() {
226
- if (!shouldShowCorrectAnswerToggle) return;
227
- showCorrectAnswer = !showCorrectAnswer;
228
- }
229
-
230
- $effect(() => {
231
- if (!isEvaluateMode || isResponseCorrect) {
232
- showCorrectAnswer = false;
233
- }
234
- });
235
-
236
- function onRadioGroupKeydown(e: KeyboardEvent) {
237
- if (model?.disabled) return;
238
- const key = e.key;
239
- if (!['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'].includes(key)) return;
240
- e.preventDefault();
241
- const container = e.currentTarget as HTMLElement;
242
- const inputs = Array.from(
243
- container.querySelectorAll('input[type="radio"]:not([disabled])')
244
- ) as HTMLInputElement[];
245
- if (inputs.length === 0) return;
246
- let idx = inputs.findIndex((i) => i.checked);
247
- if (key === 'ArrowDown' || key === 'ArrowRight') {
248
- idx = idx < 0 ? 0 : (idx + 1) % inputs.length;
249
- } else {
250
- idx = idx < 0 ? inputs.length - 1 : (idx - 1 + inputs.length) % inputs.length;
251
- }
252
- const next = inputs[idx];
253
- next.checked = true;
254
- next.dispatchEvent(new Event('change', { bubbles: true }));
255
- next.focus();
256
- }
257
-
258
- const featureAudioSkin = $derived(
259
- computeFeatureAudioSkin({
260
- locale: model?.locale,
261
- audioButtonSkin: model?.audioButtonSkin,
262
- audioButtonSkinsByLocale: model?.audioButtonSkinsByLocale,
263
- })
264
- );
265
-
266
- function onAudioStarted() {
267
- resolveDeliveryHost(rootEl, { fallbackSelector: 'mc-populated-blank' })?.onAudioStarted?.();
268
- }
269
-
270
- function onAudioEnded() {
271
- resolveDeliveryHost(rootEl, { fallbackSelector: 'mc-populated-blank' })?.onAudioEnded?.();
272
- }
273
-
274
- $effect(() => {
275
- if (session?.choiceId) {
276
- localChoiceId = session.choiceId;
277
- }
278
- });
279
-
280
- $effect(() => {
281
- const btn = toggleCorrectAnswerButtonEl;
282
- if (!btn) return;
283
- btn.addEventListener('click', toggleCorrectAnswer);
284
- return () => btn.removeEventListener('click', toggleCorrectAnswer);
285
- });
286
-
287
- // Delegated listeners on the choices group — two events with a radio-guard on change.
288
- $effect(() => {
289
- const group = choicesGroupEl;
290
- if (!group) return;
291
- const handleChange = (e: Event) => {
292
- const target = e.target as HTMLInputElement | null;
293
- if (!target || target.type !== 'radio') return;
294
- onRadioChange(e);
295
- };
296
- const handleKeydown = (e: KeyboardEvent) => onRadioGroupKeydown(e);
297
- group.addEventListener('change', handleChange);
298
- group.addEventListener('keydown', handleKeydown);
299
- return () => {
300
- group.removeEventListener('change', handleChange);
301
- group.removeEventListener('keydown', handleKeydown);
302
- };
303
- });
304
-
305
- $effect(() => {
306
- ensureVariantCssInjected(variantCssConfig);
307
- });
308
-
309
- $effect(() => {
310
- const el = rootEl;
311
- if (!el) return;
312
-
313
- const check = () => {
314
- ancestorHasTranscriptClass = !!el.closest('.rli-with-audio-transcript');
315
- };
316
- check();
317
-
318
- const observer = new MutationObserver(check);
319
- // Walk up and observe each ancestor for class changes
320
- let node: Element | null = el.parentElement;
321
- while (node) {
322
- observer.observe(node, { attributes: true, attributeFilter: ['class'] });
323
- node = node.parentElement;
324
- }
325
- return () => observer.disconnect();
326
- });
327
- </script>
328
-
329
- <div
330
- bind:this={rootEl}
331
- class={`p-4 mc-populated-blank-root pie-element pie-element-mc-populated-blank pie-delivery-root layout-${layoutProfile} ${variantRootClass} ${hasInlineSentenceAudioLayout ? 'has-inline-audio' : ''}`}
332
- lang={lang}
333
- style={layout.rootStyle}
334
- >
335
- {#if model?.prompt}
336
- <div class="mb-4 prose pie-prompt" id={promptId}>{@html model.prompt}</div>
337
- {/if}
338
-
339
- {#if model?.audioTranscript}
340
- <p
341
- class={`text-sm mb-3 text-gray-700 text-center pie-audio-transcript ${showVisibleTranscript ? '' : 'sr-only'}`}
342
- id={transcriptId}
343
- >
344
- {model.audioTranscript}
345
- </p>
346
- {/if}
347
-
348
- {#if shouldShowCorrectAnswerToggle}
349
- <div class="pie-correct-answer-toggle-row">
350
- <button
351
- bind:this={toggleCorrectAnswerButtonEl}
352
- type="button"
353
- class="mb-3 pie-toggle-correct-answer"
354
- style="gap:var(--mpb-toggle-button-gap, 0.5rem);"
355
- aria-pressed={showCorrectAnswer}
356
- data-testid="show-correct-answer"
357
- >
358
- <span class="pie-correct-answer-toggle-content">
359
- <span class="pie-correct-answer-toggle-icon-holder" aria-hidden="true">
360
- {#if showCorrectAnswer}
361
- <svg
362
- class="pie-correct-answer-toggle-svg"
363
- preserveAspectRatio="xMinYMin meet"
364
- version="1.1"
365
- viewBox="-283 359 34 35"
366
- >
367
- <circle cx="-266" cy="375.9" r="14" fill="var(--pie-correct-answer-toggle-icon-open-bg, #bce2ff)" />
368
- <path
369
- d="M-280.5,375.9c0-8,6.5-14.5,14.5-14.5s14.5,6.5,14.5,14.5s-6.5,14.5-14.5,14.5S-280.5,383.9-280.5,375.9z M-279.5,375.9c0,7.4,6.1,13.5,13.5,13.5c7.4,0,13.5-6.1,13.5-13.5s-6.1-13.5-13.5-13.5C-273.4,362.4-279.5,368.5-279.5,375.9z"
370
- fill="var(--pie-correct-answer-toggle-icon-open-bg, #bce2ff)"
371
- />
372
- <polygon
373
- points="-265.4,383.1 -258.6,377.2 -261.2,374.2 -264.3,376.9 -268.9,368.7 -272.4,370.6"
374
- fill="var(--pie-correct-answer-toggle-icon-glyph-color, #1a9cff)"
375
- />
376
- </svg>
377
- {:else}
378
- <svg
379
- class="pie-correct-answer-toggle-svg"
380
- preserveAspectRatio="xMinYMin meet"
381
- version="1.1"
382
- viewBox="-129.5 127 34 35"
383
- >
384
- <path
385
- d="M-112.9,160.4c-8.5,0-15.5-6.9-15.5-15.5c0-8.5,6.9-15.5,15.5-15.5s15.5,6.9,15.5,15.5 C-97.4,153.5-104.3,160.4-112.9,160.4z"
386
- fill="#D0CAC5"
387
- stroke="#E6E3E0"
388
- stroke-width="0.75"
389
- />
390
- <path
391
- d="M-113.2,159c-8,0-14.5-6.5-14.5-14.5s6.5-14.5,14.5-14.5s14.5,6.5,14.5,14.5S-105.2,159-113.2,159z"
392
- fill="#B3ABA4"
393
- stroke="#CDC7C2"
394
- stroke-width="0.5"
395
- />
396
- <circle cx="-114.2" cy="143.5" r="14" fill="white" />
397
- <path
398
- d="M-114.2,158c-8,0-14.5-6.5-14.5-14.5s6.5-14.5,14.5-14.5s14.5,6.5,14.5,14.5S-106.2,158-114.2,158z M-114.2,130c-7.4,0-13.5,6.1-13.5,13.5s6.1,13.5,13.5,13.5s13.5-6.1,13.5-13.5S-106.8,130-114.2,130z"
399
- fill="var(--pie-correct-answer-toggle-icon-closed-bg, #bce2ff)"
400
- />
401
- <polygon
402
- points="-114.8,150.7 -121.6,144.8 -119,141.8 -115.9,144.5 -111.3,136.3 -107.8,138.2"
403
- fill="var(--pie-correct-answer-toggle-icon-glyph-color, #1a9cff)"
404
- />
405
- </svg>
406
- {/if}
407
- </span>
408
- <span class="pie-correct-answer-toggle-label">
409
- {showCorrectAnswer ? uiText.hideCorrectAnswer : uiText.showCorrectAnswer}
410
- </span>
411
- </span>
412
- </button>
413
- </div>
414
- {/if}
415
-
416
- <AudioPlayer
417
- hasAudio={!!model?.hasAudio}
418
- audioUrl={model?.audioUrl}
419
- {useFeatureButtonAudio}
420
- autoplayEnabled={!!model?.autoplayAudioEnabled}
421
- audioTranscript={model?.audioTranscript}
422
- {showVisibleTranscript}
423
- {transcriptId}
424
- {featureAudioSkin}
425
- {uiText}
426
- locale={model?.locale}
427
- onaudiostarted={onAudioStarted}
428
- onaudioended={onAudioEnded}
429
- />
430
-
431
- {#if model?.sentenceHtml}
432
- <div class="mb-3 prose prose-p:my-1 sentence-line pie-sentence-line" aria-describedby={templateDescribedBy}>
433
- {@html model.sentenceHtml}
434
- </div>
435
- {/if}
436
-
437
- {#if !isAudioOnlyMode}
438
- <div class="mb-4 template-line pie-template-line" aria-describedby={templateDescribedBy}>
439
- {@html templateParts.before}
440
- <ClozeMarker
441
- {choiceMode}
442
- displayChoice={displayChoice}
443
- {displayChoiceLabelHtml}
444
- isStandalone={isBlankOnlyTemplate}
445
- blankWidth={layout.blankWidth}
446
- blankBorderWidth={layout.blankBorderWidth}
447
- ariaLabel={uiText.selectedAnswerInSentence}
448
- />
449
- {@html templateParts.after}
450
- </div>
451
- {/if}
452
-
453
- {#if resultText}
454
- <p id={resultId} class="sr-only pie-result-feedback" role="status" aria-live="polite">{resultText}</p>
455
- {/if}
456
-
457
- <fieldset class="border-0 p-0 m-0 pie-choices-fieldset" disabled={model?.disabled}>
458
- <legend class="sr-only pie-choices-legend" id={legendId}>{legendText}</legend>
459
- <div
460
- bind:this={choicesGroupEl}
461
- class={`pie-choices ${isHorizontalChoices ? 'flex flex-row flex-wrap items-start justify-center' : 'flex flex-col'}`}
462
- style="gap:var(--mpb-choice-group-gap, 0.5rem);"
463
- role="radiogroup"
464
- tabindex="-1"
465
- aria-labelledby={choicesGroupLabelledBy}
466
- aria-label={choicesGroupAriaLabel}
467
- aria-describedby={resultText ? resultId : undefined}
468
- >
469
- {#each choices as c (c.id)}
470
- <ChoiceRow
471
- choice={c}
472
- {choiceMode}
473
- isHorizontal={isHorizontalChoices}
474
- isSelected={displayChoiceId === c.id}
475
- isDisabled={!!model?.disabled}
476
- correctness={choiceCorrectnessById.get(c.id)}
477
- {isEvaluateMode}
478
- {instanceId}
479
- {radioGroupName}
480
- />
481
- {/each}
482
- </div>
483
- </fieldset>
484
- </div>
485
-
486
- <style>
487
- .sr-only {
488
- position: absolute;
489
- width: 1px;
490
- height: 1px;
491
- padding: 0;
492
- margin: -1px;
493
- overflow: hidden;
494
- clip: rect(0, 0, 0, 0);
495
- white-space: nowrap;
496
- border: 0;
497
- }
498
-
499
- .mc-populated-blank-root :global(.prose) {
500
- max-width: none;
501
- }
502
-
503
- .pie-toggle-correct-answer {
504
- width: 100%;
505
- cursor: pointer;
506
- border: 0;
507
- background: transparent;
508
- padding: 0;
509
- display: flex;
510
- justify-content: center;
511
- text-align: center;
512
- color: var(--pie-correct-answer-toggle-label-color, var(--pie-text, black));
513
- }
514
-
515
- .pie-correct-answer-toggle-row {
516
- width: 100%;
517
- display: flex;
518
- justify-content: flex-end;
519
- }
520
-
521
- .pie-correct-answer-toggle-content {
522
- display: flex;
523
- margin: 0 auto;
524
- align-items: center;
525
- }
526
-
527
- .pie-correct-answer-toggle-icon-holder {
528
- width: 25px;
529
- margin-right: 5px;
530
- display: inline-flex;
531
- align-items: center;
532
- justify-content: center;
533
- }
534
-
535
- .pie-correct-answer-toggle-svg {
536
- width: 25px;
537
- height: 25px;
538
- }
539
-
540
- .pie-correct-answer-toggle-label {
541
- width: fit-content;
542
- min-width: 140px;
543
- align-self: center;
544
- vertical-align: middle;
545
- font-weight: 400;
546
- user-select: none;
547
- }
548
-
549
- .pie-toggle-correct-answer:hover .pie-correct-answer-toggle-label,
550
- .pie-toggle-correct-answer:focus-visible .pie-correct-answer-toggle-label {
551
- text-decoration: underline;
552
- }
553
-
554
- .template-line {
555
- white-space: pre-wrap;
556
- }
557
-
558
- .template-line :global(p) {
559
- margin: 0;
560
- }
561
-
562
- .layout-audio_blank_only :global(.pie-audio-container),
563
- .layout-stimulus_image_blank :global(.pie-audio-container) {
564
- display: flex;
565
- width: min(100%, var(--mpb-audio-instructions-max-width, 875px));
566
- margin-left: auto;
567
- margin-right: auto;
568
- justify-content: flex-end;
569
- }
570
-
571
- .layout-audio_blank_only .template-line,
572
- .layout-stimulus_image_blank .template-line {
573
- text-align: center;
574
- margin: var(--mpb-audio-blank-template-margin-top, 0.8rem) 0
575
- var(--mpb-audio-blank-template-margin-bottom, 1.8rem);
576
- }
577
-
578
- .layout-audio_blank_only fieldset {
579
- display: flex;
580
- justify-content: center;
581
- }
582
-
583
- .layout-audio_blank_only :global(.pie-blank-slot),
584
- .layout-stimulus_image_blank :global(.pie-blank-slot) {
585
- width: var(--mpb-blank-wide-width, 10rem);
586
- border-bottom-width: var(--mpb-blank-underline-wide-width, 6px);
587
- min-height: 160px;
588
- padding-bottom: 4px;
589
- }
590
-
591
- .layout-stimulus_image_blank {
592
- display: grid;
593
- grid-template-columns: minmax(var(--mpb-stimulus-min-column, 210px), 1fr) auto;
594
- grid-template-areas:
595
- 'transcript transcript'
596
- 'sentence audio'
597
- '. template'
598
- 'choices choices';
599
- column-gap: var(--mpb-stimulus-grid-column-gap, 2rem);
600
- row-gap: var(--mpb-stimulus-grid-row-gap, 0.7rem);
601
- align-items: start;
602
- }
603
-
604
- .layout-stimulus_image_blank .pie-audio-transcript {
605
- grid-area: transcript;
606
- }
607
-
608
- .layout-stimulus_image_blank :global(.pie-audio-container) {
609
- grid-area: audio;
610
- margin: 0;
611
- }
612
-
613
- .layout-stimulus_image_blank .sentence-line {
614
- grid-area: sentence;
615
- margin: var(--mpb-stimulus-sentence-margin-top, 0.2rem) 0 0;
616
- }
617
-
618
- .layout-stimulus_image_blank .template-line {
619
- grid-area: template;
620
- margin: 0;
621
- text-align: center;
622
- justify-self: center;
623
- }
624
-
625
- .layout-stimulus_image_blank fieldset {
626
- grid-area: choices;
627
- margin-top: var(--mpb-stimulus-choices-margin-top, 0.9rem);
628
- }
629
-
630
- .layout-token_sequence {
631
- display: grid;
632
- grid-template-columns: 1fr auto;
633
- grid-template-areas:
634
- 'transcript audio'
635
- 'template template'
636
- 'choices choices';
637
- column-gap: var(--mpb-token-grid-column-gap, 1.5rem);
638
- row-gap: var(--mpb-token-grid-row-gap, 0.8rem);
639
- align-items: start;
640
- }
641
-
642
- .layout-token_sequence .pie-audio-transcript {
643
- grid-area: transcript;
644
- }
645
-
646
- .layout-token_sequence :global(.pie-audio-container) {
647
- grid-area: audio;
648
- margin: 0;
649
- justify-self: end;
650
- }
651
-
652
- .layout-token_sequence .template-line {
653
- grid-area: template;
654
- margin: var(--mpb-token-template-margin-top, 0.6rem) 0 0;
655
- justify-self: center;
656
- text-align: center;
657
- }
658
-
659
- .layout-token_sequence .template-line :global(span + span) {
660
- margin-left: var(--mpb-token-inline-token-gap, 0.35rem);
661
- }
662
-
663
- .layout-token_sequence :global(.pie-blank-slot) {
664
- width: var(--mpb-blank-standalone-width, 7rem);
665
- border-bottom-width: var(--mpb-blank-underline-wide-width, 4px);
666
- margin-left: var(--mpb-token-inline-token-gap, 0.35rem);
667
- }
668
-
669
- .layout-token_sequence fieldset {
670
- grid-area: choices;
671
- margin-top: var(--mpb-token-choices-margin-top, 0.2rem);
672
- }
673
-
674
- .layout-inline_sentence.has-inline-audio {
675
- display: grid;
676
- grid-template-columns: minmax(var(--mpb-text-min-column, 260px), 1fr) auto;
677
- grid-template-areas:
678
- 'transcript transcript'
679
- 'template audio'
680
- 'choices choices';
681
- column-gap: var(--mpb-inline-grid-column-gap, 1.5rem);
682
- row-gap: var(--mpb-inline-grid-row-gap, 0.65rem);
683
- align-items: start;
684
- }
685
-
686
- .layout-inline_sentence.has-inline-audio .pie-audio-transcript {
687
- grid-area: transcript;
688
- }
689
-
690
- .layout-inline_sentence.has-inline-audio :global(.pie-audio-container) {
691
- grid-area: audio;
692
- margin: 0;
693
- justify-self: end;
694
- }
695
-
696
- .layout-inline_sentence.has-inline-audio .template-line {
697
- grid-area: template;
698
- margin: var(--mpb-inline-template-margin-top, 0.45rem) 0 0;
699
- text-align: center;
700
- justify-self: center;
701
- }
702
-
703
- .layout-inline_sentence.has-inline-audio fieldset {
704
- grid-area: choices;
705
- margin-top: var(--mpb-inline-choices-margin-top, 0.25rem);
706
- }
707
-
708
- /* Match Learnosity responsive behavior for CQT audio-blank layouts:
709
- at smaller widths, audio control shifts left and answer tiles stack. */
710
- @media (max-width: 760px) {
711
- .layout-audio_blank_only :global(.pie-audio-container),
712
- .layout-stimulus_image_blank :global(.pie-audio-container),
713
- .layout-token_sequence :global(.pie-audio-container),
714
- .layout-inline_sentence.has-inline-audio :global(.pie-audio-container) {
715
- width: 100%;
716
- margin-left: 0;
717
- margin-right: 0;
718
- justify-content: flex-start;
719
- justify-self: start;
720
- }
721
-
722
- .layout-audio_blank_only .pie-choices,
723
- .layout-token_sequence .pie-choices,
724
- .layout-stimulus_image_blank .pie-choices,
725
- .layout-inline_sentence.has-inline-audio .pie-choices {
726
- flex-direction: column;
727
- align-items: flex-start;
728
- justify-content: flex-start;
729
- }
730
-
731
- .layout-audio_blank_only .choice-row-horizontal,
732
- .layout-token_sequence .choice-row-horizontal,
733
- .layout-stimulus_image_blank .choice-row-horizontal,
734
- .layout-inline_sentence.has-inline-audio .choice-row-horizontal {
735
- width: min(100%, var(--mpb-narrow-choice-max-width, 230px));
736
- align-items: flex-start;
737
- }
738
- }
739
- </style>