@pie-element/mc-populated-blank 0.2.0 → 0.2.1
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 +9 -3
- package/dist/controller/defaults.d.ts +4 -0
- package/dist/controller/defaults.d.ts.map +1 -1
- package/dist/controller/index.d.ts.map +1 -1
- package/dist/controller/index.js +5 -4
- package/dist/controller/index.js.map +1 -1
- package/dist/delivery/index.js +284 -108
- package/dist/delivery/index.js.map +1 -1
- package/dist/index.iife.d.ts +2 -1
- package/dist/index.iife.d.ts.map +1 -1
- package/dist/index.iife.js +19 -5
- package/dist/index.js +284 -108
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/controller/defaults.ts +2 -0
- package/src/controller/index.ts +3 -4
- package/src/delivery/McPopulatedBlank.svelte +193 -78
- package/src/index.iife.ts +2 -8
package/package.json
CHANGED
|
@@ -22,6 +22,8 @@ export const DEFAULT_LAYOUT_LIMITS = {
|
|
|
22
22
|
horizontalChoiceRadioTopMarginRem: 0.5,
|
|
23
23
|
audioBlankTemplateMarginTopRem: 0.8,
|
|
24
24
|
audioBlankTemplateMarginBottomRem: 1.8,
|
|
25
|
+
audioInstructionsMaxWidthPx: 875,
|
|
26
|
+
narrowHorizontalChoiceMaxWidthPx: 230,
|
|
25
27
|
stimulusGridColumnGapRem: 2,
|
|
26
28
|
stimulusGridRowGapRem: 0.7,
|
|
27
29
|
stimulusSentenceMarginTopRem: 0.2,
|
package/src/controller/index.ts
CHANGED
|
@@ -163,10 +163,7 @@ export const model = async (question: any, session: any, env: any, updateSession
|
|
|
163
163
|
typeof normalizedQuestion.layoutProfilePresets === 'object'
|
|
164
164
|
? normalizedQuestion.layoutProfilePresets
|
|
165
165
|
: {},
|
|
166
|
-
layoutLimits: {
|
|
167
|
-
...DEFAULT_LAYOUT_LIMITS,
|
|
168
|
-
...(normalizedQuestion.layoutLimits || {}),
|
|
169
|
-
},
|
|
166
|
+
layoutLimits: normalizedQuestion.layoutLimits || { ...DEFAULT_LAYOUT_LIMITS },
|
|
170
167
|
audioButtonSkin:
|
|
171
168
|
normalizedQuestion.audioButtonSkin && typeof normalizedQuestion.audioButtonSkin === 'object'
|
|
172
169
|
? normalizedQuestion.audioButtonSkin
|
|
@@ -324,6 +321,8 @@ export const validate = (model: any = {}, _config: any = {}) => {
|
|
|
324
321
|
'horizontalChoiceRadioTopMarginRem',
|
|
325
322
|
'audioBlankTemplateMarginTopRem',
|
|
326
323
|
'audioBlankTemplateMarginBottomRem',
|
|
324
|
+
'audioInstructionsMaxWidthPx',
|
|
325
|
+
'narrowHorizontalChoiceMaxWidthPx',
|
|
327
326
|
'stimulusGridColumnGapRem',
|
|
328
327
|
'stimulusGridRowGapRem',
|
|
329
328
|
'stimulusSentenceMarginTopRem',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<svelte:options
|
|
2
2
|
customElement={{
|
|
3
|
-
tag: 'mc-populated-blank
|
|
3
|
+
tag: 'mc-populated-blank',
|
|
4
4
|
shadow: 'none',
|
|
5
5
|
props: {
|
|
6
6
|
model: { type: 'Object' },
|
|
@@ -34,6 +34,8 @@ const DEFAULT_LAYOUT_LIMITS = {
|
|
|
34
34
|
horizontalChoiceRadioTopMarginRem: 0.5,
|
|
35
35
|
audioBlankTemplateMarginTopRem: 0.8,
|
|
36
36
|
audioBlankTemplateMarginBottomRem: 1.8,
|
|
37
|
+
audioInstructionsMaxWidthPx: 875,
|
|
38
|
+
narrowHorizontalChoiceMaxWidthPx: 230,
|
|
37
39
|
stimulusGridColumnGapRem: 2,
|
|
38
40
|
stimulusGridRowGapRem: 0.7,
|
|
39
41
|
stimulusSentenceMarginTopRem: 0.2,
|
|
@@ -88,21 +90,25 @@ const DEFAULT_UI_TEXT = {
|
|
|
88
90
|
listenLabelEs: 'Escuchar',
|
|
89
91
|
} as const;
|
|
90
92
|
|
|
91
|
-
let
|
|
93
|
+
let { model, session, options } = $props<{ model?: any; session?: any; options?: any }>();
|
|
92
94
|
let audioEl = $state<HTMLAudioElement | null>(null);
|
|
93
95
|
let isMediaPlaying = $state(false);
|
|
94
96
|
let localChoiceId = $state('');
|
|
95
97
|
let autoPlayPromptOpen = $state(false);
|
|
96
98
|
let autoplayAttempted = $state(false);
|
|
99
|
+
let featureAudioButtonEl = $state<HTMLButtonElement | null>(null);
|
|
100
|
+
let autoplayEnableButtonEl = $state<HTMLButtonElement | null>(null);
|
|
101
|
+
let toggleCorrectAnswerButtonEl = $state<HTMLButtonElement | null>(null);
|
|
102
|
+
let choicesGroupEl = $state<HTMLDivElement | null>(null);
|
|
97
103
|
const instanceId = `mc-populated-blank-${Math.random().toString(36).slice(2, 10)}`;
|
|
98
104
|
|
|
99
|
-
const isEvaluateMode = $derived(
|
|
100
|
-
const correctness = $derived(
|
|
105
|
+
const isEvaluateMode = $derived(model?.env?.mode === 'evaluate');
|
|
106
|
+
const correctness = $derived(model?.correctness);
|
|
101
107
|
const isCorrect = $derived(correctness === 'correct');
|
|
102
108
|
const isIncorrect = $derived(correctness === 'incorrect');
|
|
103
|
-
const isAudioOnlyMode = $derived(
|
|
109
|
+
const isAudioOnlyMode = $derived(model?.interactionMode === 'audio_mc_only');
|
|
104
110
|
const isBlankOnlyTemplate = $derived.by(() => {
|
|
105
|
-
const t =
|
|
111
|
+
const t = model?.template || '';
|
|
106
112
|
if (!t) return false;
|
|
107
113
|
const plain = t
|
|
108
114
|
.replace(/<[^>]+>/g, '')
|
|
@@ -110,27 +116,27 @@ const isBlankOnlyTemplate = $derived.by(() => {
|
|
|
110
116
|
.trim();
|
|
111
117
|
return plain === BLANK_TOKEN;
|
|
112
118
|
});
|
|
113
|
-
const layoutProfile = $derived(
|
|
119
|
+
const layoutProfile = $derived(model?.layoutProfile || '');
|
|
114
120
|
const choiceLayout = $derived(
|
|
115
|
-
|
|
121
|
+
model?.choiceLayout || (isAudioOnlyMode || isBlankOnlyTemplate ? 'horizontal' : 'vertical')
|
|
116
122
|
);
|
|
117
123
|
const isHorizontalChoices = $derived(choiceLayout === 'horizontal');
|
|
118
|
-
const showVisibleTranscript = $derived(!!
|
|
119
|
-
const hasPlayableAudio = $derived(!!
|
|
120
|
-
const hasAudioButMissingResource = $derived(!!
|
|
124
|
+
const showVisibleTranscript = $derived(!!model?.showVisibleTranscript);
|
|
125
|
+
const hasPlayableAudio = $derived(!!model?.hasAudio && !!model?.audioUrl);
|
|
126
|
+
const hasAudioButMissingResource = $derived(!!model?.hasAudio && !model?.audioUrl);
|
|
121
127
|
const hasInlineSentenceAudioLayout = $derived(
|
|
122
|
-
layoutProfile === 'inline_sentence' && !!
|
|
128
|
+
layoutProfile === 'inline_sentence' && !!model?.hasAudio
|
|
123
129
|
);
|
|
124
130
|
const uiText = $derived.by(() => ({
|
|
125
131
|
...DEFAULT_UI_TEXT,
|
|
126
|
-
...(
|
|
132
|
+
...(model?.uiText || {}),
|
|
127
133
|
}));
|
|
128
134
|
const profilePresetLimits = $derived.by(() => {
|
|
129
135
|
const profile = String(layoutProfile || '');
|
|
130
136
|
const defaults = DEFAULT_LAYOUT_PROFILE_PRESETS[profile] || {};
|
|
131
137
|
const customPresets =
|
|
132
|
-
|
|
133
|
-
?
|
|
138
|
+
model?.layoutProfilePresets && typeof model.layoutProfilePresets === 'object'
|
|
139
|
+
? model.layoutProfilePresets
|
|
134
140
|
: {};
|
|
135
141
|
const custom =
|
|
136
142
|
customPresets[profile] && typeof customPresets[profile] === 'object'
|
|
@@ -143,9 +149,7 @@ const profilePresetLimits = $derived.by(() => {
|
|
|
143
149
|
});
|
|
144
150
|
const layoutLimits = $derived.by(() => {
|
|
145
151
|
const configured =
|
|
146
|
-
|
|
147
|
-
? props.model.layoutLimits
|
|
148
|
-
: {};
|
|
152
|
+
model?.layoutLimits && typeof model.layoutLimits === 'object' ? model.layoutLimits : {};
|
|
149
153
|
return {
|
|
150
154
|
...DEFAULT_LAYOUT_LIMITS,
|
|
151
155
|
...profilePresetLimits,
|
|
@@ -173,6 +177,8 @@ const rootStyle = $derived.by(() =>
|
|
|
173
177
|
`--mpb-horizontal-choice-radio-top-margin:${layoutLimits.horizontalChoiceRadioTopMarginRem}rem`,
|
|
174
178
|
`--mpb-audio-blank-template-margin-top:${layoutLimits.audioBlankTemplateMarginTopRem}rem`,
|
|
175
179
|
`--mpb-audio-blank-template-margin-bottom:${layoutLimits.audioBlankTemplateMarginBottomRem}rem`,
|
|
180
|
+
`--mpb-audio-instructions-max-width:${layoutLimits.audioInstructionsMaxWidthPx}px`,
|
|
181
|
+
`--mpb-narrow-choice-max-width:${layoutLimits.narrowHorizontalChoiceMaxWidthPx}px`,
|
|
176
182
|
`--mpb-stimulus-grid-column-gap:${layoutLimits.stimulusGridColumnGapRem}rem`,
|
|
177
183
|
`--mpb-stimulus-grid-row-gap:${layoutLimits.stimulusGridRowGapRem}rem`,
|
|
178
184
|
`--mpb-stimulus-sentence-margin-top:${layoutLimits.stimulusSentenceMarginTopRem}rem`,
|
|
@@ -189,12 +195,12 @@ const rootStyle = $derived.by(() =>
|
|
|
189
195
|
].join(';')
|
|
190
196
|
);
|
|
191
197
|
const useFeatureButtonAudio = $derived.by(() => {
|
|
192
|
-
if (typeof
|
|
193
|
-
return !!
|
|
198
|
+
if (typeof model?.useFeatureButtonAudio === 'boolean') {
|
|
199
|
+
return !!model.useFeatureButtonAudio;
|
|
194
200
|
}
|
|
195
201
|
const profile = layoutProfile || '';
|
|
196
202
|
return (
|
|
197
|
-
!!
|
|
203
|
+
!!model?.hasAudio &&
|
|
198
204
|
(profile === 'audio_blank_only' ||
|
|
199
205
|
profile === 'stimulus_image_blank' ||
|
|
200
206
|
profile === 'token_sequence')
|
|
@@ -202,7 +208,7 @@ const useFeatureButtonAudio = $derived.by(() => {
|
|
|
202
208
|
});
|
|
203
209
|
|
|
204
210
|
const templateParts = $derived.by(() => {
|
|
205
|
-
const t =
|
|
211
|
+
const t = model?.template || '';
|
|
206
212
|
const idx = t.indexOf(BLANK_TOKEN);
|
|
207
213
|
if (idx < 0) {
|
|
208
214
|
return { before: t, after: '' };
|
|
@@ -228,16 +234,16 @@ const blankBorderWidth = $derived.by(() => {
|
|
|
228
234
|
return `${layoutLimits.blankUnderlineWidthPx}px`;
|
|
229
235
|
});
|
|
230
236
|
|
|
231
|
-
const choices = $derived(Array.isArray(
|
|
232
|
-
const choiceMode = $derived(
|
|
233
|
-
const selectedId = $derived(
|
|
234
|
-
const radioGroupName = $derived(`${instanceId}-choice-group-${
|
|
237
|
+
const choices = $derived(Array.isArray(model?.choices) ? model.choices : []);
|
|
238
|
+
const choiceMode = $derived(model?.choiceMode || 'text');
|
|
239
|
+
const selectedId = $derived(session?.choiceId || localChoiceId || '');
|
|
240
|
+
const radioGroupName = $derived(`${instanceId}-choice-group-${model?.id || '1'}`);
|
|
235
241
|
|
|
236
242
|
let showCorrectAnswer = $state(false);
|
|
237
243
|
|
|
238
244
|
const displayChoiceId = $derived.by(() => {
|
|
239
|
-
if (isEvaluateMode && showCorrectAnswer &&
|
|
240
|
-
return
|
|
245
|
+
if (isEvaluateMode && showCorrectAnswer && model?.correctChoiceId) {
|
|
246
|
+
return model.correctChoiceId;
|
|
241
247
|
}
|
|
242
248
|
return selectedId;
|
|
243
249
|
});
|
|
@@ -245,7 +251,7 @@ const displayChoiceId = $derived.by(() => {
|
|
|
245
251
|
const displayChoice = $derived.by(() => choices.find((c: any) => c.id === displayChoiceId));
|
|
246
252
|
|
|
247
253
|
const legendText = $derived.by(() => {
|
|
248
|
-
const plain = (
|
|
254
|
+
const plain = (model?.prompt || '')
|
|
249
255
|
.replace(/<[^>]+>/g, ' ')
|
|
250
256
|
.replace(/\s+/g, ' ')
|
|
251
257
|
.trim();
|
|
@@ -263,19 +269,25 @@ const transcriptId = $derived(`${instanceId}-transcript`);
|
|
|
263
269
|
const legendId = $derived(`${instanceId}-choices-legend`);
|
|
264
270
|
const resultId = $derived(`${instanceId}-result`);
|
|
265
271
|
|
|
266
|
-
const choicesGroupLabelledBy = $derived(
|
|
272
|
+
const choicesGroupLabelledBy = $derived(model?.prompt ? promptId : undefined);
|
|
273
|
+
const choicesGroupAriaLabel = $derived.by(() => {
|
|
274
|
+
if (model?.prompt) return undefined;
|
|
275
|
+
const explicit = String(model?.choiceGroupLabel || '').trim();
|
|
276
|
+
if (explicit) return explicit;
|
|
277
|
+
return legendText;
|
|
278
|
+
});
|
|
267
279
|
|
|
268
280
|
const templateDescribedBy = $derived.by(() => {
|
|
269
281
|
const ids: string[] = [];
|
|
270
|
-
if (
|
|
271
|
-
if (
|
|
282
|
+
if (model?.prompt) ids.push(promptId);
|
|
283
|
+
if (model?.hasAudio && model?.audioTranscript) {
|
|
272
284
|
ids.push(transcriptId);
|
|
273
285
|
}
|
|
274
286
|
return ids.length ? ids.join(' ') : undefined;
|
|
275
287
|
});
|
|
276
288
|
|
|
277
289
|
const lang = $derived.by(() => {
|
|
278
|
-
const locale =
|
|
290
|
+
const locale = model?.locale || '';
|
|
279
291
|
return locale ? locale.slice(0, 2) : 'en';
|
|
280
292
|
});
|
|
281
293
|
const audioErrorMessage = $derived.by(() =>
|
|
@@ -290,9 +302,7 @@ const resultText = $derived.by(() => {
|
|
|
290
302
|
});
|
|
291
303
|
|
|
292
304
|
function emitSession(updatedSession: any, sourceEl?: HTMLElement | null) {
|
|
293
|
-
const host =
|
|
294
|
-
(sourceEl?.closest?.('mc-populated-blank-element') as any) ||
|
|
295
|
-
(document.querySelector('mc-populated-blank-element') as any);
|
|
305
|
+
const host = getHostElement(sourceEl);
|
|
296
306
|
if (host?.onSessionChange) {
|
|
297
307
|
host.onSessionChange(updatedSession);
|
|
298
308
|
return;
|
|
@@ -312,8 +322,8 @@ function onRadioChange(e: Event) {
|
|
|
312
322
|
const choiceId = input.value;
|
|
313
323
|
localChoiceId = choiceId;
|
|
314
324
|
const updatedSession = {
|
|
315
|
-
...
|
|
316
|
-
id:
|
|
325
|
+
...session,
|
|
326
|
+
id: session?.id || model?.id || '1',
|
|
317
327
|
element: 'mc-populated-blank',
|
|
318
328
|
choiceId,
|
|
319
329
|
};
|
|
@@ -325,7 +335,7 @@ function toggleCorrectAnswer() {
|
|
|
325
335
|
}
|
|
326
336
|
|
|
327
337
|
function onRadioGroupKeydown(e: KeyboardEvent) {
|
|
328
|
-
if (
|
|
338
|
+
if (model?.disabled) return;
|
|
329
339
|
const key = e.key;
|
|
330
340
|
if (!['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'].includes(key)) return;
|
|
331
341
|
e.preventDefault();
|
|
@@ -355,33 +365,48 @@ function handleEnableAutoplayClick() {
|
|
|
355
365
|
}
|
|
356
366
|
|
|
357
367
|
function onAudioPlaying(e: Event) {
|
|
358
|
-
const host = (e.currentTarget as HTMLElement)
|
|
368
|
+
const host = getHostElement(e.currentTarget as HTMLElement);
|
|
359
369
|
isMediaPlaying = true;
|
|
360
370
|
host?.onAudioStarted?.();
|
|
361
371
|
autoPlayPromptOpen = false;
|
|
362
372
|
}
|
|
363
373
|
|
|
364
374
|
function onAudioEnded(e: Event) {
|
|
365
|
-
const host = (e.currentTarget as HTMLElement)
|
|
375
|
+
const host = getHostElement(e.currentTarget as HTMLElement);
|
|
366
376
|
isMediaPlaying = false;
|
|
367
377
|
host?.onAudioEnded?.();
|
|
368
378
|
}
|
|
369
379
|
|
|
380
|
+
function getHostElement(sourceEl?: HTMLElement | null) {
|
|
381
|
+
let cursor: HTMLElement | null | undefined = sourceEl;
|
|
382
|
+
while (cursor) {
|
|
383
|
+
const maybeHost = cursor as any;
|
|
384
|
+
if (
|
|
385
|
+
typeof maybeHost?.onSessionChange === 'function' ||
|
|
386
|
+
typeof maybeHost?.onAudioStarted === 'function' ||
|
|
387
|
+
typeof maybeHost?.onAudioEnded === 'function'
|
|
388
|
+
) {
|
|
389
|
+
return maybeHost;
|
|
390
|
+
}
|
|
391
|
+
cursor = cursor.parentElement;
|
|
392
|
+
}
|
|
393
|
+
return (document.querySelector('mc-populated-blank') as any) || null;
|
|
394
|
+
}
|
|
395
|
+
|
|
370
396
|
function speechButtonLabel(locale = '') {
|
|
371
397
|
return locale.toLowerCase().startsWith('es') ? uiText.listenLabelEs : uiText.listenLabelEn;
|
|
372
398
|
}
|
|
373
399
|
|
|
374
400
|
const featureAudioSkin = $derived.by(() => {
|
|
375
|
-
const locale = String(
|
|
401
|
+
const locale = String(model?.locale || '').toLowerCase();
|
|
376
402
|
const lang = locale.slice(0, 2);
|
|
377
403
|
const byLocale =
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
? props.model.audioButtonSkinsByLocale
|
|
404
|
+
model?.audioButtonSkinsByLocale && typeof model.audioButtonSkinsByLocale === 'object'
|
|
405
|
+
? model.audioButtonSkinsByLocale
|
|
381
406
|
: {};
|
|
382
407
|
const customSingle =
|
|
383
|
-
|
|
384
|
-
?
|
|
408
|
+
model?.audioButtonSkin && typeof model.audioButtonSkin === 'object'
|
|
409
|
+
? model.audioButtonSkin
|
|
385
410
|
: null;
|
|
386
411
|
const defaultSkin = locale.startsWith('es')
|
|
387
412
|
? DEFAULT_AUDIO_BUTTON_SKINS.es
|
|
@@ -398,17 +423,77 @@ function playFeatureAudio() {
|
|
|
398
423
|
}
|
|
399
424
|
|
|
400
425
|
$effect(() => {
|
|
401
|
-
if (
|
|
402
|
-
localChoiceId =
|
|
426
|
+
if (session?.choiceId) {
|
|
427
|
+
localChoiceId = session.choiceId;
|
|
403
428
|
}
|
|
404
429
|
});
|
|
405
430
|
|
|
406
431
|
$effect(() => {
|
|
407
|
-
|
|
432
|
+
const el = audioEl;
|
|
433
|
+
if (!el) return;
|
|
434
|
+
const handlePlaying = (e: Event) => onAudioPlaying(e);
|
|
435
|
+
const handleEnded = (e: Event) => onAudioEnded(e);
|
|
436
|
+
el.addEventListener('playing', handlePlaying);
|
|
437
|
+
el.addEventListener('ended', handleEnded);
|
|
438
|
+
return () => {
|
|
439
|
+
el.removeEventListener('playing', handlePlaying);
|
|
440
|
+
el.removeEventListener('ended', handleEnded);
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
$effect(() => {
|
|
445
|
+
const btn = featureAudioButtonEl;
|
|
446
|
+
if (!btn) return;
|
|
447
|
+
const handleClick = () => playFeatureAudio();
|
|
448
|
+
btn.addEventListener('click', handleClick);
|
|
449
|
+
return () => {
|
|
450
|
+
btn.removeEventListener('click', handleClick);
|
|
451
|
+
};
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
$effect(() => {
|
|
455
|
+
const btn = autoplayEnableButtonEl;
|
|
456
|
+
if (!btn) return;
|
|
457
|
+
const handleClick = () => handleEnableAutoplayClick();
|
|
458
|
+
btn.addEventListener('click', handleClick);
|
|
459
|
+
return () => {
|
|
460
|
+
btn.removeEventListener('click', handleClick);
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
$effect(() => {
|
|
465
|
+
const btn = toggleCorrectAnswerButtonEl;
|
|
466
|
+
if (!btn) return;
|
|
467
|
+
const handleClick = () => toggleCorrectAnswer();
|
|
468
|
+
btn.addEventListener('click', handleClick);
|
|
469
|
+
return () => {
|
|
470
|
+
btn.removeEventListener('click', handleClick);
|
|
471
|
+
};
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
$effect(() => {
|
|
475
|
+
const group = choicesGroupEl;
|
|
476
|
+
if (!group) return;
|
|
477
|
+
const handleChange = (e: Event) => {
|
|
478
|
+
const target = e.target as HTMLInputElement | null;
|
|
479
|
+
if (!target || target.type !== 'radio') return;
|
|
480
|
+
onRadioChange(e);
|
|
481
|
+
};
|
|
482
|
+
const handleKeydown = (e: KeyboardEvent) => onRadioGroupKeydown(e);
|
|
483
|
+
group.addEventListener('change', handleChange);
|
|
484
|
+
group.addEventListener('keydown', handleKeydown);
|
|
485
|
+
return () => {
|
|
486
|
+
group.removeEventListener('change', handleChange);
|
|
487
|
+
group.removeEventListener('keydown', handleKeydown);
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
$effect(() => {
|
|
492
|
+
if (!model?.hasAudio || !model?.autoplayAudioEnabled) return;
|
|
408
493
|
if (autoplayAttempted) return;
|
|
409
494
|
|
|
410
495
|
autoplayAttempted = true;
|
|
411
|
-
if (audioEl &&
|
|
496
|
+
if (audioEl && model?.audioUrl) {
|
|
412
497
|
// Browser autoplay restrictions vary. Try immediate play; if blocked show click-to-enable affordance.
|
|
413
498
|
audioEl.play().catch(() => {
|
|
414
499
|
autoPlayPromptOpen = true;
|
|
@@ -422,28 +507,26 @@ $effect(() => {
|
|
|
422
507
|
lang={lang}
|
|
423
508
|
style={rootStyle}
|
|
424
509
|
>
|
|
425
|
-
{#if
|
|
426
|
-
<div class="mb-4 prose pie-prompt" id={promptId}>{@html
|
|
510
|
+
{#if model?.prompt}
|
|
511
|
+
<div class="mb-4 prose pie-prompt" id={promptId}>{@html model.prompt}</div>
|
|
427
512
|
{/if}
|
|
428
513
|
|
|
429
|
-
{#if
|
|
514
|
+
{#if model?.hasAudio}
|
|
430
515
|
<div class="mb-4 audio-container pie-audio-container">
|
|
431
516
|
{#if hasPlayableAudio && useFeatureButtonAudio}
|
|
432
517
|
<audio
|
|
433
518
|
bind:this={audioEl}
|
|
434
519
|
class="sr-only pie-audio-player"
|
|
435
520
|
preload="metadata"
|
|
436
|
-
src={
|
|
521
|
+
src={model.audioUrl}
|
|
437
522
|
aria-hidden="true"
|
|
438
523
|
tabindex="-1"
|
|
439
|
-
on:playing={onAudioPlaying}
|
|
440
|
-
on:ended={onAudioEnded}
|
|
441
524
|
></audio>
|
|
442
525
|
<button
|
|
526
|
+
bind:this={featureAudioButtonEl}
|
|
443
527
|
class="listen-button pie-listen-button rli-feature-audio"
|
|
444
528
|
type="button"
|
|
445
|
-
aria-label={speechButtonLabel(
|
|
446
|
-
on:click={playFeatureAudio}
|
|
529
|
+
aria-label={speechButtonLabel(model?.locale)}
|
|
447
530
|
>
|
|
448
531
|
<img
|
|
449
532
|
class={`listen-feature-icon pie-listen-icon rli-feature-listen ${isMediaPlaying ? '' : 'listen-active'}`}
|
|
@@ -464,18 +547,16 @@ $effect(() => {
|
|
|
464
547
|
controls
|
|
465
548
|
class="w-full max-w-md pie-audio-player"
|
|
466
549
|
preload="metadata"
|
|
467
|
-
src={
|
|
468
|
-
aria-describedby={
|
|
469
|
-
on:playing={onAudioPlaying}
|
|
470
|
-
on:ended={onAudioEnded}
|
|
550
|
+
src={model.audioUrl}
|
|
551
|
+
aria-describedby={model?.audioTranscript ? transcriptId : undefined}
|
|
471
552
|
>
|
|
472
553
|
<track kind="captions" />
|
|
473
554
|
</audio>
|
|
474
555
|
{#if autoPlayPromptOpen}
|
|
475
556
|
<button
|
|
557
|
+
bind:this={autoplayEnableButtonEl}
|
|
476
558
|
class="mt-2 text-sm underline pie-audio-autoplay-enable"
|
|
477
559
|
type="button"
|
|
478
|
-
on:click={handleEnableAutoplayClick}
|
|
479
560
|
>
|
|
480
561
|
{uiText.clickToEnableAutoplay}
|
|
481
562
|
</button>
|
|
@@ -483,20 +564,20 @@ $effect(() => {
|
|
|
483
564
|
{:else if hasAudioButMissingResource}
|
|
484
565
|
<p class="text-sm text-red-700 pie-audio-error" role="alert">{audioErrorMessage}</p>
|
|
485
566
|
{/if}
|
|
486
|
-
{#if
|
|
567
|
+
{#if model?.audioTranscript}
|
|
487
568
|
<p
|
|
488
569
|
class={`text-sm mt-2 text-gray-700 pie-audio-transcript ${showVisibleTranscript ? '' : 'sr-only'}`}
|
|
489
570
|
id={transcriptId}
|
|
490
571
|
>
|
|
491
|
-
<strong>{uiText.transcriptLabel}:</strong> {
|
|
572
|
+
<strong>{uiText.transcriptLabel}:</strong> {model.audioTranscript}
|
|
492
573
|
</p>
|
|
493
574
|
{/if}
|
|
494
575
|
</div>
|
|
495
576
|
{/if}
|
|
496
577
|
|
|
497
|
-
{#if
|
|
578
|
+
{#if model?.sentenceHtml}
|
|
498
579
|
<div class="mb-3 prose prose-p:my-1 sentence-line pie-sentence-line" aria-describedby={templateDescribedBy}>
|
|
499
|
-
{@html
|
|
580
|
+
{@html model.sentenceHtml}
|
|
500
581
|
</div>
|
|
501
582
|
{/if}
|
|
502
583
|
|
|
@@ -530,10 +611,10 @@ $effect(() => {
|
|
|
530
611
|
|
|
531
612
|
{#if isEvaluateMode && isIncorrect}
|
|
532
613
|
<button
|
|
614
|
+
bind:this={toggleCorrectAnswerButtonEl}
|
|
533
615
|
type="button"
|
|
534
616
|
class="mb-3 flex items-center cursor-pointer select-none pie-toggle-correct-answer"
|
|
535
617
|
style="gap:var(--mpb-toggle-button-gap, 0.5rem);"
|
|
536
|
-
on:click={toggleCorrectAnswer}
|
|
537
618
|
aria-pressed={showCorrectAnswer}
|
|
538
619
|
>
|
|
539
620
|
<span class="text-sm hover:underline">
|
|
@@ -545,20 +626,21 @@ $effect(() => {
|
|
|
545
626
|
<p id={resultId} class="sr-only pie-result-feedback" role="status" aria-live="polite">{resultText}</p>
|
|
546
627
|
{/if}
|
|
547
628
|
|
|
548
|
-
<fieldset class="border-0 p-0 m-0 pie-choices-fieldset" disabled={
|
|
629
|
+
<fieldset class="border-0 p-0 m-0 pie-choices-fieldset" disabled={model?.disabled}>
|
|
549
630
|
<legend class="sr-only pie-choices-legend" id={legendId}>{legendText}</legend>
|
|
550
631
|
<div
|
|
632
|
+
bind:this={choicesGroupEl}
|
|
551
633
|
class={`pie-choices ${isHorizontalChoices ? 'flex flex-row flex-wrap items-start justify-center' : 'flex flex-col'}`}
|
|
552
634
|
style="gap:var(--mpb-choice-group-gap, 0.5rem);"
|
|
553
635
|
role="radiogroup"
|
|
554
636
|
tabindex="-1"
|
|
555
637
|
aria-labelledby={choicesGroupLabelledBy}
|
|
638
|
+
aria-label={choicesGroupAriaLabel}
|
|
556
639
|
aria-describedby={resultText ? resultId : undefined}
|
|
557
|
-
on:keydown={onRadioGroupKeydown}
|
|
558
640
|
>
|
|
559
641
|
{#each choices as c (c.id)}
|
|
560
642
|
<div
|
|
561
|
-
class={`flex items-start choice-row pie-choice ${isHorizontalChoices ? 'choice-row-horizontal pie-choice-horizontal' : ''} ${((showCorrectAnswer && isEvaluateMode ?
|
|
643
|
+
class={`flex items-start choice-row pie-choice ${isHorizontalChoices ? 'choice-row-horizontal pie-choice-horizontal' : ''} ${((showCorrectAnswer && isEvaluateMode ? model?.correctChoiceId : selectedId) === c.id) ? 'is-selected pie-choice-selected' : ''}`}
|
|
562
644
|
style="gap:var(--mpb-choice-row-gap, 0.5rem);"
|
|
563
645
|
>
|
|
564
646
|
{#if isHorizontalChoices}
|
|
@@ -584,10 +666,9 @@ $effect(() => {
|
|
|
584
666
|
id={`${instanceId}-opt-${c.id}`}
|
|
585
667
|
value={c.id}
|
|
586
668
|
checked={
|
|
587
|
-
(showCorrectAnswer && isEvaluateMode ?
|
|
669
|
+
(showCorrectAnswer && isEvaluateMode ? model?.correctChoiceId : selectedId) === c.id
|
|
588
670
|
}
|
|
589
|
-
disabled={
|
|
590
|
-
on:change={onRadioChange}
|
|
671
|
+
disabled={model?.disabled}
|
|
591
672
|
class="choice-radio-bottom pie-choice-radio pie-choice-radio-bottom"
|
|
592
673
|
/>
|
|
593
674
|
</label>
|
|
@@ -598,10 +679,9 @@ $effect(() => {
|
|
|
598
679
|
id={`${instanceId}-opt-${c.id}`}
|
|
599
680
|
value={c.id}
|
|
600
681
|
checked={
|
|
601
|
-
(showCorrectAnswer && isEvaluateMode ?
|
|
682
|
+
(showCorrectAnswer && isEvaluateMode ? model?.correctChoiceId : selectedId) === c.id
|
|
602
683
|
}
|
|
603
|
-
disabled={
|
|
604
|
-
on:change={onRadioChange}
|
|
684
|
+
disabled={model?.disabled}
|
|
605
685
|
class="choice-radio-inline pie-choice-radio pie-choice-radio-inline"
|
|
606
686
|
/>
|
|
607
687
|
<label for={`${instanceId}-opt-${c.id}`} class="cursor-pointer flex-1 pie-choice-label-wrap">
|
|
@@ -775,6 +855,9 @@ $effect(() => {
|
|
|
775
855
|
.layout-audio_blank_only .audio-container,
|
|
776
856
|
.layout-stimulus_image_blank .audio-container {
|
|
777
857
|
display: flex;
|
|
858
|
+
width: min(100%, var(--mpb-audio-instructions-max-width, 875px));
|
|
859
|
+
margin-left: auto;
|
|
860
|
+
margin-right: auto;
|
|
778
861
|
justify-content: flex-end;
|
|
779
862
|
}
|
|
780
863
|
|
|
@@ -892,4 +975,36 @@ $effect(() => {
|
|
|
892
975
|
grid-area: choices;
|
|
893
976
|
margin-top: var(--mpb-inline-choices-margin-top, 0.25rem);
|
|
894
977
|
}
|
|
978
|
+
|
|
979
|
+
/* Match Learnosity responsive behavior for CQT audio-blank layouts:
|
|
980
|
+
at smaller widths, audio control shifts left and answer tiles stack. */
|
|
981
|
+
@media (max-width: 760px) {
|
|
982
|
+
.layout-audio_blank_only .audio-container,
|
|
983
|
+
.layout-stimulus_image_blank .audio-container,
|
|
984
|
+
.layout-token_sequence .audio-container,
|
|
985
|
+
.layout-inline_sentence.has-inline-audio .audio-container {
|
|
986
|
+
width: 100%;
|
|
987
|
+
margin-left: 0;
|
|
988
|
+
margin-right: 0;
|
|
989
|
+
justify-content: flex-start;
|
|
990
|
+
justify-self: start;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.layout-audio_blank_only .pie-choices,
|
|
994
|
+
.layout-token_sequence .pie-choices,
|
|
995
|
+
.layout-stimulus_image_blank .pie-choices,
|
|
996
|
+
.layout-inline_sentence.has-inline-audio .pie-choices {
|
|
997
|
+
flex-direction: column;
|
|
998
|
+
align-items: flex-start;
|
|
999
|
+
justify-content: flex-start;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.layout-audio_blank_only .choice-row-horizontal,
|
|
1003
|
+
.layout-token_sequence .choice-row-horizontal,
|
|
1004
|
+
.layout-stimulus_image_blank .choice-row-horizontal,
|
|
1005
|
+
.layout-inline_sentence.has-inline-audio .choice-row-horizontal {
|
|
1006
|
+
width: min(100%, var(--mpb-narrow-choice-max-width, 230px));
|
|
1007
|
+
align-items: flex-start;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
895
1010
|
</style>
|
package/src/index.iife.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IIFE entry for mc-populated-blank delivery
|
|
2
|
+
* IIFE entry for mc-populated-blank delivery.
|
|
3
|
+
* Registration is handled by PIE players/loaders, not by the element bundle itself.
|
|
3
4
|
*/
|
|
4
5
|
import Element from './delivery';
|
|
5
6
|
|
|
6
|
-
if (typeof window !== 'undefined' && !customElements.get('mc-populated-blank-element')) {
|
|
7
|
-
customElements.define(
|
|
8
|
-
'mc-populated-blank-element',
|
|
9
|
-
Element as unknown as CustomElementConstructor
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
export default Element;
|