@pie-element/mc-populated-blank 0.2.10 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/author/index.js +186 -152
- package/dist/author/index.js.map +1 -1
- package/dist/controller/defaults.d.ts +41 -73
- package/dist/controller/defaults.d.ts.map +1 -1
- package/dist/controller/index.d.ts +154 -10
- package/dist/controller/index.d.ts.map +1 -1
- package/dist/controller/index.js +29 -30
- package/dist/controller/index.js.map +1 -1
- package/dist/controller/index.test.d.ts +63 -0
- package/dist/controller/index.test.d.ts.map +1 -0
- package/dist/delivery/AudioPlayer.test.d.ts +2 -0
- package/dist/delivery/AudioPlayer.test.d.ts.map +1 -0
- package/dist/delivery/ChoiceRow.test.d.ts +2 -0
- package/dist/delivery/ChoiceRow.test.d.ts.map +1 -0
- package/dist/delivery/ClozeMarker.test.d.ts +2 -0
- package/dist/delivery/ClozeMarker.test.d.ts.map +1 -0
- package/dist/delivery/McPopulatedBlank.transcript.test.d.ts +2 -0
- package/dist/delivery/McPopulatedBlank.transcript.test.d.ts.map +1 -0
- package/dist/delivery/computeAudioMode.d.ts +7 -0
- package/dist/delivery/computeAudioMode.d.ts.map +1 -0
- package/dist/delivery/computeAudioMode.test.d.ts +2 -0
- package/dist/delivery/computeAudioMode.test.d.ts.map +1 -0
- package/dist/delivery/computeChoiceCorrectness.d.ts +18 -0
- package/dist/delivery/computeChoiceCorrectness.d.ts.map +1 -0
- package/dist/delivery/computeChoiceCorrectness.test.d.ts +19 -0
- package/dist/delivery/computeChoiceCorrectness.test.d.ts.map +1 -0
- package/dist/delivery/computeDisplayState.d.ts +47 -0
- package/dist/delivery/computeDisplayState.d.ts.map +1 -0
- package/dist/delivery/computeDisplayState.test.d.ts +38 -0
- package/dist/delivery/computeDisplayState.test.d.ts.map +1 -0
- package/dist/delivery/computeLayoutProfile.d.ts +18 -0
- package/dist/delivery/computeLayoutProfile.d.ts.map +1 -0
- package/dist/delivery/computeLayoutProfile.test.d.ts +2 -0
- package/dist/delivery/computeLayoutProfile.test.d.ts.map +1 -0
- package/dist/delivery/computeLayoutStyle.d.ts +27 -0
- package/dist/delivery/computeLayoutStyle.d.ts.map +1 -0
- package/dist/delivery/computeLayoutStyle.test.d.ts +2 -0
- package/dist/delivery/computeLayoutStyle.test.d.ts.map +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1591 -927
- package/dist/delivery/index.js.map +1 -1
- package/dist/delivery/variant-css-map.d.ts.map +1 -1
- package/dist/index.iife.d.ts +151 -10
- package/dist/index.iife.d.ts.map +1 -1
- package/dist/index.iife.js +233 -114
- package/dist/index.js +1591 -927
- package/dist/index.js.map +1 -1
- package/dist/print/index.js +194 -166
- package/dist/print/index.js.map +1 -1
- package/dist/shared/layoutLimits.d.ts +40 -0
- package/dist/shared/layoutLimits.d.ts.map +1 -0
- package/dist/shared/types.d.ts +82 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +4 -2
- package/src/controller/defaults.ts +9 -39
- package/src/controller/index.test.ts +428 -0
- package/src/controller/index.ts +63 -50
- package/src/delivery/AudioPlayer.svelte +246 -0
- package/src/delivery/AudioPlayer.test.ts +149 -0
- package/src/delivery/ChoiceRow.svelte +222 -0
- package/src/delivery/ChoiceRow.test.ts +179 -0
- package/src/delivery/ClozeMarker.svelte +83 -0
- package/src/delivery/ClozeMarker.test.ts +128 -0
- package/src/delivery/McPopulatedBlank.svelte +225 -732
- package/src/delivery/McPopulatedBlank.transcript.test.ts +154 -0
- package/src/delivery/computeAudioMode.test.ts +40 -0
- package/src/delivery/computeAudioMode.ts +11 -0
- package/src/delivery/computeChoiceCorrectness.test.ts +120 -0
- package/src/delivery/computeChoiceCorrectness.ts +42 -0
- package/src/delivery/computeDisplayState.test.ts +274 -0
- package/src/delivery/computeDisplayState.ts +101 -0
- package/src/delivery/computeLayoutProfile.test.ts +163 -0
- package/src/delivery/computeLayoutProfile.ts +72 -0
- package/src/delivery/computeLayoutStyle.test.ts +168 -0
- package/src/delivery/computeLayoutStyle.ts +128 -0
- package/src/delivery/cqt-css/sel-r1-base.css +59 -0
- package/src/delivery/cqt-css/sel-r1-g-stem.css +5 -20
- package/src/delivery/cqt-css/sel-r1-gg-plus.css +3 -20
- package/src/delivery/cqt-css/sel-r1-ggplus.css +4 -21
- package/src/delivery/cqt-css/sel-r1-gplusggg.css +16 -11
- package/src/delivery/cqt-css/sel-r1-plusggg.css +8 -11
- package/src/delivery/cqt-css/sel-r1-s3.css +3 -9
- package/src/delivery/cqt-css/sel-vic.css +61 -0
- package/src/delivery/cqt-css/sr-vic.css +49 -0
- package/src/delivery/index.ts +14 -23
- package/src/delivery/variant-css-map.ts +26 -12
- package/src/shared/layoutLimits.ts +76 -0
- package/src/shared/types.ts +84 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<svelte:options
|
|
2
2
|
customElement={{
|
|
3
|
-
tag: 'mc-populated-blank',
|
|
4
3
|
shadow: 'none',
|
|
5
4
|
props: {
|
|
6
5
|
model: { type: 'Object' },
|
|
@@ -12,78 +11,26 @@
|
|
|
12
11
|
|
|
13
12
|
<script lang="ts">
|
|
14
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';
|
|
15
27
|
import {
|
|
16
28
|
ensureVariantCssInjected,
|
|
17
29
|
getVariantCssConfig,
|
|
18
30
|
getVariantRootClass,
|
|
19
31
|
} from './variant-css-map';
|
|
20
|
-
|
|
32
|
+
|
|
21
33
|
const BLANK_TOKEN = '{{blank}}';
|
|
22
|
-
const DEFAULT_LAYOUT_LIMITS = {
|
|
23
|
-
blankStandaloneWidthRem: 7,
|
|
24
|
-
blankWideWidthRem: 10,
|
|
25
|
-
blankUnderlineWidthPx: 2,
|
|
26
|
-
blankUnderlineWideWidthPx: 4,
|
|
27
|
-
horizontalChoiceWidthPx: 170,
|
|
28
|
-
horizontalChoiceWidthVw: 30,
|
|
29
|
-
horizontalChoiceTileMinHeightRem: 11,
|
|
30
|
-
horizontalChoiceContentMinHeightRem: 7.5,
|
|
31
|
-
selectedImageMaxHeightRem: 4,
|
|
32
|
-
choiceImageMaxHeightRem: 5,
|
|
33
|
-
listenButtonSizePx: 128,
|
|
34
|
-
stimulusMinColumnPx: 210,
|
|
35
|
-
textMinColumnPx: 260,
|
|
36
|
-
legendMaxChars: 120,
|
|
37
|
-
choiceGroupGapRem: 0.5,
|
|
38
|
-
choiceRowGapRem: 0.5,
|
|
39
|
-
toggleButtonGapRem: 0.5,
|
|
40
|
-
horizontalChoiceRadioTopMarginRem: 0.5,
|
|
41
|
-
audioBlankTemplateMarginTopRem: 0.8,
|
|
42
|
-
audioBlankTemplateMarginBottomRem: 1.8,
|
|
43
|
-
audioInstructionsMaxWidthPx: 875,
|
|
44
|
-
narrowHorizontalChoiceMaxWidthPx: 230,
|
|
45
|
-
stimulusGridColumnGapRem: 2,
|
|
46
|
-
stimulusGridRowGapRem: 0.7,
|
|
47
|
-
stimulusSentenceMarginTopRem: 0.2,
|
|
48
|
-
stimulusChoicesMarginTopRem: 0.9,
|
|
49
|
-
tokenGridColumnGapRem: 1.5,
|
|
50
|
-
tokenGridRowGapRem: 0.8,
|
|
51
|
-
tokenTemplateMarginTopRem: 0.6,
|
|
52
|
-
tokenInlineTokenGapRem: 0.35,
|
|
53
|
-
tokenChoicesMarginTopRem: 0.2,
|
|
54
|
-
inlineGridColumnGapRem: 1.5,
|
|
55
|
-
inlineGridRowGapRem: 0.65,
|
|
56
|
-
inlineTemplateMarginTopRem: 0.45,
|
|
57
|
-
inlineChoicesMarginTopRem: 0.25,
|
|
58
|
-
} as const;
|
|
59
|
-
const DEFAULT_LAYOUT_PROFILE_PRESETS: Record<string, Record<string, number>> = {
|
|
60
|
-
audio_blank_only: {
|
|
61
|
-
blankWideWidthRem: 10,
|
|
62
|
-
blankUnderlineWideWidthPx: 4,
|
|
63
|
-
},
|
|
64
|
-
stimulus_image_blank: {
|
|
65
|
-
blankWideWidthRem: 10,
|
|
66
|
-
blankUnderlineWideWidthPx: 4,
|
|
67
|
-
},
|
|
68
|
-
token_sequence: {
|
|
69
|
-
blankStandaloneWidthRem: 7,
|
|
70
|
-
blankUnderlineWideWidthPx: 4,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
const DEFAULT_AUDIO_BUTTON_SKINS = {
|
|
74
|
-
default: {
|
|
75
|
-
silentUrl:
|
|
76
|
-
'https://assets.learnosity.com/organisations/844/0c9f2aa3-3cd5-4de7-93ef-541c24ca35da.svg',
|
|
77
|
-
playingUrl:
|
|
78
|
-
'https://assets.learnosity.com/organisations/844/231dfdc2-c113-4be5-91fb-e75a0ca5994b.svg',
|
|
79
|
-
},
|
|
80
|
-
es: {
|
|
81
|
-
silentUrl:
|
|
82
|
-
'https://assets.learnosity.com/organisations/844/27a9d5b5-d873-4bd5-b9ba-22748782d8ba.svg',
|
|
83
|
-
playingUrl:
|
|
84
|
-
'https://assets.learnosity.com/organisations/844/120f216d-96b7-4560-94b8-1d90710216b7.svg',
|
|
85
|
-
},
|
|
86
|
-
} as const;
|
|
87
34
|
const DEFAULT_UI_TEXT = {
|
|
88
35
|
answerChoices: 'Answer choices',
|
|
89
36
|
selectedAnswerInSentence: 'Selected answer in sentence',
|
|
@@ -92,22 +39,20 @@ const DEFAULT_UI_TEXT = {
|
|
|
92
39
|
clickToEnableAutoplay: 'Click to enable audio autoplay',
|
|
93
40
|
audioResourceUnavailable: 'Audio is enabled but no playable audio URL is configured.',
|
|
94
41
|
transcriptLabel: 'Transcript',
|
|
95
|
-
listenLabelEn: 'Listen',
|
|
96
|
-
listenLabelEs: 'Escuchar',
|
|
97
42
|
} as const;
|
|
98
43
|
|
|
99
44
|
let { model, session } = $props<{ model?: any; session?: any; options?: any }>();
|
|
100
|
-
let audioEl = $state<HTMLAudioElement | null>(null);
|
|
101
|
-
let isMediaPlaying = $state(false);
|
|
102
45
|
let localChoiceId = $state('');
|
|
103
|
-
let autoPlayPromptOpen = $state(false);
|
|
104
|
-
let autoplayAttempted = $state(false);
|
|
105
|
-
let featureAudioButtonEl = $state<HTMLButtonElement | null>(null);
|
|
106
|
-
let autoplayEnableButtonEl = $state<HTMLButtonElement | null>(null);
|
|
107
46
|
let toggleCorrectAnswerButtonEl = $state<HTMLButtonElement | null>(null);
|
|
108
47
|
let choicesGroupEl = $state<HTMLDivElement | null>(null);
|
|
48
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
49
|
+
let ancestorHasTranscriptClass = $state(false);
|
|
109
50
|
const instanceId = `mc-populated-blank-${Math.random().toString(36).slice(2, 10)}`;
|
|
110
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Cluster: modeFlags — delivery mode, correctness, and answer-reveal state
|
|
54
|
+
// Feeds: shouldShowCorrectAnswerToggle, choiceState, a11y
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
111
56
|
const deliveryMode = $derived((model?.mode || model?.env?.mode || '').toString());
|
|
112
57
|
const isEvaluateMode = $derived(deliveryMode === 'evaluate');
|
|
113
58
|
const correctness = $derived(model?.correctness);
|
|
@@ -116,58 +61,33 @@ const isResponseCorrect = $derived(
|
|
|
116
61
|
);
|
|
117
62
|
const isCorrect = $derived(correctness === 'correct');
|
|
118
63
|
const isIncorrect = $derived(correctness === 'incorrect');
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const plain = t
|
|
124
|
-
.replace(/<[^>]+>/g, '')
|
|
125
|
-
.replace(/ /g, ' ')
|
|
126
|
-
.trim();
|
|
127
|
-
return plain === BLANK_TOKEN;
|
|
128
|
-
});
|
|
129
|
-
const layoutProfile = $derived(model?.layoutProfile || '');
|
|
130
|
-
const variantCssConfig = $derived(getVariantCssConfig(model?.customType));
|
|
131
|
-
const variantRootClass = $derived(getVariantRootClass(model?.customType));
|
|
132
|
-
const choiceLayout = $derived(
|
|
133
|
-
model?.choiceLayout || (isAudioOnlyMode || isBlankOnlyTemplate ? 'horizontal' : 'vertical')
|
|
64
|
+
|
|
65
|
+
let showCorrectAnswer = $state(false);
|
|
66
|
+
const shouldShowCorrectAnswerToggle = $derived(
|
|
67
|
+
isEvaluateMode && !isResponseCorrect && !!model?.correctChoiceId
|
|
134
68
|
);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
})
|
|
141
84
|
);
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const defaults = DEFAULT_LAYOUT_PROFILE_PRESETS[profile] || {};
|
|
149
|
-
const customPresets =
|
|
150
|
-
model?.layoutProfilePresets && typeof model.layoutProfilePresets === 'object'
|
|
151
|
-
? model.layoutProfilePresets
|
|
152
|
-
: {};
|
|
153
|
-
const custom =
|
|
154
|
-
customPresets[profile] && typeof customPresets[profile] === 'object'
|
|
155
|
-
? customPresets[profile]
|
|
156
|
-
: {};
|
|
157
|
-
return {
|
|
158
|
-
...defaults,
|
|
159
|
-
...custom,
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
const layoutLimits = $derived.by(() => {
|
|
163
|
-
const configured =
|
|
164
|
-
model?.layoutLimits && typeof model.layoutLimits === 'object' ? model.layoutLimits : {};
|
|
165
|
-
return {
|
|
166
|
-
...DEFAULT_LAYOUT_LIMITS,
|
|
167
|
-
...profilePresetLimits,
|
|
168
|
-
...configured,
|
|
169
|
-
};
|
|
170
|
-
});
|
|
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);
|
|
171
91
|
const correctAnswerStyleVars = $derived.by(() =>
|
|
172
92
|
[
|
|
173
93
|
`--pie-correct-answer-toggle-label-color:${color.text()}`,
|
|
@@ -185,124 +105,66 @@ const correctAnswerStyleVars = $derived.by(() =>
|
|
|
185
105
|
`--pie-correct-answer-feedback-glyph-color:${color.white()}`,
|
|
186
106
|
].join(';')
|
|
187
107
|
);
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
`--mpb-blank-underline-wide-width:${layoutLimits.blankUnderlineWideWidthPx}px`,
|
|
195
|
-
`--mpb-selected-image-max-height:${layoutLimits.selectedImageMaxHeightRem}rem`,
|
|
196
|
-
`--mpb-choice-image-max-height:${layoutLimits.choiceImageMaxHeightRem}rem`,
|
|
197
|
-
`--mpb-choice-width-px:${layoutLimits.horizontalChoiceWidthPx}px`,
|
|
198
|
-
`--mpb-choice-width-vw:${layoutLimits.horizontalChoiceWidthVw}vw`,
|
|
199
|
-
`--mpb-choice-tile-min-height:${layoutLimits.horizontalChoiceTileMinHeightRem}rem`,
|
|
200
|
-
`--mpb-choice-content-min-height:${layoutLimits.horizontalChoiceContentMinHeightRem}rem`,
|
|
201
|
-
`--mpb-stimulus-min-column:${layoutLimits.stimulusMinColumnPx}px`,
|
|
202
|
-
`--mpb-text-min-column:${layoutLimits.textMinColumnPx}px`,
|
|
203
|
-
`--mpb-choice-group-gap:${layoutLimits.choiceGroupGapRem}rem`,
|
|
204
|
-
`--mpb-choice-row-gap:${layoutLimits.choiceRowGapRem}rem`,
|
|
205
|
-
`--mpb-toggle-button-gap:${layoutLimits.toggleButtonGapRem}rem`,
|
|
206
|
-
`--mpb-horizontal-choice-radio-top-margin:${layoutLimits.horizontalChoiceRadioTopMarginRem}rem`,
|
|
207
|
-
`--mpb-audio-blank-template-margin-top:${layoutLimits.audioBlankTemplateMarginTopRem}rem`,
|
|
208
|
-
`--mpb-audio-blank-template-margin-bottom:${layoutLimits.audioBlankTemplateMarginBottomRem}rem`,
|
|
209
|
-
`--mpb-audio-instructions-max-width:${layoutLimits.audioInstructionsMaxWidthPx}px`,
|
|
210
|
-
`--mpb-narrow-choice-max-width:${layoutLimits.narrowHorizontalChoiceMaxWidthPx}px`,
|
|
211
|
-
`--mpb-stimulus-grid-column-gap:${layoutLimits.stimulusGridColumnGapRem}rem`,
|
|
212
|
-
`--mpb-stimulus-grid-row-gap:${layoutLimits.stimulusGridRowGapRem}rem`,
|
|
213
|
-
`--mpb-stimulus-sentence-margin-top:${layoutLimits.stimulusSentenceMarginTopRem}rem`,
|
|
214
|
-
`--mpb-stimulus-choices-margin-top:${layoutLimits.stimulusChoicesMarginTopRem}rem`,
|
|
215
|
-
`--mpb-token-grid-column-gap:${layoutLimits.tokenGridColumnGapRem}rem`,
|
|
216
|
-
`--mpb-token-grid-row-gap:${layoutLimits.tokenGridRowGapRem}rem`,
|
|
217
|
-
`--mpb-token-template-margin-top:${layoutLimits.tokenTemplateMarginTopRem}rem`,
|
|
218
|
-
`--mpb-token-inline-token-gap:${layoutLimits.tokenInlineTokenGapRem}rem`,
|
|
219
|
-
`--mpb-token-choices-margin-top:${layoutLimits.tokenChoicesMarginTopRem}rem`,
|
|
220
|
-
`--mpb-inline-grid-column-gap:${layoutLimits.inlineGridColumnGapRem}rem`,
|
|
221
|
-
`--mpb-inline-grid-row-gap:${layoutLimits.inlineGridRowGapRem}rem`,
|
|
222
|
-
`--mpb-inline-template-margin-top:${layoutLimits.inlineTemplateMarginTopRem}rem`,
|
|
223
|
-
`--mpb-inline-choices-margin-top:${layoutLimits.inlineChoicesMarginTopRem}rem`,
|
|
108
|
+
const layout = $derived(
|
|
109
|
+
computeLayoutStyle({
|
|
110
|
+
layoutProfile,
|
|
111
|
+
isBlankOnlyTemplate,
|
|
112
|
+
configuredLimits: model?.layoutLimits,
|
|
113
|
+
customProfilePresets: model?.layoutProfilePresets,
|
|
224
114
|
correctAnswerStyleVars,
|
|
225
|
-
|
|
115
|
+
})
|
|
226
116
|
);
|
|
227
|
-
const useFeatureButtonAudio = $derived.by(() => {
|
|
228
|
-
if (typeof model?.useFeatureButtonAudio === 'boolean') {
|
|
229
|
-
return !!model.useFeatureButtonAudio;
|
|
230
|
-
}
|
|
231
|
-
const profile = layoutProfile || '';
|
|
232
|
-
return (
|
|
233
|
-
!!model?.hasAudio &&
|
|
234
|
-
(profile === 'audio_blank_only' ||
|
|
235
|
-
profile === 'stimulus_image_blank' ||
|
|
236
|
-
profile === 'token_sequence')
|
|
237
|
-
);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const templateParts = $derived.by(() => {
|
|
241
|
-
const t = model?.template || '';
|
|
242
|
-
const idx = t.indexOf(BLANK_TOKEN);
|
|
243
|
-
if (idx < 0) {
|
|
244
|
-
return { before: t, after: '' };
|
|
245
|
-
}
|
|
246
|
-
return {
|
|
247
|
-
before: t.slice(0, idx),
|
|
248
|
-
after: t.slice(idx + BLANK_TOKEN.length),
|
|
249
|
-
};
|
|
250
|
-
});
|
|
251
|
-
const blankWidth = $derived.by(() => {
|
|
252
|
-
if (layoutProfile === 'audio_blank_only' || layoutProfile === 'stimulus_image_blank') {
|
|
253
|
-
return `${layoutLimits.blankWideWidthRem}rem`;
|
|
254
|
-
}
|
|
255
|
-
if (isBlankOnlyTemplate) {
|
|
256
|
-
return `${layoutLimits.blankStandaloneWidthRem}rem`;
|
|
257
|
-
}
|
|
258
|
-
return 'auto';
|
|
259
|
-
});
|
|
260
|
-
const blankBorderWidth = $derived.by(() => {
|
|
261
|
-
if (layoutProfile === 'audio_blank_only' || layoutProfile === 'stimulus_image_blank') {
|
|
262
|
-
return `${layoutLimits.blankUnderlineWideWidthPx}px`;
|
|
263
|
-
}
|
|
264
|
-
return `${layoutLimits.blankUnderlineWidthPx}px`;
|
|
265
|
-
});
|
|
266
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
|
+
// ---------------------------------------------------------------------------
|
|
267
122
|
const choices = $derived(Array.isArray(model?.choices) ? model.choices : []);
|
|
268
123
|
const choiceMode = $derived(model?.choiceMode || 'text');
|
|
269
124
|
const selectedId = $derived(session?.choiceId || localChoiceId || '');
|
|
270
125
|
const radioGroupName = $derived(`${instanceId}-choice-group-${model?.id || '1'}`);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
126
|
+
const displayChoiceId = $derived(
|
|
127
|
+
computeDisplayChoiceId({
|
|
128
|
+
selectedId,
|
|
129
|
+
isEvaluateMode,
|
|
130
|
+
showCorrectAnswer,
|
|
131
|
+
alwaysShowCorrect: !!model?.alwaysShowCorrect,
|
|
132
|
+
correctChoiceId: String(model?.correctChoiceId || ''),
|
|
133
|
+
})
|
|
275
134
|
);
|
|
276
|
-
|
|
277
|
-
const displayChoiceId = $derived.by(() => {
|
|
278
|
-
if (isEvaluateMode && showCorrectAnswer && model?.correctChoiceId) {
|
|
279
|
-
return model.correctChoiceId;
|
|
280
|
-
}
|
|
281
|
-
return selectedId;
|
|
282
|
-
});
|
|
283
|
-
|
|
284
135
|
const displayChoice = $derived.by(() => choices.find((c: any) => c.id === displayChoiceId));
|
|
285
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
|
+
);
|
|
286
148
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
);
|
|
296
|
-
return plain.length > maxChars
|
|
297
|
-
? `${plain.slice(0, Math.max(1, maxChars - 1))}…`
|
|
298
|
-
: plain || uiText.answerChoices;
|
|
299
|
-
});
|
|
300
|
-
|
|
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
|
+
}));
|
|
301
157
|
const promptId = $derived(`${instanceId}-prompt`);
|
|
302
158
|
const transcriptId = $derived(`${instanceId}-transcript`);
|
|
303
159
|
const legendId = $derived(`${instanceId}-choices-legend`);
|
|
304
160
|
const resultId = $derived(`${instanceId}-result`);
|
|
305
|
-
|
|
161
|
+
const legendText = $derived(
|
|
162
|
+
computeLegendText({
|
|
163
|
+
prompt: model?.prompt || '',
|
|
164
|
+
legendMaxChars: layout.legendMaxChars,
|
|
165
|
+
answerChoicesLabel: uiText.answerChoices,
|
|
166
|
+
})
|
|
167
|
+
);
|
|
306
168
|
const choicesGroupLabelledBy = $derived(model?.prompt ? promptId : undefined);
|
|
307
169
|
const choicesGroupAriaLabel = $derived.by(() => {
|
|
308
170
|
if (model?.prompt) return undefined;
|
|
@@ -310,73 +172,40 @@ const choicesGroupAriaLabel = $derived.by(() => {
|
|
|
310
172
|
if (explicit) return explicit;
|
|
311
173
|
return legendText;
|
|
312
174
|
});
|
|
313
|
-
|
|
314
175
|
const templateDescribedBy = $derived.by(() => {
|
|
315
176
|
const ids: string[] = [];
|
|
316
177
|
if (model?.prompt) ids.push(promptId);
|
|
317
|
-
if (model?.hasAudio && model?.audioTranscript)
|
|
318
|
-
ids.push(transcriptId);
|
|
319
|
-
}
|
|
178
|
+
if (model?.hasAudio && model?.audioTranscript) ids.push(transcriptId);
|
|
320
179
|
return ids.length ? ids.join(' ') : undefined;
|
|
321
180
|
});
|
|
322
181
|
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Misc — locale, audio error, template parsing, variant CSS, style strings
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
323
185
|
const lang = $derived.by(() => {
|
|
324
186
|
const locale = model?.locale || '';
|
|
325
187
|
return locale ? locale.slice(0, 2) : 'en';
|
|
326
188
|
});
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
const choiceCorrectnessById = $derived.by(() => {
|
|
338
|
-
const map = new Map<string, 'correct' | 'incorrect'>();
|
|
339
|
-
const correctChoiceId = String(model?.correctChoiceId || '');
|
|
340
|
-
const activeSelectedId = String(selectedId || '');
|
|
341
|
-
|
|
342
|
-
if (!isEvaluateMode || !correctChoiceId) {
|
|
343
|
-
return map;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (showCorrectAnswer) {
|
|
347
|
-
// Reveal mode should show only the canonical correct answer state.
|
|
348
|
-
map.set(correctChoiceId, 'correct');
|
|
349
|
-
return map;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (!activeSelectedId) {
|
|
353
|
-
map.set(correctChoiceId, 'incorrect');
|
|
354
|
-
return map;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (activeSelectedId === correctChoiceId) {
|
|
358
|
-
map.set(correctChoiceId, 'correct');
|
|
359
|
-
return map;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
map.set(activeSelectedId, 'incorrect');
|
|
363
|
-
map.set(correctChoiceId, 'incorrect');
|
|
364
|
-
return map;
|
|
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
|
+
};
|
|
365
199
|
});
|
|
366
200
|
|
|
367
201
|
function emitSession(updatedSession: any, sourceEl?: HTMLElement | null) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
bubbles: true,
|
|
376
|
-
composed: true,
|
|
377
|
-
detail: { complete: !!updatedSession?.choiceId, component: 'mc-populated-blank' },
|
|
378
|
-
})
|
|
379
|
-
);
|
|
202
|
+
forwardSessionChange({
|
|
203
|
+
sourceEl,
|
|
204
|
+
fallbackSelector: 'mc-populated-blank',
|
|
205
|
+
component: 'mc-populated-blank',
|
|
206
|
+
session: updatedSession,
|
|
207
|
+
complete: !!updatedSession?.choiceId,
|
|
208
|
+
});
|
|
380
209
|
}
|
|
381
210
|
|
|
382
211
|
function onRadioChange(e: Event) {
|
|
@@ -426,70 +255,20 @@ function onRadioGroupKeydown(e: KeyboardEvent) {
|
|
|
426
255
|
next.focus();
|
|
427
256
|
}
|
|
428
257
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
function onAudioPlaying(e: Event) {
|
|
438
|
-
const host = getHostElement(e.currentTarget as HTMLElement);
|
|
439
|
-
isMediaPlaying = true;
|
|
440
|
-
host?.onAudioStarted?.();
|
|
441
|
-
autoPlayPromptOpen = false;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function onAudioEnded(e: Event) {
|
|
445
|
-
const host = getHostElement(e.currentTarget as HTMLElement);
|
|
446
|
-
isMediaPlaying = false;
|
|
447
|
-
host?.onAudioEnded?.();
|
|
448
|
-
}
|
|
258
|
+
const featureAudioSkin = $derived(
|
|
259
|
+
computeFeatureAudioSkin({
|
|
260
|
+
locale: model?.locale,
|
|
261
|
+
audioButtonSkin: model?.audioButtonSkin,
|
|
262
|
+
audioButtonSkinsByLocale: model?.audioButtonSkinsByLocale,
|
|
263
|
+
})
|
|
264
|
+
);
|
|
449
265
|
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
while (cursor) {
|
|
453
|
-
const maybeHost = cursor as any;
|
|
454
|
-
if (
|
|
455
|
-
typeof maybeHost?.onSessionChange === 'function' ||
|
|
456
|
-
typeof maybeHost?.onAudioStarted === 'function' ||
|
|
457
|
-
typeof maybeHost?.onAudioEnded === 'function'
|
|
458
|
-
) {
|
|
459
|
-
return maybeHost;
|
|
460
|
-
}
|
|
461
|
-
cursor = cursor.parentElement;
|
|
462
|
-
}
|
|
463
|
-
return (document.querySelector('mc-populated-blank') as any) || null;
|
|
266
|
+
function onAudioStarted() {
|
|
267
|
+
resolveDeliveryHost(rootEl, { fallbackSelector: 'mc-populated-blank' })?.onAudioStarted?.();
|
|
464
268
|
}
|
|
465
269
|
|
|
466
|
-
function
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const featureAudioSkin = $derived.by(() => {
|
|
471
|
-
const locale = String(model?.locale || '').toLowerCase();
|
|
472
|
-
const lang = locale.slice(0, 2);
|
|
473
|
-
const byLocale =
|
|
474
|
-
model?.audioButtonSkinsByLocale && typeof model.audioButtonSkinsByLocale === 'object'
|
|
475
|
-
? model.audioButtonSkinsByLocale
|
|
476
|
-
: {};
|
|
477
|
-
const customSingle =
|
|
478
|
-
model?.audioButtonSkin && typeof model.audioButtonSkin === 'object'
|
|
479
|
-
? model.audioButtonSkin
|
|
480
|
-
: null;
|
|
481
|
-
const defaultSkin = locale.startsWith('es')
|
|
482
|
-
? DEFAULT_AUDIO_BUTTON_SKINS.es
|
|
483
|
-
: DEFAULT_AUDIO_BUTTON_SKINS.default;
|
|
484
|
-
return byLocale[locale] || byLocale[lang] || byLocale.default || customSingle || defaultSkin;
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
function playFeatureAudio() {
|
|
488
|
-
if (hasPlayableAudio && audioEl) {
|
|
489
|
-
audioEl.play().catch(() => {
|
|
490
|
-
// Keep behavior resilient: if playback is blocked, user can retry.
|
|
491
|
-
});
|
|
492
|
-
}
|
|
270
|
+
function onAudioEnded() {
|
|
271
|
+
resolveDeliveryHost(rootEl, { fallbackSelector: 'mc-populated-blank' })?.onAudioEnded?.();
|
|
493
272
|
}
|
|
494
273
|
|
|
495
274
|
$effect(() => {
|
|
@@ -498,49 +277,14 @@ $effect(() => {
|
|
|
498
277
|
}
|
|
499
278
|
});
|
|
500
279
|
|
|
501
|
-
$effect(() => {
|
|
502
|
-
const el = audioEl;
|
|
503
|
-
if (!el) return;
|
|
504
|
-
const handlePlaying = (e: Event) => onAudioPlaying(e);
|
|
505
|
-
const handleEnded = (e: Event) => onAudioEnded(e);
|
|
506
|
-
el.addEventListener('playing', handlePlaying);
|
|
507
|
-
el.addEventListener('ended', handleEnded);
|
|
508
|
-
return () => {
|
|
509
|
-
el.removeEventListener('playing', handlePlaying);
|
|
510
|
-
el.removeEventListener('ended', handleEnded);
|
|
511
|
-
};
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
$effect(() => {
|
|
515
|
-
const btn = featureAudioButtonEl;
|
|
516
|
-
if (!btn) return;
|
|
517
|
-
const handleClick = () => playFeatureAudio();
|
|
518
|
-
btn.addEventListener('click', handleClick);
|
|
519
|
-
return () => {
|
|
520
|
-
btn.removeEventListener('click', handleClick);
|
|
521
|
-
};
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
$effect(() => {
|
|
525
|
-
const btn = autoplayEnableButtonEl;
|
|
526
|
-
if (!btn) return;
|
|
527
|
-
const handleClick = () => handleEnableAutoplayClick();
|
|
528
|
-
btn.addEventListener('click', handleClick);
|
|
529
|
-
return () => {
|
|
530
|
-
btn.removeEventListener('click', handleClick);
|
|
531
|
-
};
|
|
532
|
-
});
|
|
533
|
-
|
|
534
280
|
$effect(() => {
|
|
535
281
|
const btn = toggleCorrectAnswerButtonEl;
|
|
536
282
|
if (!btn) return;
|
|
537
|
-
|
|
538
|
-
btn.
|
|
539
|
-
return () => {
|
|
540
|
-
btn.removeEventListener('click', handleClick);
|
|
541
|
-
};
|
|
283
|
+
btn.addEventListener('click', toggleCorrectAnswer);
|
|
284
|
+
return () => btn.removeEventListener('click', toggleCorrectAnswer);
|
|
542
285
|
});
|
|
543
286
|
|
|
287
|
+
// Delegated listeners on the choices group — two events with a radio-guard on change.
|
|
544
288
|
$effect(() => {
|
|
545
289
|
const group = choicesGroupEl;
|
|
546
290
|
if (!group) return;
|
|
@@ -559,32 +303,48 @@ $effect(() => {
|
|
|
559
303
|
});
|
|
560
304
|
|
|
561
305
|
$effect(() => {
|
|
562
|
-
|
|
563
|
-
if (autoplayAttempted) return;
|
|
564
|
-
|
|
565
|
-
autoplayAttempted = true;
|
|
566
|
-
if (audioEl && model?.audioUrl) {
|
|
567
|
-
// Browser autoplay restrictions vary. Try immediate play; if blocked show click-to-enable affordance.
|
|
568
|
-
audioEl.play().catch(() => {
|
|
569
|
-
autoPlayPromptOpen = true;
|
|
570
|
-
});
|
|
571
|
-
}
|
|
306
|
+
ensureVariantCssInjected(variantCssConfig);
|
|
572
307
|
});
|
|
573
308
|
|
|
574
309
|
$effect(() => {
|
|
575
|
-
|
|
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();
|
|
576
326
|
});
|
|
577
327
|
</script>
|
|
578
328
|
|
|
579
329
|
<div
|
|
330
|
+
bind:this={rootEl}
|
|
580
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' : ''}`}
|
|
581
332
|
lang={lang}
|
|
582
|
-
style={rootStyle}
|
|
333
|
+
style={layout.rootStyle}
|
|
583
334
|
>
|
|
584
335
|
{#if model?.prompt}
|
|
585
336
|
<div class="mb-4 prose pie-prompt" id={promptId}>{@html model.prompt}</div>
|
|
586
337
|
{/if}
|
|
587
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
|
+
|
|
588
348
|
{#if shouldShowCorrectAnswerToggle}
|
|
589
349
|
<div class="pie-correct-answer-toggle-row">
|
|
590
350
|
<button
|
|
@@ -653,69 +413,20 @@ $effect(() => {
|
|
|
653
413
|
</div>
|
|
654
414
|
{/if}
|
|
655
415
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
type="button"
|
|
671
|
-
aria-label={speechButtonLabel(model?.locale)}
|
|
672
|
-
>
|
|
673
|
-
<img
|
|
674
|
-
class={`listen-feature-icon pie-listen-icon rli-feature-listen ${isMediaPlaying ? '' : 'listen-active'}`}
|
|
675
|
-
src={featureAudioSkin.silentUrl}
|
|
676
|
-
alt=""
|
|
677
|
-
aria-hidden="true"
|
|
678
|
-
/>
|
|
679
|
-
<img
|
|
680
|
-
class={`listen-feature-icon pie-listen-icon rli-feature-listen ${isMediaPlaying ? 'listen-active' : ''}`}
|
|
681
|
-
src={featureAudioSkin.playingUrl}
|
|
682
|
-
alt=""
|
|
683
|
-
aria-hidden="true"
|
|
684
|
-
/>
|
|
685
|
-
</button>
|
|
686
|
-
{:else if hasPlayableAudio}
|
|
687
|
-
<audio
|
|
688
|
-
bind:this={audioEl}
|
|
689
|
-
controls
|
|
690
|
-
class="w-full max-w-md pie-audio-player"
|
|
691
|
-
preload="metadata"
|
|
692
|
-
src={model.audioUrl}
|
|
693
|
-
aria-describedby={model?.audioTranscript ? transcriptId : undefined}
|
|
694
|
-
>
|
|
695
|
-
<track kind="captions" />
|
|
696
|
-
</audio>
|
|
697
|
-
{#if autoPlayPromptOpen}
|
|
698
|
-
<button
|
|
699
|
-
bind:this={autoplayEnableButtonEl}
|
|
700
|
-
class="mt-2 text-sm underline pie-audio-autoplay-enable"
|
|
701
|
-
type="button"
|
|
702
|
-
>
|
|
703
|
-
{uiText.clickToEnableAutoplay}
|
|
704
|
-
</button>
|
|
705
|
-
{/if}
|
|
706
|
-
{:else if hasAudioButMissingResource}
|
|
707
|
-
<p class="text-sm text-red-700 pie-audio-error" role="alert">{audioErrorMessage}</p>
|
|
708
|
-
{/if}
|
|
709
|
-
{#if model?.audioTranscript}
|
|
710
|
-
<p
|
|
711
|
-
class={`text-sm mt-2 text-gray-700 pie-audio-transcript ${showVisibleTranscript ? '' : 'sr-only'}`}
|
|
712
|
-
id={transcriptId}
|
|
713
|
-
>
|
|
714
|
-
<strong>{uiText.transcriptLabel}:</strong> {model.audioTranscript}
|
|
715
|
-
</p>
|
|
716
|
-
{/if}
|
|
717
|
-
</div>
|
|
718
|
-
{/if}
|
|
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
|
+
/>
|
|
719
430
|
|
|
720
431
|
{#if model?.sentenceHtml}
|
|
721
432
|
<div class="mb-3 prose prose-p:my-1 sentence-line pie-sentence-line" aria-describedby={templateDescribedBy}>
|
|
@@ -726,27 +437,15 @@ $effect(() => {
|
|
|
726
437
|
{#if !isAudioOnlyMode}
|
|
727
438
|
<div class="mb-4 template-line pie-template-line" aria-describedby={templateDescribedBy}>
|
|
728
439
|
{@html templateParts.before}
|
|
729
|
-
<
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
<img
|
|
739
|
-
src={displayChoice.imageUrl}
|
|
740
|
-
alt={displayChoice.imageAlt || 'Selected answer image'}
|
|
741
|
-
class="w-auto object-contain pie-blank-image"
|
|
742
|
-
style="max-height:var(--mpb-selected-image-max-height, 4rem);"
|
|
743
|
-
/>
|
|
744
|
-
{:else if displayChoiceLabelHtml}
|
|
745
|
-
<span class="choice-html pie-blank-value">{@html displayChoiceLabelHtml}</span>
|
|
746
|
-
{:else}
|
|
747
|
-
<span class="blank-inner-empty" aria-hidden="true"> </span>
|
|
748
|
-
{/if}
|
|
749
|
-
</span>
|
|
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
|
+
/>
|
|
750
449
|
{@html templateParts.after}
|
|
751
450
|
</div>
|
|
752
451
|
{/if}
|
|
@@ -768,74 +467,17 @@ $effect(() => {
|
|
|
768
467
|
aria-describedby={resultText ? resultId : undefined}
|
|
769
468
|
>
|
|
770
469
|
{#each choices as c (c.id)}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
{#if choiceMode === 'image' && c.imageUrl}
|
|
783
|
-
<img
|
|
784
|
-
src={c.imageUrl}
|
|
785
|
-
alt={c.imageAlt || `Choice ${c.id}`}
|
|
786
|
-
class="object-contain mx-auto pie-choice-image"
|
|
787
|
-
style="max-height:var(--mpb-choice-image-max-height, 5rem);"
|
|
788
|
-
/>
|
|
789
|
-
{:else}
|
|
790
|
-
<span class="choice-html pie-choice-label">{@html c.labelHtml || ''}</span>
|
|
791
|
-
{/if}
|
|
792
|
-
</span>
|
|
793
|
-
<input
|
|
794
|
-
type="radio"
|
|
795
|
-
name={radioGroupName}
|
|
796
|
-
id={`${instanceId}-opt-${c.id}`}
|
|
797
|
-
value={c.id}
|
|
798
|
-
checked={
|
|
799
|
-
(showCorrectAnswer ? model?.correctChoiceId : selectedId) === c.id
|
|
800
|
-
}
|
|
801
|
-
disabled={model?.disabled}
|
|
802
|
-
class="choice-radio-bottom pie-choice-radio pie-choice-radio-bottom"
|
|
803
|
-
/>
|
|
804
|
-
</label>
|
|
805
|
-
{:else}
|
|
806
|
-
<input
|
|
807
|
-
type="radio"
|
|
808
|
-
name={radioGroupName}
|
|
809
|
-
id={`${instanceId}-opt-${c.id}`}
|
|
810
|
-
value={c.id}
|
|
811
|
-
checked={
|
|
812
|
-
(showCorrectAnswer ? model?.correctChoiceId : selectedId) === c.id
|
|
813
|
-
}
|
|
814
|
-
disabled={model?.disabled}
|
|
815
|
-
class="choice-radio-inline pie-choice-radio pie-choice-radio-inline"
|
|
816
|
-
/>
|
|
817
|
-
<label for={`${instanceId}-opt-${c.id}`} class="cursor-pointer flex-1 pie-choice-label-wrap">
|
|
818
|
-
{#if choiceMode === 'image' && c.imageUrl}
|
|
819
|
-
<img
|
|
820
|
-
src={c.imageUrl}
|
|
821
|
-
alt={c.imageAlt || `Choice ${c.id}`}
|
|
822
|
-
class="object-contain pie-choice-image"
|
|
823
|
-
style="max-height:var(--mpb-choice-image-max-height, 5rem);"
|
|
824
|
-
/>
|
|
825
|
-
{:else}
|
|
826
|
-
<span class="choice-html pie-choice-label">{@html c.labelHtml || ''}</span>
|
|
827
|
-
{/if}
|
|
828
|
-
</label>
|
|
829
|
-
{/if}
|
|
830
|
-
{#if isEvaluateMode && choiceCorrectness}
|
|
831
|
-
<span
|
|
832
|
-
class={`pie-choice-feedback-badge ${choiceCorrectness === 'correct' ? 'pie-choice-feedback-correct' : 'pie-choice-feedback-incorrect'}`}
|
|
833
|
-
aria-hidden="true"
|
|
834
|
-
>
|
|
835
|
-
{choiceCorrectness === 'correct' ? '✓' : '✕'}
|
|
836
|
-
</span>
|
|
837
|
-
{/if}
|
|
838
|
-
</div>
|
|
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
|
+
/>
|
|
839
481
|
{/each}
|
|
840
482
|
</div>
|
|
841
483
|
</fieldset>
|
|
@@ -858,19 +500,6 @@ $effect(() => {
|
|
|
858
500
|
max-width: none;
|
|
859
501
|
}
|
|
860
502
|
|
|
861
|
-
.blank-slot:focus-within {
|
|
862
|
-
outline: 2px solid var(--pie-focus, #2563eb);
|
|
863
|
-
outline-offset: 2px;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
.blank-slot {
|
|
867
|
-
display: inline-flex;
|
|
868
|
-
align-items: center;
|
|
869
|
-
justify-content: center;
|
|
870
|
-
text-align: center;
|
|
871
|
-
vertical-align: baseline;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
503
|
.pie-toggle-correct-answer {
|
|
875
504
|
width: 100%;
|
|
876
505
|
cursor: pointer;
|
|
@@ -930,166 +559,8 @@ $effect(() => {
|
|
|
930
559
|
margin: 0;
|
|
931
560
|
}
|
|
932
561
|
|
|
933
|
-
.
|
|
934
|
-
|
|
935
|
-
min-width: 4ch;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
.blank-slot-standalone {
|
|
939
|
-
width: var(--mpb-blank-standalone-width, 7rem);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
.listen-button {
|
|
943
|
-
width: var(--mpb-listen-button-size, 128px);
|
|
944
|
-
height: var(--mpb-listen-button-size, 128px);
|
|
945
|
-
border: 0;
|
|
946
|
-
padding: 0;
|
|
947
|
-
background-color: transparent;
|
|
948
|
-
cursor: pointer;
|
|
949
|
-
z-index: 1;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
.listen-button:hover {
|
|
953
|
-
background-color: #e2f1fe;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
.listen-feature-icon {
|
|
957
|
-
width: var(--mpb-listen-button-size, 128px);
|
|
958
|
-
height: var(--mpb-listen-button-size, 128px);
|
|
959
|
-
object-fit: contain;
|
|
960
|
-
display: none;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
.listen-feature-icon.listen-active {
|
|
964
|
-
display: block;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
.choice-row-horizontal {
|
|
968
|
-
flex-direction: column;
|
|
969
|
-
align-items: center;
|
|
970
|
-
width: min(var(--mpb-choice-width-px, 170px), var(--mpb-choice-width-vw, 30vw));
|
|
971
|
-
gap: 0;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
.choice-tile {
|
|
975
|
-
display: flex;
|
|
976
|
-
flex-direction: column;
|
|
977
|
-
justify-content: space-between;
|
|
978
|
-
align-items: center;
|
|
979
|
-
width: 100%;
|
|
980
|
-
min-height: var(--mpb-choice-tile-min-height, 11rem);
|
|
981
|
-
padding: 0.8rem 0.65rem 0.5rem;
|
|
982
|
-
border-radius: 8px;
|
|
983
|
-
background: transparent;
|
|
984
|
-
transition: background-color 120ms ease-in-out;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
.choice-tile-content {
|
|
988
|
-
width: 100%;
|
|
989
|
-
display: flex;
|
|
990
|
-
align-items: center;
|
|
991
|
-
justify-content: center;
|
|
992
|
-
min-height: var(--mpb-choice-content-min-height, 7.5rem);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
.choice-row-horizontal:hover .choice-tile {
|
|
996
|
-
background: var(--pie-correct-answer-choice-hover-bg, #ececec);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
.pie-choice-horizontal:not(.is-selected):not(:hover) .pie-choice-tile {
|
|
1000
|
-
background: transparent;
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
.choice-row-horizontal.is-selected .choice-tile {
|
|
1004
|
-
background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
.choice-row-horizontal.is-selected:hover .choice-tile {
|
|
1008
|
-
background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
.pie-choice:not(.pie-choice-horizontal):hover .pie-choice-label-wrap {
|
|
1012
|
-
background: var(--pie-correct-answer-choice-hover-bg, #ececec);
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
.pie-choice:not(.pie-choice-horizontal):not(.is-selected):not(:hover) .pie-choice-label-wrap {
|
|
1016
|
-
background: transparent;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
.pie-choice:not(.pie-choice-horizontal).is-selected .pie-choice-label-wrap {
|
|
1020
|
-
background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
|
|
1021
|
-
border-radius: 6px;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
.pie-choice:not(.pie-choice-horizontal).is-selected:hover .pie-choice-label-wrap {
|
|
1025
|
-
background: var(--pie-correct-answer-choice-selected-bg, #f1f1f1);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
.pie-choice.choice-correct {
|
|
1029
|
-
border-left: 3px solid var(--pie-correct-answer-choice-correct-border, #0ea449);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
.pie-choice.choice-incorrect {
|
|
1033
|
-
border-left: 3px solid var(--pie-correct-answer-choice-incorrect-border, #bf0d00);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
.pie-choice-horizontal.choice-correct .choice-tile {
|
|
1037
|
-
background: var(--pie-correct-answer-choice-correct-bg, #e8f5e9);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
.pie-choice-horizontal.choice-incorrect .choice-tile {
|
|
1041
|
-
background: var(--pie-correct-answer-choice-incorrect-bg, #ffebee);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
.pie-choice-feedback-badge {
|
|
1045
|
-
margin-left: auto;
|
|
1046
|
-
display: inline-flex;
|
|
1047
|
-
align-items: center;
|
|
1048
|
-
justify-content: center;
|
|
1049
|
-
width: 1.1rem;
|
|
1050
|
-
height: 1.1rem;
|
|
1051
|
-
border-radius: 9999px;
|
|
1052
|
-
font-size: 0.72rem;
|
|
1053
|
-
line-height: 1;
|
|
1054
|
-
font-weight: 700;
|
|
1055
|
-
color: var(--pie-correct-answer-feedback-glyph-color, #fff);
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
.pie-choice-feedback-correct {
|
|
1059
|
-
background: var(--pie-correct-answer-feedback-correct-bg, #087d38);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
.pie-choice-feedback-incorrect {
|
|
1063
|
-
background: var(--pie-correct-answer-feedback-incorrect-bg, #bf0d00);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
.choice-row-horizontal :global(p) {
|
|
1067
|
-
margin: 0;
|
|
1068
|
-
text-align: center;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
.choice-html :global(p) {
|
|
1072
|
-
margin: 0;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
.choice-html {
|
|
1076
|
-
width: 100%;
|
|
1077
|
-
display: inline-flex;
|
|
1078
|
-
align-items: center;
|
|
1079
|
-
justify-content: center;
|
|
1080
|
-
text-align: center;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
.choice-radio-bottom {
|
|
1084
|
-
margin-top: var(--mpb-horizontal-choice-radio-top-margin, 0.5rem);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
.choice-radio-inline {
|
|
1088
|
-
margin-top: var(--mpb-horizontal-choice-radio-top-margin, 0.5rem);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
.layout-audio_blank_only .audio-container,
|
|
1092
|
-
.layout-stimulus_image_blank .audio-container {
|
|
562
|
+
.layout-audio_blank_only :global(.pie-audio-container),
|
|
563
|
+
.layout-stimulus_image_blank :global(.pie-audio-container) {
|
|
1093
564
|
display: flex;
|
|
1094
565
|
width: min(100%, var(--mpb-audio-instructions-max-width, 875px));
|
|
1095
566
|
margin-left: auto;
|
|
@@ -1104,16 +575,24 @@ $effect(() => {
|
|
|
1104
575
|
var(--mpb-audio-blank-template-margin-bottom, 1.8rem);
|
|
1105
576
|
}
|
|
1106
577
|
|
|
1107
|
-
.layout-audio_blank_only
|
|
1108
|
-
|
|
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) {
|
|
1109
585
|
width: var(--mpb-blank-wide-width, 10rem);
|
|
1110
|
-
border-bottom-width: var(--mpb-blank-underline-wide-width,
|
|
586
|
+
border-bottom-width: var(--mpb-blank-underline-wide-width, 6px);
|
|
587
|
+
min-height: 160px;
|
|
588
|
+
padding-bottom: 4px;
|
|
1111
589
|
}
|
|
1112
590
|
|
|
1113
591
|
.layout-stimulus_image_blank {
|
|
1114
592
|
display: grid;
|
|
1115
593
|
grid-template-columns: minmax(var(--mpb-stimulus-min-column, 210px), 1fr) auto;
|
|
1116
594
|
grid-template-areas:
|
|
595
|
+
'transcript transcript'
|
|
1117
596
|
'sentence audio'
|
|
1118
597
|
'. template'
|
|
1119
598
|
'choices choices';
|
|
@@ -1122,7 +601,11 @@ $effect(() => {
|
|
|
1122
601
|
align-items: start;
|
|
1123
602
|
}
|
|
1124
603
|
|
|
1125
|
-
.layout-stimulus_image_blank .audio-
|
|
604
|
+
.layout-stimulus_image_blank .pie-audio-transcript {
|
|
605
|
+
grid-area: transcript;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.layout-stimulus_image_blank :global(.pie-audio-container) {
|
|
1126
609
|
grid-area: audio;
|
|
1127
610
|
margin: 0;
|
|
1128
611
|
}
|
|
@@ -1146,16 +629,21 @@ $effect(() => {
|
|
|
1146
629
|
|
|
1147
630
|
.layout-token_sequence {
|
|
1148
631
|
display: grid;
|
|
1149
|
-
grid-template-columns:
|
|
632
|
+
grid-template-columns: 1fr auto;
|
|
1150
633
|
grid-template-areas:
|
|
1151
|
-
'
|
|
1152
|
-
'
|
|
634
|
+
'transcript audio'
|
|
635
|
+
'template template'
|
|
636
|
+
'choices choices';
|
|
1153
637
|
column-gap: var(--mpb-token-grid-column-gap, 1.5rem);
|
|
1154
638
|
row-gap: var(--mpb-token-grid-row-gap, 0.8rem);
|
|
1155
639
|
align-items: start;
|
|
1156
640
|
}
|
|
1157
641
|
|
|
1158
|
-
.layout-token_sequence .audio-
|
|
642
|
+
.layout-token_sequence .pie-audio-transcript {
|
|
643
|
+
grid-area: transcript;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.layout-token_sequence :global(.pie-audio-container) {
|
|
1159
647
|
grid-area: audio;
|
|
1160
648
|
margin: 0;
|
|
1161
649
|
justify-self: end;
|
|
@@ -1172,7 +660,7 @@ $effect(() => {
|
|
|
1172
660
|
margin-left: var(--mpb-token-inline-token-gap, 0.35rem);
|
|
1173
661
|
}
|
|
1174
662
|
|
|
1175
|
-
.layout-token_sequence .blank-slot {
|
|
663
|
+
.layout-token_sequence :global(.pie-blank-slot) {
|
|
1176
664
|
width: var(--mpb-blank-standalone-width, 7rem);
|
|
1177
665
|
border-bottom-width: var(--mpb-blank-underline-wide-width, 4px);
|
|
1178
666
|
margin-left: var(--mpb-token-inline-token-gap, 0.35rem);
|
|
@@ -1187,6 +675,7 @@ $effect(() => {
|
|
|
1187
675
|
display: grid;
|
|
1188
676
|
grid-template-columns: minmax(var(--mpb-text-min-column, 260px), 1fr) auto;
|
|
1189
677
|
grid-template-areas:
|
|
678
|
+
'transcript transcript'
|
|
1190
679
|
'template audio'
|
|
1191
680
|
'choices choices';
|
|
1192
681
|
column-gap: var(--mpb-inline-grid-column-gap, 1.5rem);
|
|
@@ -1194,7 +683,11 @@ $effect(() => {
|
|
|
1194
683
|
align-items: start;
|
|
1195
684
|
}
|
|
1196
685
|
|
|
1197
|
-
.layout-inline_sentence.has-inline-audio .audio-
|
|
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) {
|
|
1198
691
|
grid-area: audio;
|
|
1199
692
|
margin: 0;
|
|
1200
693
|
justify-self: end;
|
|
@@ -1215,10 +708,10 @@ $effect(() => {
|
|
|
1215
708
|
/* Match Learnosity responsive behavior for CQT audio-blank layouts:
|
|
1216
709
|
at smaller widths, audio control shifts left and answer tiles stack. */
|
|
1217
710
|
@media (max-width: 760px) {
|
|
1218
|
-
.layout-audio_blank_only .audio-container,
|
|
1219
|
-
.layout-stimulus_image_blank .audio-container,
|
|
1220
|
-
.layout-token_sequence .audio-container,
|
|
1221
|
-
.layout-inline_sentence.has-inline-audio .audio-container {
|
|
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) {
|
|
1222
715
|
width: 100%;
|
|
1223
716
|
margin-left: 0;
|
|
1224
717
|
margin-right: 0;
|