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