@pocketprep/ui-kit 3.7.2 → 3.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@pocketprep/ui-kit.css +1 -1
- package/dist/@pocketprep/ui-kit.js +7766 -7705
- package/dist/@pocketprep/ui-kit.js.map +1 -1
- package/dist/@pocketprep/ui-kit.umd.cjs +10 -10
- package/dist/@pocketprep/ui-kit.umd.cjs.map +1 -1
- package/lib/components/Quiz/Question/BuildListChoicesContainer.vue +128 -4
- package/lib/components/Quiz/Question/MPMCChoicesContainer.vue +2 -2
- package/lib/components/Quiz/Question/MPMCRadioGroup.vue +1 -1
- package/lib/components/Quiz/Question/Summary.vue +1 -0
- package/lib/components/Quiz/Question/composables.ts +2 -1
- package/lib/components/Quiz/Question/injectionSymbols.ts +3 -1
- package/lib/components/Quiz/Question.vue +45 -9
- package/lib/components/Quiz/question.d.ts +6 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="uikit-question-build-list-choices-container-root">
|
|
2
|
+
<div class="uikit-question-build-list-choices-container-root" @click="clearFocusState">
|
|
3
3
|
<!--
|
|
4
4
|
Visually hidden aria-live region for choice move announcements:
|
|
5
5
|
"Moved Photosynthesis to position 2 of 6."
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<TransitionGroup
|
|
18
18
|
v-dark="isDarkMode"
|
|
19
19
|
v-breakpoint="breakpointsWithEl"
|
|
20
|
-
name="list"
|
|
20
|
+
:name="shouldDisableTransitions ? '' : 'list'"
|
|
21
21
|
tag="ol"
|
|
22
22
|
class="uikit-question-build-list-choices-container"
|
|
23
23
|
:class="{
|
|
@@ -126,6 +126,9 @@
|
|
|
126
126
|
:disabled="index === 0"
|
|
127
127
|
:tabindex="buttonsAreFocusable && index !== 0 ? 0 : -1"
|
|
128
128
|
@click.prevent.stop="moveChoiceUp(index)"
|
|
129
|
+
@keydown.enter.prevent.stop="moveChoiceUp(index)"
|
|
130
|
+
@focus="setFocusedArrowButton(choice.key, 'up')"
|
|
131
|
+
@blur="clearFocusState"
|
|
129
132
|
:aria-label="`Move '${stripHtmlTags(choice.text || '')}' up`"
|
|
130
133
|
:title="'Move up'"
|
|
131
134
|
>
|
|
@@ -146,6 +149,9 @@
|
|
|
146
149
|
:disabled="index === orderedChoices.length - 1"
|
|
147
150
|
:tabindex="buttonsAreFocusable && index !== orderedChoices.length - 1 ? 0 : -1"
|
|
148
151
|
@click.prevent.stop="moveChoiceDown(index)"
|
|
152
|
+
@keydown.enter.prevent.stop="moveChoiceDown(index)"
|
|
153
|
+
@focus="setFocusedArrowButton(choice.key, 'down')"
|
|
154
|
+
@blur="clearFocusState"
|
|
149
155
|
:aria-label="`Move '${stripHtmlTags(choice.text || '')}' down`"
|
|
150
156
|
:title="'Move down'"
|
|
151
157
|
>
|
|
@@ -195,11 +201,17 @@ const floatingChoiceKey = ref<string | null>(null)
|
|
|
195
201
|
const choiceRefs = ref<Map<string, HTMLDivElement>>(new Map())
|
|
196
202
|
const announcementMessage = ref<string>('')
|
|
197
203
|
const isMoveTriggeredByArrowKey = ref<boolean>(false)
|
|
204
|
+
const focusedArrowButton = ref<{ choiceKey: string; direction: 'up' | 'down' } | null>(null)
|
|
205
|
+
const isMoveInProgress = ref<boolean>(false)
|
|
198
206
|
|
|
199
207
|
const buttonsAreFocusable = computed(() => {
|
|
200
208
|
return !showBuildListOrder.value && !reviewMode.value
|
|
201
209
|
})
|
|
202
210
|
|
|
211
|
+
const shouldDisableTransitions = computed(() => {
|
|
212
|
+
return reviewMode.value || showBuildListOrder.value
|
|
213
|
+
})
|
|
214
|
+
|
|
203
215
|
onMounted(() => {
|
|
204
216
|
updateOrderedChoices()
|
|
205
217
|
})
|
|
@@ -232,12 +244,56 @@ const setCardRef = (el: Element | ComponentPublicInstance | null, choiceKey: str
|
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
246
|
|
|
247
|
+
const clearFocusState = () => {
|
|
248
|
+
// Don't clear focus state if we're in the middle of animation
|
|
249
|
+
// This prevents clearing focus when the button temporarily loses focus during animation
|
|
250
|
+
if (!isMoveInProgress.value) {
|
|
251
|
+
focusedArrowButton.value = null
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const setFocusedArrowButton = (choiceKey: string, direction: 'up' | 'down') => {
|
|
256
|
+
focusedArrowButton.value = { choiceKey, direction }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const restoreFocusToArrowButton = (choiceKey: string, direction: 'up' | 'down') => {
|
|
260
|
+
const choiceEl = choiceRefs.value.get(choiceKey)
|
|
261
|
+
if (choiceEl) {
|
|
262
|
+
const buttonSelector = direction === 'up'
|
|
263
|
+
? '.uikit-question-build-list-choices-container__choice-up-button'
|
|
264
|
+
: '.uikit-question-build-list-choices-container__choice-down-button'
|
|
265
|
+
const button = choiceEl.querySelector(buttonSelector) as HTMLButtonElement
|
|
266
|
+
if (button && !button.disabled) {
|
|
267
|
+
button.focus()
|
|
268
|
+
setFocusedArrowButton(choiceKey, direction)
|
|
269
|
+
} else {
|
|
270
|
+
// Move focus to opposite button if focused button becomes disabled
|
|
271
|
+
const oppositeDirection = direction === 'up' ? 'down' : 'up'
|
|
272
|
+
const oppositeButtonSelector = oppositeDirection === 'up'
|
|
273
|
+
? '.uikit-question-build-list-choices-container__choice-up-button'
|
|
274
|
+
: '.uikit-question-build-list-choices-container__choice-down-button'
|
|
275
|
+
const oppositeButton = choiceEl.querySelector(oppositeButtonSelector) as HTMLButtonElement
|
|
276
|
+
if (oppositeButton && !oppositeButton.disabled) {
|
|
277
|
+
oppositeButton.focus()
|
|
278
|
+
setFocusedArrowButton(choiceKey, oppositeDirection)
|
|
279
|
+
} else {
|
|
280
|
+
clearFocusState()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
235
286
|
const restoreFocusAfterMove = (movedChoiceKey: string) => {
|
|
236
287
|
// Don't restore focus if we're in review mode or showing the build list order
|
|
237
288
|
if (showBuildListOrder.value || reviewMode.value) {
|
|
238
289
|
return
|
|
239
290
|
}
|
|
240
291
|
|
|
292
|
+
// Don't restore focus to choice element if there's a focused arrow button
|
|
293
|
+
if (focusedArrowButton.value && focusedArrowButton.value.choiceKey === movedChoiceKey) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
241
297
|
const delay = prefersReducedMotion() ? 0 : 50
|
|
242
298
|
setTimeout(() => {
|
|
243
299
|
const choiceEl = choiceRefs.value.get(movedChoiceKey)
|
|
@@ -262,7 +318,8 @@ const removeFocusAfterAnimation = (movedChoiceKey: string) => {
|
|
|
262
318
|
}
|
|
263
319
|
|
|
264
320
|
// Only remove focus if the move was NOT triggered by arrow keys
|
|
265
|
-
|
|
321
|
+
// AND there's no focused arrow button that should maintain focus
|
|
322
|
+
if (!isMoveTriggeredByArrowKey.value && !focusedArrowButton.value) {
|
|
266
323
|
const animationDelay = prefersReducedMotion() ? 0 : 350 // Slightly after animation completes
|
|
267
324
|
setTimeout(() => {
|
|
268
325
|
const choiceEl = choiceRefs.value.get(movedChoiceKey)
|
|
@@ -293,6 +350,9 @@ const moveChoiceUp = (index: number) => {
|
|
|
293
350
|
const previousItem = newChoices[ index - 1 ]
|
|
294
351
|
|
|
295
352
|
if (currentItem && previousItem) {
|
|
353
|
+
// Set move in progress flag
|
|
354
|
+
isMoveInProgress.value = true
|
|
355
|
+
|
|
296
356
|
// Set floating state for the clicked choice
|
|
297
357
|
floatingChoiceKey.value = currentItem.key
|
|
298
358
|
|
|
@@ -309,6 +369,25 @@ const moveChoiceUp = (index: number) => {
|
|
|
309
369
|
// Remove focus after animation if not triggered by arrow key
|
|
310
370
|
removeFocusAfterAnimation(currentItem.key)
|
|
311
371
|
|
|
372
|
+
// Restore focus to arrow button if it was previously focused
|
|
373
|
+
if (focusedArrowButton.value && focusedArrowButton.value.choiceKey === currentItem.key) {
|
|
374
|
+
const delay = prefersReducedMotion() ? 0 : 400 // After animation completes
|
|
375
|
+
setTimeout(() => {
|
|
376
|
+
const focusedButton = focusedArrowButton.value
|
|
377
|
+
if (focusedButton) {
|
|
378
|
+
restoreFocusToArrowButton(currentItem.key, focusedButton.direction)
|
|
379
|
+
}
|
|
380
|
+
// Clear move in progress flag after focus restoration
|
|
381
|
+
isMoveInProgress.value = false
|
|
382
|
+
}, delay)
|
|
383
|
+
} else {
|
|
384
|
+
// Clear move in progress flag if no focus restoration needed
|
|
385
|
+
const delay = prefersReducedMotion() ? 0 : 400
|
|
386
|
+
setTimeout(() => {
|
|
387
|
+
isMoveInProgress.value = false
|
|
388
|
+
}, delay)
|
|
389
|
+
}
|
|
390
|
+
|
|
312
391
|
// Clear floating state after animation completes
|
|
313
392
|
const animationDelay = prefersReducedMotion() ? 0 : 300
|
|
314
393
|
setTimeout(() => {
|
|
@@ -325,6 +404,9 @@ const moveChoiceDown = (index: number) => {
|
|
|
325
404
|
const nextItem = newChoices[ index + 1 ]
|
|
326
405
|
|
|
327
406
|
if (currentItem && nextItem) {
|
|
407
|
+
// Set move in progress flag
|
|
408
|
+
isMoveInProgress.value = true
|
|
409
|
+
|
|
328
410
|
// Set floating state for the clicked choice
|
|
329
411
|
floatingChoiceKey.value = currentItem.key
|
|
330
412
|
|
|
@@ -342,6 +424,25 @@ const moveChoiceDown = (index: number) => {
|
|
|
342
424
|
// Remove focus after animation
|
|
343
425
|
removeFocusAfterAnimation(currentItem.key)
|
|
344
426
|
|
|
427
|
+
// Restore focus to arrow button if it was previously focused
|
|
428
|
+
if (focusedArrowButton.value && focusedArrowButton.value.choiceKey === currentItem.key) {
|
|
429
|
+
const delay = prefersReducedMotion() ? 0 : 400 // After animation completes
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
const focusedButton = focusedArrowButton.value
|
|
432
|
+
if (focusedButton) {
|
|
433
|
+
restoreFocusToArrowButton(currentItem.key, focusedButton.direction)
|
|
434
|
+
}
|
|
435
|
+
// Clear move in progress flag after focus restoration
|
|
436
|
+
isMoveInProgress.value = false
|
|
437
|
+
}, delay)
|
|
438
|
+
} else {
|
|
439
|
+
// Clear move in progress flag if no focus restoration needed
|
|
440
|
+
const delay = prefersReducedMotion() ? 0 : 400
|
|
441
|
+
setTimeout(() => {
|
|
442
|
+
isMoveInProgress.value = false
|
|
443
|
+
}, delay)
|
|
444
|
+
}
|
|
445
|
+
|
|
345
446
|
// Clear floating state after animation completes
|
|
346
447
|
const animationDelay = prefersReducedMotion() ? 0 : 300
|
|
347
448
|
setTimeout(() => {
|
|
@@ -438,7 +539,7 @@ watch(selectedBuildListChoiceOrder, () => {
|
|
|
438
539
|
top: -8px;
|
|
439
540
|
bottom: -8px;
|
|
440
541
|
left: 8px;
|
|
441
|
-
right:
|
|
542
|
+
right: 14px;
|
|
442
543
|
border-radius: 11px;
|
|
443
544
|
pointer-events: none;
|
|
444
545
|
|
|
@@ -523,6 +624,29 @@ watch(selectedBuildListChoiceOrder, () => {
|
|
|
523
624
|
}
|
|
524
625
|
}
|
|
525
626
|
|
|
627
|
+
// Disable transitions when in review mode or showing build list order
|
|
628
|
+
&--review-mode,
|
|
629
|
+
&--correct,
|
|
630
|
+
&--incorrect,
|
|
631
|
+
&--teach-group-review {
|
|
632
|
+
.uikit-question-build-list-choices-container__choice-container {
|
|
633
|
+
transition: none;
|
|
634
|
+
|
|
635
|
+
&--floating {
|
|
636
|
+
transform: none;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.uikit-question-build-list-choices-container__choice {
|
|
641
|
+
transition: none;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.uikit-question-build-list-choices-container__choice-up-button,
|
|
645
|
+
.uikit-question-build-list-choices-container__choice-down-button {
|
|
646
|
+
transition: none;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
526
650
|
&__choice-number,
|
|
527
651
|
&__mobile-choice-number {
|
|
528
652
|
display: flex;
|
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
isDarkMode,
|
|
28
28
|
showMPMCAnswers,
|
|
29
29
|
selectedMPMCChoices,
|
|
30
|
+
mpmcChoices,
|
|
30
31
|
} = useQuestionContext()
|
|
31
32
|
|
|
32
33
|
const mpmcRadioGrid = ref<IMPMCRadioOptions[] | undefined>(undefined)
|
|
@@ -37,8 +38,7 @@ const mpmcLabels = computed(() => {
|
|
|
37
38
|
})
|
|
38
39
|
|
|
39
40
|
const questionChoices = computed(() => {
|
|
40
|
-
|
|
41
|
-
return choices
|
|
41
|
+
return mpmcChoices.value
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
const choiceKeysByLabelIndexObj = computed(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inject, ref } from 'vue'
|
|
2
2
|
import * as InjectionKeys from './injectionSymbols'
|
|
3
|
-
import type { TChoice, TChoiceKey, TMatrixChoiceKey, TBuildListChoice } from '../question'
|
|
3
|
+
import type { TChoice, TChoiceKey, TMatrixChoiceKey, TBuildListChoice, TMPMCChoice } from '../question'
|
|
4
4
|
|
|
5
5
|
export const useQuestionContext = () => {
|
|
6
6
|
const question = inject(InjectionKeys.questionKey)
|
|
@@ -16,6 +16,7 @@ export const useQuestionContext = () => {
|
|
|
16
16
|
choiceScores,
|
|
17
17
|
choices: inject(InjectionKeys.choicesKey, ref<TChoice[]>([])),
|
|
18
18
|
buildListChoices: inject(InjectionKeys.buildListChoicesKey, ref<TBuildListChoice[]>([])),
|
|
19
|
+
mpmcChoices: inject(InjectionKeys.mpmcChoicesKey, ref<TMPMCChoice[]>([])),
|
|
19
20
|
questionEl: inject(InjectionKeys.questionElKey, ref(null)),
|
|
20
21
|
breakpointsWithEl: inject(InjectionKeys.breakpointsWithElKey, ref({
|
|
21
22
|
breakpoints: {
|
|
@@ -11,12 +11,14 @@ import type {
|
|
|
11
11
|
TMatrixChoiceScores,
|
|
12
12
|
TBuildListChoiceScores,
|
|
13
13
|
TQuizMode,
|
|
14
|
+
TMPMCChoice,
|
|
14
15
|
} from '../question'
|
|
15
16
|
import type { ComputedRef, InjectionKey, Ref } from 'vue'
|
|
16
17
|
|
|
17
18
|
export const questionKey = Symbol('question') as InjectionKey<ComputedRef<Study.Class.QuestionJSON>>
|
|
18
19
|
export const choicesKey = Symbol('choices') as InjectionKey<ComputedRef<TChoice[]>>
|
|
19
|
-
export const buildListChoicesKey = Symbol('
|
|
20
|
+
export const buildListChoicesKey = Symbol('buildListChoices') as InjectionKey<ComputedRef<TBuildListChoice[]>>
|
|
21
|
+
export const mpmcChoicesKey = Symbol('mpmcChoices') as InjectionKey<ComputedRef<TMPMCChoice[]>>
|
|
20
22
|
export const questionElKey = Symbol('questionEl') as InjectionKey<Ref<Element | null>>
|
|
21
23
|
export const breakpointsWithElKey = Symbol('breakpointsWithEl') as InjectionKey<Ref<{
|
|
22
24
|
breakpoints: {
|
|
@@ -93,7 +93,11 @@
|
|
|
93
93
|
v-if="question.passage || passageImageUrl"
|
|
94
94
|
ref="uikit-question__passage-and-image-dropdown"
|
|
95
95
|
class="uikit-question__passage-and-image-dropdown"
|
|
96
|
-
:class="{
|
|
96
|
+
:class="{
|
|
97
|
+
'uikit-question__passage-and-image-dropdown--review-mode': reviewMode,
|
|
98
|
+
'uikit-question__passage-and-image-dropdown--build-list': isBuildListQuestion,
|
|
99
|
+
|
|
100
|
+
}"
|
|
97
101
|
@togglePassageImageLongAltDropdown="togglePassageImageLongAlt"
|
|
98
102
|
/>
|
|
99
103
|
<Paywall
|
|
@@ -470,6 +474,7 @@ import type {
|
|
|
470
474
|
TBuildListChoiceKey,
|
|
471
475
|
TChoice,
|
|
472
476
|
TBuildListChoice,
|
|
477
|
+
TMPMCChoice,
|
|
473
478
|
TChoiceScores,
|
|
474
479
|
TMatrixChoiceScores,
|
|
475
480
|
TBuildListChoiceScores,
|
|
@@ -800,6 +805,10 @@ const buildListChoices = computed(() => {
|
|
|
800
805
|
return shuffledChoices
|
|
801
806
|
})
|
|
802
807
|
|
|
808
|
+
const mpmcChoices = computed(() => {
|
|
809
|
+
return shuffleMPMCChoices([ ...props.question.choices ])
|
|
810
|
+
})
|
|
811
|
+
|
|
803
812
|
const isCorrect = computed(() => {
|
|
804
813
|
// In order to be correct, user must have selected all the answers and none of the distractors
|
|
805
814
|
return showAnswers.value
|
|
@@ -946,11 +955,11 @@ const isMatrixQuestionAnswered = computed(() => {
|
|
|
946
955
|
|
|
947
956
|
const isMPMCQuestionAnswered = computed(() => {
|
|
948
957
|
const mpmcLabels = question.value.mpmcLabels
|
|
949
|
-
const
|
|
958
|
+
const mpmcQuestionChoices = question.value.choices
|
|
950
959
|
const selectedMPMCLabelIndexes: number[] = []
|
|
951
960
|
|
|
952
961
|
selectedMPMCChoices.value.forEach(choice => {
|
|
953
|
-
const mpmcChoiceObj =
|
|
962
|
+
const mpmcChoiceObj = mpmcQuestionChoices.find(c => c.id === choice)
|
|
954
963
|
if (
|
|
955
964
|
mpmcChoiceObj?.labelIndex !== undefined
|
|
956
965
|
&& !selectedMPMCLabelIndexes.includes(mpmcChoiceObj?.labelIndex)
|
|
@@ -1044,7 +1053,8 @@ const keydownListener = (e: KeyboardEvent) => {
|
|
|
1044
1053
|
}
|
|
1045
1054
|
break
|
|
1046
1055
|
case 'KeyX':
|
|
1047
|
-
showAnswers.value
|
|
1056
|
+
(showAnswers.value || showMatrixAnswers.value || showBuildListOrder.value || showMPMCAnswers.value)
|
|
1057
|
+
&& toggleExplanation()
|
|
1048
1058
|
break
|
|
1049
1059
|
case 'Escape':
|
|
1050
1060
|
emitClose()
|
|
@@ -1191,6 +1201,30 @@ const shuffleBuildListChoices = (choicesToShuffle: TBuildListChoice[]): TBuildLi
|
|
|
1191
1201
|
: sortedChoices
|
|
1192
1202
|
}
|
|
1193
1203
|
|
|
1204
|
+
const shuffleMPMCChoices = (choicesToShuffle: TMPMCChoice[]): TMPMCChoice[]=> {
|
|
1205
|
+
const sortedChoices = choicesToShuffle.sort((a, b) => {
|
|
1206
|
+
const hashChar = (char: string, num: number) => ((num << 5) - num) + char.charCodeAt(0)
|
|
1207
|
+
|
|
1208
|
+
const aHash = a.text?.split('')
|
|
1209
|
+
.reduce((acc: number, char: string) => hashChar(char, acc) & hashChar(char, acc), 0)
|
|
1210
|
+
const bHash = b.text?.split('')
|
|
1211
|
+
.reduce((acc: number, char: string) => hashChar(char, acc) & hashChar(char, acc), 0)
|
|
1212
|
+
|
|
1213
|
+
return (aHash || 0) - (bHash || 0)
|
|
1214
|
+
})
|
|
1215
|
+
|
|
1216
|
+
return props.answerSeed
|
|
1217
|
+
? props.answerSeed.reduce<TMPMCChoice[]>((acc, i) => {
|
|
1218
|
+
const sortedChoice = sortedChoices[i]
|
|
1219
|
+
if (sortedChoice) {
|
|
1220
|
+
acc.push(sortedChoice)
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return acc
|
|
1224
|
+
}, [])
|
|
1225
|
+
: sortedChoices
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1194
1228
|
const choiceFocusOut = (event: FocusEvent) => {
|
|
1195
1229
|
const relatedTarget = event.relatedTarget
|
|
1196
1230
|
if (
|
|
@@ -1454,7 +1488,6 @@ const clickCheckMatrixAnswer = () => {
|
|
|
1454
1488
|
const clickCheckMPMCAnswer = () => {
|
|
1455
1489
|
if (!props.hideAnswer) {
|
|
1456
1490
|
showMPMCAnswers.value = true
|
|
1457
|
-
|
|
1458
1491
|
emitCheckAnswer({
|
|
1459
1492
|
isCorrect: isMPMCQuestionCorrect.value,
|
|
1460
1493
|
selectedChoices: selectedMPMCChoices.value,
|
|
@@ -1624,10 +1657,7 @@ onMounted(() => {
|
|
|
1624
1657
|
showBuildListOrder.value = props.initialShowAnswers
|
|
1625
1658
|
}
|
|
1626
1659
|
|
|
1627
|
-
if (
|
|
1628
|
-
props.allowKeyboardShortcuts
|
|
1629
|
-
&& !isMPMCQuestion.value
|
|
1630
|
-
) {
|
|
1660
|
+
if (props.allowKeyboardShortcuts) {
|
|
1631
1661
|
window.addEventListener('keydown', keydownListener)
|
|
1632
1662
|
}
|
|
1633
1663
|
|
|
@@ -1652,6 +1682,7 @@ onBeforeUnmount(() => {
|
|
|
1652
1682
|
provide(InjectionKeys.questionKey, question)
|
|
1653
1683
|
provide(InjectionKeys.choicesKey, choices)
|
|
1654
1684
|
provide(InjectionKeys.buildListChoicesKey, buildListChoices)
|
|
1685
|
+
provide(InjectionKeys.mpmcChoicesKey, mpmcChoices)
|
|
1655
1686
|
provide(InjectionKeys.questionElKey, questionEl)
|
|
1656
1687
|
provide(InjectionKeys.breakpointsWithElKey, breakpointsWithEl)
|
|
1657
1688
|
provide(InjectionKeys.quizLengthKey, quizLength)
|
|
@@ -2048,6 +2079,11 @@ provide(InjectionKeys.isTeachGroupReviewKey, isTeachGroupReview)
|
|
|
2048
2079
|
&--review-mode {
|
|
2049
2080
|
display: block;
|
|
2050
2081
|
}
|
|
2082
|
+
|
|
2083
|
+
&--build-list:not(#{&}--review-mode):not(#{&}--mobile) {
|
|
2084
|
+
max-width: 452px;
|
|
2085
|
+
margin-left: -8px;
|
|
2086
|
+
}
|
|
2051
2087
|
}
|
|
2052
2088
|
|
|
2053
2089
|
&__unanswered-teach-review-label {
|
|
@@ -17,6 +17,12 @@ export type TBreakPointsObject = {
|
|
|
17
17
|
|
|
18
18
|
export type TChoice = { text?: string; key: TChoiceKey }
|
|
19
19
|
export type TBuildListChoice = { text?: string; key: TBuildListChoiceKey }
|
|
20
|
+
export type TMPMCChoice = {
|
|
21
|
+
text?: string
|
|
22
|
+
id?: string
|
|
23
|
+
isCorrect?: boolean
|
|
24
|
+
labelIndex?: number
|
|
25
|
+
}
|
|
20
26
|
|
|
21
27
|
export type TChoiceScores = Partial<Record<TChoiceKey, number>> & {
|
|
22
28
|
totalAnswered: number
|