@pocketprep/ui-kit 3.4.90 → 3.5.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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/@pocketprep/ui-kit.css +1 -0
  3. package/dist/@pocketprep/ui-kit.js +16490 -18228
  4. package/dist/@pocketprep/ui-kit.js.map +1 -1
  5. package/dist/@pocketprep/ui-kit.umd.cjs +11 -11
  6. package/dist/@pocketprep/ui-kit.umd.cjs.map +1 -1
  7. package/eslint.config.ts +111 -0
  8. package/lib/components/Banners/Banner.vue +2 -2
  9. package/lib/components/Bundles/BundleList.vue +1 -1
  10. package/lib/components/Bundles/BundleSearch.vue +43 -12
  11. package/lib/components/Bundles/PremiumPill.vue +2 -2
  12. package/lib/components/Buttons/Button.vue +19 -18
  13. package/lib/components/Buttons/Link.vue +9 -8
  14. package/lib/components/Buttons/Tab.vue +4 -3
  15. package/lib/components/Calendar/Calendar.vue +14 -2
  16. package/lib/components/Charts/Bar.vue +3 -3
  17. package/lib/components/Charts/Pie.vue +4 -4
  18. package/lib/components/Controls/SegmentControl.vue +8 -7
  19. package/lib/components/Controls/Slider.vue +2 -3
  20. package/lib/components/Controls/ToggleSwitch.vue +3 -2
  21. package/lib/components/EmptyStates/EmptyState.vue +3 -2
  22. package/lib/components/Exams/ExamCard.vue +3 -3
  23. package/lib/components/Exams/ExamMenuCard.vue +2 -2
  24. package/lib/components/Filters/FilterDropdown.vue +2 -2
  25. package/lib/components/Filters/FilterOptions.vue +2 -2
  26. package/lib/components/Forms/Checkbox.vue +2 -2
  27. package/lib/components/Forms/CheckboxOption.vue +2 -2
  28. package/lib/components/Forms/Errors.vue +2 -2
  29. package/lib/components/Forms/Input.vue +2 -2
  30. package/lib/components/Forms/Radio.vue +37 -39
  31. package/lib/components/Forms/RadioButton.vue +1 -1
  32. package/lib/components/Forms/Select.vue +7 -6
  33. package/lib/components/Forms/Textarea.vue +2 -2
  34. package/lib/components/Icons/Icon.vue +1 -0
  35. package/lib/components/Icons/IconEdit.vue +4 -2
  36. package/lib/components/Icons/IconFullViewActive.vue +1 -1
  37. package/lib/components/Icons/IconLoading2.vue +1 -3
  38. package/lib/components/Loaders/SkeletonLoader.vue +2 -2
  39. package/lib/components/Messaging/InfoMessage.vue +2 -2
  40. package/lib/components/Modal/Modal.vue +2 -2
  41. package/lib/components/Modal/ModalContainer.vue +2 -2
  42. package/lib/components/Onboarding/EmailAuth.vue +5 -5
  43. package/lib/components/Onboarding/MagicCodeEntry.vue +3 -4
  44. package/lib/components/Pagination/QuestionReviewPagination.vue +23 -21
  45. package/lib/components/Pagination/TablePagination.vue +2 -2
  46. package/lib/components/Quiz/FlagToggle.vue +2 -2
  47. package/lib/components/Quiz/GlobalMetricsToggle.vue +3 -2
  48. package/lib/components/Quiz/KeyboardShortcutsButton.vue +1 -1
  49. package/lib/components/Quiz/KeyboardShortcutsModal.vue +1 -1
  50. package/lib/components/Quiz/Question/ChoicesContainer.vue +99 -132
  51. package/lib/components/Quiz/Question/DropdownExplanation.vue +41 -55
  52. package/lib/components/Quiz/Question/Explanation.vue +49 -59
  53. package/lib/components/Quiz/Question/MatrixChoicesContainer.vue +208 -226
  54. package/lib/components/Quiz/Question/MatrixRadioGroup.vue +7 -6
  55. package/lib/components/Quiz/Question/MobileMatrixChoicesContainer.vue +315 -320
  56. package/lib/components/Quiz/Question/MobileMatrixRadioGroup.vue +14 -11
  57. package/lib/components/Quiz/Question/PassageAndImage.vue +34 -45
  58. package/lib/components/Quiz/Question/PassageAndImageDropdown.vue +39 -49
  59. package/lib/components/Quiz/Question/Paywall.vue +30 -41
  60. package/lib/components/Quiz/Question/QuestionContext.vue +24 -33
  61. package/lib/components/Quiz/Question/StatsSummary.vue +12 -22
  62. package/lib/components/Quiz/Question/Summary.vue +56 -66
  63. package/lib/components/Quiz/Question/composables.ts +71 -0
  64. package/lib/components/Quiz/Question/injectionSymbols.ts +69 -0
  65. package/lib/components/Quiz/Question.vue +810 -1009
  66. package/lib/components/Quiz/QuizContainer.vue +63 -67
  67. package/lib/components/Quiz/QuizProgress.vue +73 -77
  68. package/lib/components/Quiz/QuizProgressBar.vue +3 -2
  69. package/lib/components/Quiz/question.d.ts +4 -4
  70. package/lib/components/Search/Pill.vue +2 -2
  71. package/lib/components/Search/Search.vue +2 -2
  72. package/lib/components/SidePanels/SidePanel.vue +8 -3
  73. package/lib/components/Tables/Table.vue +4 -3
  74. package/lib/components/Tables/TableActions.vue +3 -3
  75. package/lib/components/Tags/Tag.vue +2 -2
  76. package/lib/components/Toasts/Toast.vue +5 -3
  77. package/lib/components/Tooltips/OverflowTooltip.vue +2 -2
  78. package/lib/components/Tooltips/Tooltip.vue +2 -2
  79. package/lib/directives.ts +28 -23
  80. package/lib/pocketprep-export.module.scss +3 -2
  81. package/lib/pocketprep.scss +2 -2
  82. package/lib/styles/_breakpoints.scss +12 -24
  83. package/lib/styles/_colors.scss +0 -1
  84. package/package.json +38 -29
  85. package/stylelint.config.js +38 -0
  86. package/.eslintrc.cjs +0 -74
  87. package/dist/style.css +0 -1
  88. package/stylelint.config.cjs +0 -22
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
- ref="question"
4
- v-breakpoint="breakpoints"
3
+ ref="uikit-question"
4
+ v-breakpoint="breakpointsWithEl"
5
5
  class="uikit-question"
6
6
  :class="{
7
7
  'uikit-question--show-side': (showExplanation && !showPaywall) || showPassageAndImage,
@@ -9,7 +9,7 @@
9
9
  >
10
10
  <slot name="closeIcon">
11
11
  <Icon
12
- v-breakpoint:questionEl="breakpoints"
12
+ v-breakpoint:questionEl="breakpointsWithEl"
13
13
  v-dark="isDarkMode"
14
14
  class="uikit-question__close-icon"
15
15
  role="button"
@@ -20,12 +20,12 @@
20
20
  </slot>
21
21
  <div
22
22
  class="uikit-question__tag-mobile"
23
- v-breakpoint:questionEl="breakpoints"
23
+ v-breakpoint="breakpointsWithEl"
24
24
  >
25
25
  <slot name="tag" />
26
26
  </div>
27
27
  <div
28
- v-breakpoint:questionEl="breakpoints"
28
+ v-breakpoint="breakpointsWithEl"
29
29
  class="uikit-question__main"
30
30
  :class="{
31
31
  'uikit-question__main--show-side': (showExplanation && !showPaywall) || showPassageAndImage,
@@ -43,19 +43,9 @@
43
43
  <slot name="context">
44
44
  <QuestionContext
45
45
  ref="uikit-question__context"
46
- v-breakpoint:questionEl="breakpoints"
46
+ v-breakpoint="breakpointsWithEl"
47
47
  class="uikit-question__context"
48
48
  tabindex="-1"
49
- :quiz-length="quizLength"
50
- :quiz-mode="quizMode"
51
- :question-number="questionNumber"
52
- :is-correct="isCorrect"
53
- :is-dark-mode="isDarkMode"
54
- :context-icon-type="contextIconType"
55
- :show-answers="showAnswers"
56
- :show-matrix-answers="showMatrixAnswers"
57
- :question-el="questionEl"
58
- :breakpoints="breakpoints"
59
49
  >
60
50
  <template #contextIcon>
61
51
  <slot name="contextIcon" />
@@ -68,7 +58,7 @@
68
58
  <div
69
59
  v-if="questionScenario && currentScenarioQuestionNumber && numberOfScenarioQuestions"
70
60
  v-dark="isDarkMode"
71
- v-breakpoint:questionEl="breakpoints"
61
+ v-breakpoint="breakpointsWithEl"
72
62
  tabindex="-1"
73
63
  class="uikit-question__scenario-part-x-of-y-label"
74
64
  :aria-label="`Scenario Part ${currentScenarioQuestionNumber} of ${numberOfScenarioQuestions}`"
@@ -78,8 +68,8 @@
78
68
  </div>
79
69
  <slot name="promptExperiment" />
80
70
  <div
81
- ref="prompt"
82
- v-breakpoint:questionEl="breakpoints"
71
+ ref="promptRef"
72
+ v-breakpoint="breakpointsWithEl"
83
73
  tabindex="-1"
84
74
  role="group"
85
75
  class="uikit-question__prompt"
@@ -91,7 +81,7 @@
91
81
  />
92
82
  <PocketButton
93
83
  v-if="question.passage || passageImageUrl"
94
- v-breakpoint:questionEl="breakpoints"
84
+ v-breakpoint="breakpointsWithEl"
95
85
  class="uikit-question__skip-to-passage"
96
86
  @click="moveFocusToPassage"
97
87
  >
@@ -102,26 +92,11 @@
102
92
  ref="uikit-question__passage-and-image-dropdown"
103
93
  class="uikit-question__passage-and-image-dropdown"
104
94
  :class="{ 'uikit-question__passage-and-image-dropdown--review-mode': reviewMode }"
105
- :question="question"
106
- :question-el="questionEl"
107
- :breakpoints="breakpoints"
108
- :is-dark-mode="isDarkMode"
109
- :review-mode="reviewMode"
110
- :image-url-prefix="imageUrlPrefix"
111
- :show-passage-image-long-alt="showPassageImageLongAlt"
112
- :passage-image-url="passageImageUrl"
113
- :passage-image-long-alt="passageImageLongAlt"
114
- :passage-image-alt="passageImageAlt"
115
- :passage-and-image-title="passageAndImageTitle"
116
95
  @togglePassageImageLongAltDropdown="togglePassageImageLongAlt"
117
96
  />
118
97
  <Paywall
119
98
  v-if="showPaywall"
120
99
  class="uikit-question__paywall"
121
- :review-Mode="reviewMode"
122
- :is-dark-mode="isDarkMode"
123
- :question-el="questionEl"
124
- :breakpoints="breakpoints"
125
100
  @upgradeClicked="emitUpgrade"
126
101
  />
127
102
  <div
@@ -175,33 +150,6 @@
175
150
  <ChoicesContainer
176
151
  v-if="question.type !== 'Matrix Checkbox' && question.type !== 'Matrix Radio Button'"
177
152
  ref="uikit-question__choices-container"
178
- :question="question"
179
- :choices="choices"
180
- :show-answers="showAnswers"
181
- :show-explanation="showExplanation"
182
- :is-MCR="isMCR"
183
- :is-unanswered="isUnanswered"
184
- :answer-keys="answerKeys"
185
- :hover-choice-key="hoverChoiceKey"
186
- :focus-choice-key="focusChoiceKey"
187
- :selected-choices="selectedChoices"
188
- :distractor-keys="distractorKeys"
189
- :choice-strikes="choiceStrikes"
190
- :choice-scores="choiceScores"
191
- :passage-image-url="passageImageUrl"
192
- :is-correct="isCorrect"
193
- :global-metrics="globalMetrics"
194
- :reviewMode="reviewMode"
195
- :show-explanation-image-long-alt="showExplanationImageLongAlt"
196
- :explanation-image-url="explanationImageUrl"
197
- :explanation-image-alt="explanationImageAlt"
198
- :explanation-image-long-alt="explanationImageLongAlt"
199
- :reference="reference"
200
- :hide-references="hideReferences"
201
- :is-dark-mode="isDarkMode"
202
- :question-el="questionEl"
203
- :breakpoints="breakpoints"
204
- :keyword-definitions="keywordDefinitions"
205
153
  @emitChoiceMouseOver="choiceMouseOver"
206
154
  @emitChoiceMouseLeave="choiceMouseLeave"
207
155
  @emitChoiceFocusIn="choiceFocusIn"
@@ -219,22 +167,22 @@
219
167
  <slot name="explanationTopExperiment"></slot>
220
168
  </template>
221
169
  <template #motivationalMoment="{
222
- isCorrect,
170
+ isCorrect:isCorrectMM,
223
171
  choiceKey,
224
- showAnswers,
225
- answerKeys,
172
+ showAnswers:showAnswersMM,
173
+ answerKeys:answerKeysMM,
226
174
  }">
227
175
  <slot
228
176
  name="motivationalMoment"
229
- :showAnswers="showAnswers"
230
- :answerKeys="answerKeys"
177
+ :showAnswers="showAnswersMM"
178
+ :answerKeys="answerKeysMM"
231
179
  :choiceKey="choiceKey"
232
- :isCorrect="isCorrect"
180
+ :isCorrect="isCorrectMM"
233
181
  />
234
182
  </template>
235
183
  <template #showNamesTable="{
236
184
  choiceKey,
237
- }" >
185
+ }">
238
186
  <slot
239
187
  name="showNamesTable"
240
188
  :choiceKey="choiceKey"
@@ -248,35 +196,21 @@
248
196
  v-if="question.type === 'Matrix Checkbox' || question.type === 'Matrix Radio Button'"
249
197
  class="uikit-question__matrix-choices-container"
250
198
  ref="uikit-question__matrix-choices-container"
251
- v-breakpoint:questionEl="breakpoints"
252
- :question="question"
253
- :matrix-choice-layout="question.matrixChoiceLayout"
254
- :is-matrix-question-correct="isMatrixQuestionCorrect"
255
- :matrix-answer-keys="matrixAnswerKeys"
256
- :matrix-distractor-keys="matrixDistractorKeys"
257
- :selected-matrix-choices="selectedMatrixChoices"
258
- :show-matrix-answers="showMatrixAnswers"
259
- :review-mode="reviewMode"
260
- :matrix-choice-scores="matrixChoiceScores"
261
- :global-metrics="globalMetrics"
262
- :is-dark-mode="isDarkMode"
263
- :question-el="questionEl"
264
- :breakpoints="breakpoints"
265
- :is-teach-group-review="isTeachGroupReview"
199
+ v-breakpoint="breakpointsWithEl"
266
200
  @emitSelectedMatrixChoice="selectMatrixChoice"
267
201
  >
268
202
  <template #motivationalMoment="{
269
- isCorrect,
203
+ isCorrect:isCorrectMM,
270
204
  choiceKey,
271
- showAnswers,
272
- answerKeys,
205
+ showAnswers:showAnswersMM,
206
+ answerKeys:answerKeysMM,
273
207
  }">
274
208
  <slot
275
209
  name="motivationalMoment"
276
- :showAnswers="showAnswers"
277
- :answerKeys="answerKeys"
210
+ :showAnswers="showAnswersMM"
211
+ :answerKeys="answerKeysMM"
278
212
  :choiceKey="choiceKey"
279
- :isCorrect="isCorrect"
213
+ :isCorrect="isCorrectMM"
280
214
  />
281
215
  </template>
282
216
  <template #explanationBottomExperiment>
@@ -286,35 +220,23 @@
286
220
  <MobileMatrixChoicesContainer
287
221
  v-if="question.type === 'Matrix Checkbox' || question.type === 'Matrix Radio Button'"
288
222
  class="uikit-question__mobile-matrix-choices-container"
289
- v-breakpoint:questionEl="breakpoints"
290
- :question="question"
291
- :matrix-choice-layout="question.matrixChoiceLayout"
292
- :is-matrix-question-correct="isMatrixQuestionCorrect"
293
- :matrix-answer-keys="matrixAnswerKeys"
294
- :matrix-distractor-keys="matrixDistractorKeys"
295
- :selected-matrix-choices="selectedMatrixChoices"
296
- :show-matrix-answers="showMatrixAnswers"
297
- :review-mode="reviewMode"
298
- :global-metrics="globalMetrics"
299
- :is-dark-mode="isDarkMode"
300
- :question-el="questionEl"
301
- :breakpoints="breakpoints"
223
+ v-breakpoint="breakpointsWithEl"
302
224
  @emitSelectedMatrixChoice="selectMatrixChoice"
303
225
  >
304
226
  <template
305
227
  #motivationalMoment="{
306
- isCorrect,
228
+ isCorrect:isCorrectMM,
307
229
  choiceKey,
308
- showAnswers,
309
- answerKeys,
230
+ showAnswers:showAnswersMM,
231
+ answerKeys:answerKeysMM,
310
232
  }"
311
233
  >
312
234
  <slot
313
235
  name="motivationalMoment"
314
- :showAnswers="showAnswers"
315
- :answerKeys="answerKeys"
236
+ :showAnswers="showAnswersMM"
237
+ :answerKeys="answerKeysMM"
316
238
  :choiceKey="choiceKey"
317
- :isCorrect="isCorrect"
239
+ :isCorrect="isCorrectMM"
318
240
  />
319
241
  </template>
320
242
  <template #explanationBottomExperiment>
@@ -326,23 +248,6 @@
326
248
  v-if="((isMCR && showAnswers) || (isMatrixQuestion && showMatrixAnswers)) && !showPaywall"
327
249
  ref="uikit-question__summary"
328
250
  class="uikit-question__summary"
329
- :question="question"
330
- :show-explanation="showExplanation"
331
- :show-explanation-image-long-alt="showExplanationImageLongAlt"
332
- :explanation-image-url="explanationImageUrl"
333
- :explanation-image-alt="explanationImageAlt"
334
- :explanation-image-long-alt="explanationImageLongAlt"
335
- :reference="reference"
336
- :hide-references="hideReferences"
337
- :is-correct="isCorrect"
338
- :is-matrix-question-correct="isMatrixQuestionCorrect"
339
- :is-matrix-question="isMatrixQuestion"
340
- :is-unanswered="isUnanswered"
341
- :review-mode="reviewMode"
342
- :is-dark-mode="isDarkMode"
343
- :question-el="questionEl"
344
- :breakpoints="breakpoints"
345
- :keyword-definitions="keywordDefinitions"
346
251
  @click="keywordClick"
347
252
  @toggleSummaryExplanation="toggleExplanation"
348
253
  @toggleSummaryExplanationImageLongAlt="toggleExplanationImageLongAlt"
@@ -358,15 +263,10 @@
358
263
  <StatsSummary
359
264
  v-if="globalMetrics"
360
265
  class="uikit-question__stats-summary"
361
- :global-metrics="globalMetrics"
362
- :choice-scores="choiceScores"
363
- :matrix-choice-scores="matrixChoiceScores"
364
- :is-matrix-question="isMatrixQuestion"
365
- :is-dark-mode="isDarkMode"
366
266
  />
367
267
  </slot>
368
268
  <div
369
- v-breakpoint:questionEl="breakpoints"
269
+ v-breakpoint="breakpointsWithEl"
370
270
  class="uikit-question__motivational-moment-bottom"
371
271
  :class="{
372
272
  'uikit-question__motivational-moment-bottom--mcr': isMCR,
@@ -386,7 +286,7 @@
386
286
  <slot name="actionExperiment" />
387
287
  <div
388
288
  v-if="!reviewMode"
389
- v-breakpoint:questionEl="breakpoints"
289
+ v-breakpoint="breakpointsWithEl"
390
290
  class="uikit-question__action-container"
391
291
  :class="{
392
292
  'uikit-question__action-container--mcr-summary': isMCR && showAnswers,
@@ -436,7 +336,7 @@
436
336
  </div>
437
337
  <div
438
338
  v-if="showPassageAndImage || (showExplanation && !showPaywall)"
439
- v-breakpoint:questionEl="breakpoints"
339
+ v-breakpoint="breakpointsWithEl"
440
340
  class="uikit-question__right-side"
441
341
  :class="{
442
342
  'uikit-question__right-side--explanation': showExplanation && !showPaywall,
@@ -447,36 +347,12 @@
447
347
  v-if="showPassageAndImage"
448
348
  ref="uikit-question__passage-and-image"
449
349
  class="uikit-question__passage-and-image"
450
- :question="question"
451
- :show-passage-image-long-alt="showPassageImageLongAlt"
452
- :show-passage-and-image="showPassageAndImage"
453
- :passage-image-url="passageImageUrl"
454
- :passage-image-long-alt="passageImageLongAlt"
455
- :passage-image-alt="passageImageAlt"
456
- :passage-and-image-title="passageAndImageTitle"
457
- :is-dark-mode="isDarkMode"
458
- :question-el="questionEl"
459
- :breakpoints="breakpoints"
460
350
  @emitTogglePassageImageLongAlt="togglePassageImageLongAlt"
461
351
  @emitMoveFocusToPrompt="moveFocusToPrompt"
462
352
  />
463
353
  <Explanation
464
354
  ref="uikit-question__explanation"
465
355
  class="uikit-question__explanation"
466
- :question="question"
467
- :show-explanation="showExplanation"
468
- :show-paywall="showPaywall"
469
- :review-mode="reviewMode"
470
- :show-explanation-image-long-alt="showExplanationImageLongAlt"
471
- :explanation-image-url="explanationImageUrl"
472
- :explanation-image-alt="explanationImageAlt"
473
- :explanation-image-long-alt="explanationImageLongAlt"
474
- :reference="reference"
475
- :hide-references="hideReferences"
476
- :is-dark-mode="isDarkMode"
477
- :question-el="questionEl"
478
- :breakpoints="breakpoints"
479
- :keyword-definitions="keywordDefinitions"
480
356
  @toggleExplanationImageLongAlt="toggleExplanationImageLongAlt"
481
357
  @toggleExplanation="toggleExplanation"
482
358
  @click="keywordClick"
@@ -492,17 +368,12 @@
492
368
  </div>
493
369
  </template>
494
370
 
495
- <script lang="ts">
496
- import { Component, Vue, Prop, Watch, Emit } from 'vue-facing-decorator'
497
- import type { ComponentPublicInstance } from 'vue'
371
+ <script setup lang="ts">
372
+ import { computed, onMounted, onBeforeUnmount, watch, useTemplateRef, ref, provide } from 'vue'
498
373
  import PocketButton from '../Buttons/Button.vue'
499
374
  import Icon from '../Icons/Icon.vue'
500
- import type { ITableColumn, ITableSortSettings } from '../Tables/table'
501
- import Table from '../Tables/Table.vue'
502
- import OverflowTooltip from '../Tooltips/OverflowTooltip.vue'
503
375
  import QuestionContext from '../Quiz/Question/QuestionContext.vue'
504
376
  import PassageAndImageDropdown from '../Quiz/Question/PassageAndImageDropdown.vue'
505
- import DropdownExplanation from '../Quiz/Question/DropdownExplanation.vue'
506
377
  import Paywall from '../Quiz/Question/Paywall.vue'
507
378
  import Summary from '../Quiz/Question/Summary.vue'
508
379
  import ChoicesContainer from '../Quiz/Question/ChoicesContainer.vue'
@@ -512,7 +383,6 @@ import PassageAndImage from '../Quiz/Question/PassageAndImage.vue'
512
383
  import MatrixChoicesContainer from '../Quiz/Question/MatrixChoicesContainer.vue'
513
384
  import MobileMatrixChoicesContainer from '../Quiz/Question/MobileMatrixChoicesContainer.vue'
514
385
  import type { Study } from '@pocketprep/types'
515
- import { breakpoint, dark } from '../../directives'
516
386
  import { highlightKeywordsInText, studyModes } from '../../utils'
517
387
  import type {
518
388
  Ref,
@@ -522,1001 +392,931 @@ import type {
522
392
  TChoice,
523
393
  TChoiceScores,
524
394
  TMatrixChoiceScores,
525
- TNamesRow,
526
395
  TViewNames,
527
396
  IScenarioSerial,
528
397
  } from './question'
529
-
530
- @Component({
531
- components: {
532
- PocketButton,
533
- Icon,
534
- Table,
535
- OverflowTooltip,
536
- QuestionContext,
537
- PassageAndImageDropdown,
538
- Paywall,
539
- DropdownExplanation,
540
- Summary,
541
- ChoicesContainer,
542
- StatsSummary,
543
- Explanation,
544
- PassageAndImage,
545
- MatrixChoicesContainer,
546
- MobileMatrixChoicesContainer,
547
- },
548
- directives: {
549
- breakpoint,
550
- dark,
551
- },
398
+ import { dark as vDark, breakpoint as vBreakpoint } from '../../directives'
399
+ import * as InjectionKeys from './Question/injectionSymbols'
400
+
401
+ const props = withDefaults(defineProps<{
402
+ question: Study.Class.QuestionJSON
403
+ questionNumber: number
404
+ quizLength: number
405
+ imageUrlPrefix?: string
406
+ quizMode?: TQuizMode
407
+ initialShowAnswers?: boolean
408
+ showCheckAnswer?: boolean
409
+ hideAnswer?: boolean
410
+ showNextQuestion?: boolean
411
+ reviewMode?: boolean
412
+ previousChoices?: TChoiceKey[] | null
413
+ previousMatrixChoices?: TMatrixChoiceKey[] | null
414
+ globalMetrics?: Study.Class.GlobalQuestionMetricJSON | null
415
+ showNames?: TViewNames | null
416
+ allowKeyboardShortcuts?: boolean
417
+ containerEl?: HTMLElement | null
418
+ isDarkMode?: boolean
419
+ answerSeed?: number[] | null
420
+ showCloseButton?: boolean
421
+ defaultShowExplanation?: boolean | null
422
+ autoFocusPrompt?: boolean | null // false autofocuses the context, null is no autofocus
423
+ showPaywall?: boolean
424
+ hideReferences?: boolean
425
+ isTeachReview?: boolean
426
+ isTeachGroupReview?: boolean
427
+ keywordDefinitions?: { keyword: string; definition: string }[]
428
+ }>(), {
429
+ imageUrlPrefix: '',
430
+ quizMode: 'quick10',
431
+ initialShowAnswers: false,
432
+ showCheckAnswer: true,
433
+ hideAnswer: false,
434
+ showNextQuestion: true,
435
+ reviewMode: false,
436
+ previousChoices: null,
437
+ previousMatrixChoices: null,
438
+ globalMetrics: null,
439
+ showNames: null,
440
+ allowKeyboardShortcuts: true,
441
+ containerEl: null,
442
+ isDarkMode: false,
443
+ answerSeed: null,
444
+ showCloseButton: false,
445
+ defaultShowExplanation: null,
446
+ autoFocusPrompt: false,
447
+ showPaywall: false,
448
+ hideReferences: false,
449
+ isTeachReview: false,
450
+ isTeachGroupReview: false,
451
+ keywordDefinitions: () => [],
552
452
  })
553
- export default class Question extends Vue {
554
- @Prop() question!: Study.Class.QuestionJSON
555
- @Prop() questionNumber!: number
556
- @Prop() quizLength!: number
557
- @Prop({ default: '' }) imageUrlPrefix!: string
558
- @Prop({ default: 'quick10' }) quizMode!: TQuizMode
559
- @Prop({ default: false }) initialShowAnswers!: boolean
560
- @Prop({ default: true }) showCheckAnswer!: boolean
561
- @Prop({ default: false }) hideAnswer!: boolean
562
- @Prop({ default: true }) showNextQuestion!: boolean
563
- @Prop({ default: false }) reviewMode!: boolean
564
- @Prop({ default: null }) previousChoices!: TChoiceKey[] | null
565
- @Prop({ default: null }) previousMatrixChoices!: TMatrixChoiceKey[] | null
566
- @Prop({ default: null }) globalMetrics!: Study.Class.GlobalQuestionMetricJSON | null
567
- @Prop({ default: null }) showNames!: TViewNames | null
568
- @Prop({ default: true }) allowKeyboardShortcuts!: boolean
569
- @Prop({ default: null }) containerEl!: Element | null
570
- @Prop({ default: false }) isDarkMode!: boolean
571
- @Prop({ default: null }) answerSeed!: number[]
572
- @Prop({ default: false }) showCloseButton!: boolean
573
- @Prop({ default: null }) defaultShowExplanation!: boolean
574
- @Prop({ default: false }) autoFocusPrompt!: boolean | null // false autofocuses the context, null is no autofocus
575
- @Prop({ default: false }) showPaywall!: boolean
576
- @Prop({ default: false }) hideReferences!: boolean
577
- @Prop({ default: false }) isTeachReview!: boolean
578
- @Prop({ default: false }) isTeachGroupReview!: boolean
579
- @Prop({ default: [] }) keywordDefinitions!: { keyword: string; definition: string }[]
580
-
581
- hoverChoiceKey: TChoiceKey | null = null
582
- focusChoiceKey: TChoiceKey | null = null
583
- choiceStrikes: TChoiceKey[] = []
584
- selectedChoices: TChoiceKey[] = []
585
- selectedMatrixChoices: TMatrixChoiceKey[] = []
586
- showAnswers = false
587
- showMatrixAnswers = false
588
- showExplanation = false
589
- showPassageImageLongAlt = false
590
- showExplanationImageDropdown = false
591
- showExplanationImageLongAlt = false
592
- swipeStart: {
593
- x: number | null
594
- y: number | null
595
- } = { x: null, y: null }
596
-
597
- sortSettings: ITableSortSettings | null = null
598
- showNamesColumns: ITableColumn[] = [{
599
- name: '',
600
- propName: 'nameOne',
601
- isSortDisabled: true,
602
- }, {
603
- name: '',
604
- propName: 'nameTwo',
605
- isSortDisabled: true,
606
- }, {
607
- name: '',
608
- propName: 'nameThree',
609
- isSortDisabled: true,
610
- }]
611
-
612
- get breakpoints () {
613
- // Passing the container element (typically QuizContainer) allows us to bind to that element's width
614
- if (this.containerEl) {
615
- return {
453
+
454
+ const emit = defineEmits<{
455
+ 'keyword-click': [ keywordClick: {
456
+ keyword: string
457
+ location: string | null
458
+ clickLocation: {
459
+ x: number
460
+ y: number
461
+ }
462
+ target: HTMLElement
463
+ } | undefined ]
464
+ 'selectedChoices': [ selectedChoices: Study.Cloud.IQuizAnswer ]
465
+ 'close': []
466
+ 'upgrade': []
467
+ 'submitQuiz': []
468
+ 'nextQuestion': []
469
+ 'previousQuestion': []
470
+ 'checkAnswer': [ answer: Study.Cloud.IQuizAnswer ]
471
+ 'update:showExplanation': [ showExplanation: boolean ]
472
+ }>()
473
+
474
+ const question = computed(() => props.question)
475
+ const quizLength = computed(() => props.quizLength)
476
+ const isDarkMode = computed(() => props.isDarkMode)
477
+ const quizMode = computed(() => props.quizMode)
478
+ const questionNumber = computed(() => props.questionNumber)
479
+ const imageUrlPrefix = computed(() => props.imageUrlPrefix)
480
+ const globalMetrics = computed(() => props.globalMetrics)
481
+ const reviewMode = computed(() => props.reviewMode)
482
+ const hideReferences = computed(() => props.hideReferences)
483
+ const keywordDefinitions = computed(() => props.keywordDefinitions)
484
+ const showPaywall = computed(() => props.showPaywall)
485
+ const isTeachGroupReview = computed(() => props.isTeachGroupReview)
486
+ const hoverChoiceKey = ref<TChoiceKey | null>(null)
487
+ const focusChoiceKey = ref<TChoiceKey | null>(null)
488
+ const choiceStrikes = ref<TChoiceKey[]>([])
489
+ const selectedChoices = ref<TChoiceKey[]>([])
490
+ const selectedMatrixChoices = ref<TMatrixChoiceKey[]>([])
491
+ const showAnswers = ref(false)
492
+ const showMatrixAnswers = ref(false)
493
+ const showExplanation = ref(false)
494
+ const showPassageImageLongAlt = ref(false)
495
+ const showExplanationImageLongAlt = ref(false)
496
+ const swipeStart = ref<{
497
+ x: number | null
498
+ y: number | null
499
+ }>({ x: null, y: null })
500
+ const questionEl = ref<Element | null>(null)
501
+ const questionRef = useTemplateRef<HTMLElement>('uikit-question')
502
+ const questionContextRef = useTemplateRef<{ contextEl?: HTMLElement }>('uikit-question__context')
503
+ const promptEl = useTemplateRef<HTMLElement>('promptRef')
504
+ const passageAndImageRef = useTemplateRef<{
505
+ passageTitleEl?: HTMLElement
506
+ longAltEl?: HTMLElement
507
+ }>('uikit-question__passage-and-image')
508
+ const choicesContainerRef = useTemplateRef<{
509
+ choiceEls?: HTMLElement[]
510
+ mobileImgDropdownImgDescriptionEl?: HTMLElement
511
+ showExplanationEls?: HTMLElement[] | HTMLElement
512
+ }>('uikit-question__choices-container')
513
+ const passageImageDropdownRef = useTemplateRef<{
514
+ mobileImgDropdownImgDescriptionEl?: HTMLElement
515
+ }>('uikit-question__passage-and-image-dropdown')
516
+ const questionSummaryRef = useTemplateRef<{
517
+ mcrLongAltEl?: HTMLElement
518
+ summaryMCRExplanationEl?: HTMLElement
519
+ summaryMatrixExplanationEl?: HTMLElement
520
+ }>('uikit-question__summary')
521
+ const questionExplanationRef = useTemplateRef<{
522
+ explanationTitleEl?: HTMLElement
523
+ longAltEl?: HTMLElement
524
+ }>('uikit-question__explanation')
525
+
526
+ const breakpointsWithEl = computed(() => {
527
+ // Passing the container element (typically QuizContainer) allows us to bind to that element's width
528
+ if (props.containerEl) {
529
+ return {
530
+ breakpoints: {
616
531
  'mobile': 767,
617
532
  'tablet-portrait': 1023,
618
533
  'tablet-landscape': 1439,
619
- }
620
- } else {
621
- // These were calculated to account for the expected widths of our Quiz Container sides
622
- return {
534
+ },
535
+ containerEl: props.containerEl,
536
+ }
537
+ } else {
538
+ // These were calculated to account for the expected widths of our Quiz Container sides
539
+ return {
540
+ breakpoints: {
623
541
  'mobile': 729, // 767 - (19 + 19)
624
542
  'tablet-portrait': 978, // 1023 - (22 + 23)
625
543
  'tablet-landscape': 1309, // 1439 - (65 + 65)
626
- }
544
+ },
545
+ containerEl: null,
627
546
  }
628
547
  }
548
+ })
629
549
 
630
- get questionEl () {
631
- return this.containerEl || this.$refs['question']
632
- }
633
-
634
- get contextIconType () {
635
- const mode = this.quizMode
636
- return Object.values(studyModes).find(studyMode => studyMode.shortName === mode)?.icon || 'quick10'
637
- }
638
550
 
639
- get isMCR () {
640
- return this.question.type === 'Multiple Correct Response'
641
- }
642
-
643
- get isMatrixQuestion () {
644
- return this.question.type === 'Matrix Checkbox' || this.question.type === 'Matrix Radio Button'
645
- }
551
+ const contextIconType = computed(() => {
552
+ const mode = props.quizMode
553
+ return Object.values(studyModes).find(studyMode => studyMode.shortName === mode)?.icon || 'quick10'
554
+ })
646
555
 
647
- get questionScenario () {
648
- return this.question.questionScenario as Study.Class.QuestionScenarioJSON | undefined
649
- }
556
+ const isMCR = computed(() => {
557
+ return props.question.type === 'Multiple Correct Response'
558
+ })
650
559
 
651
- get numberOfScenarioQuestions () {
652
- return this.questionScenario?.questions.length
653
- }
560
+ const isMatrixQuestion = computed(() => {
561
+ return props.question.type === 'Matrix Checkbox' || props.question.type === 'Matrix Radio Button'
562
+ })
654
563
 
655
- get currentScenarioQuestionNumber () {
656
- if (this.questionScenario?.questions) {
657
- const indexOfScenarioSerial = this.questionScenario.questions.findIndex(
658
- (scenarioSerial: IScenarioSerial) => {
659
- return scenarioSerial.serial === this.question.serial
660
- }
661
- )
564
+ const questionScenario = computed(() => {
565
+ return props.question.questionScenario as Study.Class.QuestionScenarioJSON | undefined
566
+ })
662
567
 
663
- return indexOfScenarioSerial !== -1 ? indexOfScenarioSerial + 1 : undefined
664
- }
568
+ const numberOfScenarioQuestions = computed(() => {
569
+ return questionScenario.value?.questions.length
570
+ })
665
571
 
666
- return undefined
572
+ const currentScenarioQuestionNumber = computed(() => {
573
+ if (questionScenario.value?.questions) {
574
+ const indexOfScenarioSerial = questionScenario.value.questions.findIndex(
575
+ (scenarioSerial: IScenarioSerial) => {
576
+ return scenarioSerial.serial === props.question.serial
577
+ }
578
+ )
667
579
 
580
+ return indexOfScenarioSerial !== -1 ? indexOfScenarioSerial + 1 : undefined
668
581
  }
582
+ return undefined
583
+ })
669
584
 
670
- get passageImageUrl () {
671
- const imageUrl = this.question.passageImage?.url
672
-
673
- return imageUrl ? `${this.imageUrlPrefix}${imageUrl}` : null
674
- }
585
+ const passageImageUrl = computed(() => {
586
+ const imageUrl = props.question.passageImage?.url
587
+
588
+ return imageUrl ? `${props.imageUrlPrefix}${imageUrl}` : null
589
+ })
675
590
 
676
- get passageImageAlt () {
677
- return this.question.passageImage?.altText
678
- }
591
+ const passageImageAlt = computed(() => {
592
+ return props.question.passageImage?.altText
593
+ })
679
594
 
680
- get passageImageLongAlt () {
681
- return this.question.passageImage?.longAltText
682
- }
595
+ const passageImageLongAlt = computed(() => {
596
+ return props.question.passageImage?.longAltText
597
+ })
683
598
 
684
- get explanationImageUrl () {
685
- const imageUrl = this.question.explanationImage?.url
686
-
687
- return imageUrl ? `${this.imageUrlPrefix}${imageUrl}` : null
688
- }
599
+ const explanationImageUrl = computed(() => {
600
+ const imageUrl = props.question.explanationImage?.url
601
+
602
+ return imageUrl ? `${props.imageUrlPrefix}${imageUrl}` : null
603
+ })
689
604
 
690
- get explanationImageAlt () {
691
- return this.question.explanationImage?.altText
692
- }
605
+ const explanationImageAlt = computed(() => {
606
+ return props.question.explanationImage?.altText
607
+ })
693
608
 
694
- get explanationImageLongAlt () {
695
- return this.question.explanationImage?.longAltText
696
- }
609
+ const explanationImageLongAlt = computed(() => {
610
+ return props.question.explanationImage?.longAltText
611
+ })
697
612
 
698
- get showPassageAndImage () {
699
- return !this.showExplanation && !!(this.question.passage || this.passageImageUrl)
700
- }
613
+ const showPassageAndImage = computed(() => {
614
+ return !showExplanation.value && !!(props.question.passage || passageImageUrl.value)
615
+ })
701
616
 
702
- get passageAndImageTitle () {
703
- if (this.question.passage && this.passageImageUrl) {
704
- return this.question.passageLabel ? `${this.question.passageLabel} + Image` :
705
- 'Passage + Image'
706
- } else if (!this.question.passage && this.passageImageUrl) {
707
- return 'Image'
708
- } else {
709
- return this.question.passageLabel ? `${this.question.passageLabel}` :
710
- 'Passage'
711
- }
617
+ const passageAndImageTitle = computed(() => {
618
+ if (props.question.passage && passageImageUrl.value) {
619
+ return props.question.passageLabel ? `${props.question.passageLabel} + Image` :
620
+ 'Passage + Image'
621
+ } else if (!props.question.passage && passageImageUrl.value) {
622
+ return 'Image'
623
+ } else {
624
+ return props.question.passageLabel ? props.question.passageLabel :
625
+ 'Passage'
712
626
  }
627
+ })
713
628
 
714
- get reference () {
715
- return this.question.references?.length ? this.question.references.join('') : undefined
716
- }
629
+ const reference = computed(() => {
630
+ return props.question.references?.length ? props.question.references.join('') : undefined
631
+ })
717
632
 
718
- get answers (): TChoice[] {
719
- const answers = this.question.choices.filter(choice => choice.isCorrect).map((choice, index) => ({
720
- text: choice.text,
721
- key: `a${index + 1}` as `a${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`,
722
- }))
723
-
724
- return answers.filter(choice => !!choice.text)
725
- }
633
+ const answers = computed((): TChoice[] => {
634
+ return props.question.choices.filter(choice => choice.isCorrect).map((choice, index) => ({
635
+ text: choice.text,
636
+ key: `a${index + 1}` as `a${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`,
637
+ })).filter(choice => !!choice.text)
638
+ })
726
639
 
727
- get answerKeys (): TChoiceKey[] {
728
- return this.answers.map(choice => choice.key)
729
- }
640
+ const answerKeys = computed((): TChoiceKey[] => {
641
+ return answers.value.map(choice => choice.key)
642
+ })
730
643
 
731
- get matrixAnswerKeys () {
732
- if (this.question?.matrixChoiceLayout) {
733
- return this.question?.matrixChoiceLayout.flat().filter(choice => choice.startsWith('a'))
734
- }
735
- return []
644
+ const matrixAnswerKeys = computed<TMatrixChoiceKey[]>(() => {
645
+ if (props.question?.matrixChoiceLayout) {
646
+ return props.question?.matrixChoiceLayout.flat().filter(choice => choice.startsWith('a')) as TMatrixChoiceKey[]
736
647
  }
648
+ return [] as TMatrixChoiceKey[]
649
+ })
737
650
 
738
- get matrixDistractorKeys () {
739
- if (this.question?.matrixChoiceLayout) {
740
- return this.question?.matrixChoiceLayout.flat().filter(choice => choice.startsWith('d'))
741
- }
742
- return []
743
- }
651
+ const distractors = computed((): TChoice[] => {
652
+ return props.question.choices.filter(choice => !choice.isCorrect).map((choice, index) => ({
653
+ text: choice.text,
654
+ key: `d${index + 1}` as `d${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`,
655
+ })).filter(choice => !!choice.text)
656
+ })
657
+
658
+ const distractorKeys = computed((): TChoiceKey[] => {
659
+ return distractors.value.map(choice => choice.key)
660
+ })
744
661
 
745
- get distractors (): TChoice[] {
746
- const distractors = this.question.choices.filter(choice => !choice.isCorrect).map((choice, index) => ({
747
- text: choice.text,
748
- key: `d${index + 1}` as `d${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`,
749
- }))
662
+ const choices = computed(() => {
663
+ return shuffleChoices([
664
+ ...answers.value,
665
+ ...distractors.value,
666
+ ])
667
+ })
750
668
 
751
- return distractors.filter(choice => !!choice.text)
752
- }
669
+ const isCorrect = computed(() => {
670
+ // In order to be correct, user must have selected all the answers and none of the distractors
671
+ return showAnswers.value
672
+ && selectedChoices.value.length === answerKeys.value.length
673
+ && !selectedChoices.value.join(' ').includes('d')
674
+ })
753
675
 
754
- get distractorKeys (): TChoiceKey[] {
755
- return this.distractors.map(choice => choice.key)
756
- }
676
+ const isMatrixQuestionCorrect = computed(() => {
677
+ return showMatrixAnswers.value
678
+ && selectedMatrixChoices.value.length === matrixAnswerKeys.value.length
679
+ && !selectedMatrixChoices.value.join(' ').includes('d')
680
+ })
757
681
 
758
- get choices () {
759
- return this.shuffleChoices([
760
- ...this.answers,
761
- ...this.distractors,
762
- ])
682
+ const choiceScores = computed((): TChoiceScores => {
683
+ const metrics = globalMetrics.value
684
+ const scores: TChoiceScores = {
685
+ totalAnswered: selectedChoices.value.length && showAnswers && !props.reviewMode ? 1 : 0,
686
+ answeredCorrectly: isCorrect.value && !props.reviewMode ? 1 : 0,
763
687
  }
764
688
 
765
- get isCorrect () {
766
- // In order to be correct, user must have selected all the answers and none of the distractors
767
- return this.showAnswers
768
- && this.selectedChoices.length === this.answerKeys.length
769
- && !this.selectedChoices.join(' ').includes('d')
689
+ if (!metrics) {
690
+ return scores
770
691
  }
771
692
 
772
- get isMatrixQuestionCorrect () {
773
- return this.showMatrixAnswers
774
- && this.selectedMatrixChoices.length === this.matrixAnswerKeys.length
775
- && !this.selectedMatrixChoices.join(' ').includes('d')
776
- }
693
+ scores.totalAnswered += (
694
+ (metrics.answeredCorrectlyCount || 0)
695
+ + (metrics.answeredIncorrectlyCount || 0)
696
+ )
697
+ scores.answeredCorrectly += (metrics.answeredCorrectlyCount || 0)
777
698
 
778
- get choiceScores (): TChoiceScores {
779
- const globalMetrics = this.globalMetrics
699
+ const selectedChoicesCount = showAnswers.value && !props.reviewMode && selectedChoices.value.length || 0
700
+ const totalChoicesCount = selectedChoicesCount + Object.values(metrics.choiceStats)
701
+ .reduce<number>((choiceCount, acc) => (acc || 0) + (choiceCount || 0), 0)
780
702
 
781
- const scores: TChoiceScores = {
782
- totalAnswered: this.selectedChoices.length && this.showAnswers && !this.reviewMode ? 1 : 0,
783
- answeredCorrectly: this.isCorrect && !this.reviewMode ? 1 : 0,
784
- }
703
+ choices.value.forEach(({ key }) => {
704
+ const globalAnswerCount = metrics.choiceStats[key] || 0
705
+ const userAnswerCount = showAnswers.value && selectedChoices.value.includes(key) && !props.reviewMode ? 1 : 0
706
+ const choiceAnswerCount = globalAnswerCount + userAnswerCount
707
+ const answerPct = Math.round((choiceAnswerCount / totalChoicesCount) * 100)
708
+ // In case we have NaN here when nobody answered the question and nobody got it right
709
+ scores[key] = answerPct || 0
710
+ })
785
711
 
786
- if (!globalMetrics) {
787
- return scores
788
- }
712
+ return scores
713
+ })
789
714
 
790
- scores.totalAnswered += (
791
- (globalMetrics.answeredCorrectlyCount || 0)
792
- + (globalMetrics.answeredIncorrectlyCount || 0)
793
- )
794
- scores.answeredCorrectly += (globalMetrics.answeredCorrectlyCount || 0)
795
-
796
- const selectedChoicesCount = this.showAnswers && !this.reviewMode && this.selectedChoices.length || 0
797
- const totalChoicesCount = selectedChoicesCount + Object.values(globalMetrics.choiceStats)
798
- .reduce<number>((choiceCount, acc) => (acc || 0) + (choiceCount || 0), 0)
799
-
800
- this.choices.forEach(({ key }) => {
801
- const globalAnswerCount = globalMetrics.choiceStats[key] || 0
802
- const userAnswerCount = this.showAnswers && this.selectedChoices.includes(key) && !this.reviewMode ? 1 : 0
803
- const choiceAnswerCount = globalAnswerCount + userAnswerCount
804
- const answerPct = Math.round((choiceAnswerCount / totalChoicesCount) * 100)
805
- // In case we have NaN here when nobody answered the question and nobody got it right
806
- scores[key] = answerPct || 0
807
- })
715
+ const matrixChoiceScores = computed((): TMatrixChoiceScores => {
716
+ const metrics = props.globalMetrics
808
717
 
809
- return scores
718
+ const scores: TMatrixChoiceScores = {
719
+ totalAnswered: selectedMatrixChoices.value.length &&
720
+ showMatrixAnswers && !props.reviewMode ? 1 : 0,
721
+ answeredCorrectly: isCorrect.value && !props.reviewMode ? 1 : 0,
810
722
  }
811
723
 
812
- get matrixChoiceScores (): TMatrixChoiceScores {
813
- const globalMetrics = this.globalMetrics
814
-
815
- const scores: TMatrixChoiceScores = {
816
- totalAnswered: this.selectedMatrixChoices.length &&
817
- this.showMatrixAnswers && !this.reviewMode ? 1 : 0,
818
- answeredCorrectly: this.isCorrect && !this.reviewMode ? 1 : 0,
819
- }
820
-
821
- if (!globalMetrics) {
822
- return scores
823
- }
824
-
825
- scores.totalAnswered += (
826
- (globalMetrics.answeredCorrectlyCount || 0)
827
- + (globalMetrics.answeredIncorrectlyCount || 0)
828
- )
829
- scores.answeredCorrectly += (globalMetrics.answeredCorrectlyCount || 0)
724
+ if (!metrics) {
830
725
  return scores
831
726
  }
832
727
 
833
- get isMatrixQuestionAnswered () {
834
- // Matrix questions are answered if each row has a selected choice
835
- if (this.isMatrixQuestion) {
836
- const answeredRowNums = this.selectedMatrixChoices.map(choice => {
837
- if (choice as TMatrixChoiceKey) {
838
- return Number(choice.split('_')[0]?.slice(1))
839
- }
840
- return
841
- })
842
-
843
- const matrixRows = this.question.matrixChoiceLayout || []
844
- const isEveryRowAnswered = matrixRows.every((row, index) => {
845
- const rowNum = index + 1
846
- const hasSelectedChoiceForRow = answeredRowNums.includes(rowNum)
847
- return hasSelectedChoiceForRow
848
- })
849
- return isEveryRowAnswered
850
- }
851
- return false
852
- }
853
-
854
- get isUnanswered () {
855
- if (!this.isMatrixQuestion) {
856
- return this.selectedChoices.length === 0
857
- }
728
+ scores.totalAnswered += (
729
+ (metrics.answeredCorrectlyCount || 0)
730
+ + (metrics.answeredIncorrectlyCount || 0)
731
+ )
732
+ scores.answeredCorrectly += (metrics.answeredCorrectlyCount || 0)
733
+ return scores
734
+ })
858
735
 
859
- return !this.isMatrixQuestionAnswered
860
- }
736
+ const isMatrixQuestionAnswered = computed(() => {
737
+ // Matrix questions are answered if each row has a selected choice
738
+ if (isMatrixQuestion.value) {
739
+ const answeredRowNums = selectedMatrixChoices.value.map(choice => {
740
+ if (choice) {
741
+ return Number(choice.split('_')[0]?.slice(1))
742
+ }
743
+ return
744
+ })
861
745
 
862
- get prompt () {
863
- return highlightKeywordsInText({
864
- text: this.question.prompt,
865
- keywordDefinitions: this.keywordDefinitions,
866
- isDarkMode: this.isDarkMode,
867
- location: 'prompt',
746
+ const matrixRows = props.question.matrixChoiceLayout || []
747
+ const isEveryRowAnswered = matrixRows.every((row, index) => {
748
+ const rowNum = index + 1
749
+ const hasSelectedChoiceForRow = answeredRowNums.includes(rowNum)
750
+ return hasSelectedChoiceForRow
868
751
  })
752
+ return isEveryRowAnswered
869
753
  }
754
+ return false
755
+ })
870
756
 
871
- created () {
872
- if (this.reviewMode) {
873
- this.startReviewMode()
874
- }
875
-
876
- if (!this.isMatrixQuestion && this.previousChoices) {
877
- this.updateSelectedChoices(this.previousChoices)
878
- }
879
-
880
- if (this.isMatrixQuestion && this.previousMatrixChoices) {
881
- this.updateSelectedMatrixChoices(this.previousMatrixChoices)
882
- }
757
+ const isUnanswered = computed(() => {
758
+ if (!isMatrixQuestion.value) {
759
+ return selectedChoices.value.length === 0
883
760
  }
884
761
 
885
- mounted () {
886
- if (this.initialShowAnswers) {
887
- this.showAnswers = this.initialShowAnswers
888
- this.showMatrixAnswers = this.initialShowAnswers
889
- }
890
-
891
- if (this.allowKeyboardShortcuts) {
892
- window.addEventListener('keydown', this.keydownListener)
893
- }
894
-
895
- if (this.autoFocusPrompt) {
896
- this.moveFocusToPrompt()
897
- } else if (this.autoFocusPrompt === false) {
898
- setTimeout(() => {
899
- const contextComp = this.$refs['uikit-question__context'] as ComponentPublicInstance | undefined
900
- const contextEl = contextComp?.$refs['uikit-question-context'] as HTMLElement | undefined
901
- contextEl?.focus()
902
- }, 0)
903
- }
904
- this.sortSettings = {
905
- column: this.showNamesColumns[0] || null,
906
- direction: 1,
907
- }
908
-
909
- const prompt = this.$refs['prompt'] as HTMLElement
910
- const promptElements = prompt?.querySelectorAll('p')
911
- if (promptElements.length) {
912
- promptElements.forEach(el => el.setAttribute('tabindex', '0'))
913
- }
914
- }
762
+ return !isMatrixQuestionAnswered.value
763
+ })
915
764
 
916
- beforeUnmount () {
917
- window.removeEventListener('keydown', this.keydownListener)
918
- }
765
+ const prompt = computed(() => {
766
+ return highlightKeywordsInText({
767
+ text: props.question.prompt,
768
+ keywordDefinitions: props.keywordDefinitions,
769
+ isDarkMode: props.isDarkMode,
770
+ location: 'prompt',
771
+ })
772
+ })
919
773
 
920
- keydownListener (e: KeyboardEvent) {
921
- switch (e.code) {
922
- case 'KeyA':
923
- this.choices[0] && this.selectChoice(this.choices[0].key, true)
924
- break
925
- case 'KeyB':
926
- this.choices[1] && this.selectChoice(this.choices[1].key, true)
927
- break
928
- case 'KeyC':
929
- this.choices[2] && this.selectChoice(this.choices[2].key, true)
930
- break
931
- case 'KeyD':
932
- this.choices[3] && this.selectChoice(this.choices[3].key, true)
933
- break
934
- case 'KeyE':
935
- this.choices[4] && this.selectChoice(this.choices[4].key, true)
936
- break
937
- case 'KeyF':
938
- this.choices[5] && this.selectChoice(this.choices[5].key, true)
939
- break
940
- case 'KeyG':
941
- this.choices[6] && this.selectChoice(this.choices[6].key, true)
942
- break
943
- case 'KeyH':
944
- this.choices[7] && this.selectChoice(this.choices[7].key, true)
945
- break
946
- case 'KeyI':
947
- this.choices[8] && this.selectChoice(this.choices[8].key, true)
948
- break
949
- case 'KeyJ':
950
- this.choices[9] && this.selectChoice(this.choices[9].key, true)
951
- break
952
- case 'KeyX':
953
- this.showAnswers && this.toggleExplanation()
954
- break
955
- case 'Escape':
956
- this.emitClose()
774
+ const keydownListener = (e: KeyboardEvent) => {
775
+ switch (e.code) {
776
+ case 'KeyA':
777
+ choices.value[0] && selectChoice(choices.value[0].key, true)
778
+ break
779
+ case 'KeyB':
780
+ choices.value[1] && selectChoice(choices.value[1].key, true)
781
+ break
782
+ case 'KeyC':
783
+ choices.value[2] && selectChoice(choices.value[2].key, true)
784
+ break
785
+ case 'KeyD':
786
+ choices.value[3] && selectChoice(choices.value[3].key, true)
787
+ break
788
+ case 'KeyE':
789
+ choices.value[4] && selectChoice(choices.value[4].key, true)
790
+ break
791
+ case 'KeyF':
792
+ choices.value[5] && selectChoice(choices.value[5].key, true)
793
+ break
794
+ case 'KeyG':
795
+ choices.value[6] && selectChoice(choices.value[6].key, true)
796
+ break
797
+ case 'KeyH':
798
+ choices.value[7] && selectChoice(choices.value[7].key, true)
799
+ break
800
+ case 'KeyI':
801
+ choices.value[8] && selectChoice(choices.value[8].key, true)
802
+ break
803
+ case 'KeyJ':
804
+ choices.value[9] && selectChoice(choices.value[9].key, true)
805
+ break
806
+ case 'KeyX':
807
+ showAnswers.value && toggleExplanation()
808
+ break
809
+ case 'Escape':
810
+ emitClose()
811
+ e.preventDefault()
812
+ break
813
+ case 'Enter':
814
+ if (!showAnswers.value && selectedChoices.value.length && focusChoiceKey.value === null) {
815
+ clickCheckAnswer()
957
816
  e.preventDefault()
958
- break
959
- case 'Enter':
960
- if (!this.showAnswers && this.selectedChoices.length && this.focusChoiceKey === null) {
961
- this.clickCheckAnswer()
962
- e.preventDefault()
963
- }
964
- break
965
- case 'ArrowLeft':
966
- this.emitPreviousQuestion()
967
- e.preventDefault()
968
- break
969
- case 'ArrowRight':
970
- this.emitNextQuestion()
971
- e.preventDefault()
972
- break
973
- }
974
- }
975
-
976
- moveFocusToPassage () {
977
- setTimeout(() => {
978
- const passageAndImageComp =
979
- this.$refs['uikit-question__passage-and-image'] as ComponentPublicInstance | undefined
980
- const passageTitleEl =
981
- // eslint-disable-next-line max-len
982
- passageAndImageComp?.$refs['uikit-question-passage-and-image__passage-and-image-title'] as HTMLElement | undefined
983
- if (passageTitleEl) {
984
- passageTitleEl?.focus()
985
817
  }
986
- }, 0)
987
- }
988
-
989
- moveFocusToPrompt () {
990
- setTimeout(() => {
991
- const promptEl = this.$refs['prompt'] as HTMLElement | undefined
992
- promptEl?.focus()
993
- }, 0)
818
+ break
819
+ case 'ArrowLeft':
820
+ emitPreviousQuestion()
821
+ e.preventDefault()
822
+ break
823
+ case 'ArrowRight':
824
+ emitNextQuestion()
825
+ e.preventDefault()
826
+ break
994
827
  }
828
+ }
995
829
 
996
- @Emit('keyword-click')
997
- keywordClick (event: MouseEvent) {
998
- const target = event.target as HTMLElement
999
- if (target.classList.contains('keyword-highlight')) {
1000
- const keyword = target.innerText.trim()
1001
- const location = target.getAttribute('data-location')
1002
- const clickLocation = { x: event.clientX, y: event.clientY }
1003
- return {
1004
- keyword,
1005
- location,
1006
- clickLocation,
1007
- target,
1008
- }
1009
- }
830
+ const moveFocusToPassage = () => {
831
+ const passageTitleEl = passageAndImageRef?.value?.passageTitleEl
832
+ if (passageTitleEl) {
833
+ passageTitleEl?.focus()
1010
834
  }
835
+ }
1011
836
 
1012
- startReviewMode () {
1013
- if (!this.isMatrixQuestion) {
1014
- this.showAnswers = true
1015
- this.showExplanation = this.defaultShowExplanation === null ? true : this.defaultShowExplanation
1016
- this.selectedChoices = this.answerKeys
1017
- } else {
1018
- this.showMatrixAnswers = true
1019
- this.showExplanation = this.defaultShowExplanation === null ? true : this.defaultShowExplanation
1020
- this.selectedMatrixChoices = this.matrixAnswerKeys as TMatrixChoiceKey[]
1021
- }
1022
- }
837
+ const moveFocusToPrompt = () => {
838
+ promptEl?.value?.focus()
839
+ }
1023
840
 
1024
- stopReviewMode () {
1025
- this.showAnswers = false
1026
- this.showMatrixAnswers = false
1027
- this.showExplanation = false
1028
- this.selectedChoices = []
1029
- this.selectedMatrixChoices = []
841
+ const keywordClick = (event: MouseEvent) => {
842
+ const target = event.target as HTMLElement
843
+ if (target.classList.contains('keyword-highlight')) {
844
+ const keyword = target.innerText.trim()
845
+ const location = target.getAttribute('data-location')
846
+ const clickLocation = { x: event.clientX, y: event.clientY }
847
+ emit('keyword-click', {
848
+ keyword,
849
+ location,
850
+ clickLocation,
851
+ target,
852
+ })
1030
853
  }
854
+ }
1031
855
 
1032
- updateSelectedChoices (choices: TChoiceKey[]) {
1033
- this.selectedChoices = [ ...choices ]
856
+ const startReviewMode = () => {
857
+ if (!isMatrixQuestion.value) {
858
+ showAnswers.value = true
859
+ showExplanation.value = props.defaultShowExplanation === null ? true : props.defaultShowExplanation
860
+ selectedChoices.value = answerKeys.value
861
+ } else {
862
+ showMatrixAnswers.value = true
863
+ showExplanation.value = props.defaultShowExplanation === null ? true : props.defaultShowExplanation
864
+ selectedMatrixChoices.value = matrixAnswerKeys.value
1034
865
  }
866
+ }
1035
867
 
1036
- updateSelectedMatrixChoices (matrixChoices: TMatrixChoiceKey[]) {
1037
- this.selectedMatrixChoices = [ ...matrixChoices ]
1038
- }
868
+ const stopReviewMode = () => {
869
+ showAnswers.value = false
870
+ showMatrixAnswers.value = false
871
+ showExplanation.value = false
872
+ selectedChoices.value = []
873
+ selectedMatrixChoices.value = []
874
+ }
1039
875
 
1040
- // deterministic shuffling of choices so they don't change order everytime you reload the component
1041
- shuffleChoices (choices: TChoice[]): TChoice[] {
1042
- const sortedChoices = choices.sort((a, b) => {
1043
- const hashChar = (char: string, num: number) => ((num << 5) - num) + char.charCodeAt(0)
876
+ const updateSelectedChoices = (updatedChoices: TChoiceKey[]) => {
877
+ selectedChoices.value = [ ...updatedChoices ]
878
+ }
1044
879
 
1045
- const aHash = a.text?.split('')
1046
- .reduce((acc: number, char: string) => hashChar(char, acc) & hashChar(char, acc), 0)
1047
- const bHash = b.text?.split('')
1048
- .reduce((acc: number, char: string) => hashChar(char, acc) & hashChar(char, acc), 0)
880
+ const updateSelectedMatrixChoices = (matrixChoices: TMatrixChoiceKey[]) => {
881
+ selectedMatrixChoices.value = [ ...matrixChoices ]
882
+ }
1049
883
 
1050
- return (aHash || 0) - (bHash || 0)
1051
- })
884
+ // deterministic shuffling of choices so they don't change order everytime you reload the component
885
+ const shuffleChoices = (choicesToShuffle: TChoice[]): TChoice[] => {
886
+ const sortedChoices = choicesToShuffle.sort((a, b) => {
887
+ const hashChar = (char: string, num: number) => ((num << 5) - num) + char.charCodeAt(0)
1052
888
 
1053
- return this.answerSeed
1054
- ? this.answerSeed.reduce<TChoice[]>((acc, i) => {
1055
- const sortedChoice = sortedChoices[i]
1056
- if (sortedChoice) {
1057
- acc.push(sortedChoice)
1058
- }
889
+ const aHash = a.text?.split('')
890
+ .reduce((acc: number, char: string) => hashChar(char, acc) & hashChar(char, acc), 0)
891
+ const bHash = b.text?.split('')
892
+ .reduce((acc: number, char: string) => hashChar(char, acc) & hashChar(char, acc), 0)
1059
893
 
1060
- return acc
1061
- }, [])
1062
- : sortedChoices
1063
- }
894
+ return (aHash || 0) - (bHash || 0)
895
+ })
1064
896
 
1065
- choiceFocusOut (event: FocusEvent) {
1066
- const relatedTarget = event.relatedTarget
1067
- if (
1068
- relatedTarget instanceof Element
1069
- ) {
1070
- const classesString = relatedTarget.getAttribute('class') || ''
1071
- const classes = classesString.split(' ')
1072
- if (classes.includes('uikit-question-choices-container__strikethrough')) {
1073
- return // Don't set focusChoiceKey = null
897
+ return props.answerSeed
898
+ ? props.answerSeed.reduce<TChoice[]>((acc, i) => {
899
+ const sortedChoice = sortedChoices[i]
900
+ if (sortedChoice) {
901
+ acc.push(sortedChoice)
1074
902
  }
903
+
904
+ return acc
905
+ }, [])
906
+ : sortedChoices
907
+ }
908
+
909
+ const choiceFocusOut = (event: FocusEvent) => {
910
+ const relatedTarget = event.relatedTarget
911
+ if (
912
+ relatedTarget instanceof Element
913
+ ) {
914
+ const classesString = relatedTarget.getAttribute('class') || ''
915
+ const classes = classesString.split(' ')
916
+ if (classes.includes('uikit-question-choices-container__strikethrough')) {
917
+ return // Don't set focusChoiceKey = null
1075
918
  }
1076
- this.focusChoiceKey = null
1077
919
  }
920
+ focusChoiceKey.value = null
921
+ }
1078
922
 
1079
- choiceFocusIn (choiceKey: TChoiceKey) {
1080
- this.focusChoiceKey = choiceKey
1081
- }
923
+ const choiceFocusIn = (choiceKey: TChoiceKey) => {
924
+ focusChoiceKey.value = choiceKey
925
+ }
1082
926
 
1083
- choiceMouseOver (choiceKey: TChoiceKey) {
1084
- this.hoverChoiceKey = choiceKey
1085
- }
927
+ const choiceMouseOver = (choiceKey: TChoiceKey) => {
928
+ hoverChoiceKey.value = choiceKey
929
+ }
1086
930
 
1087
- choiceMouseLeave () {
1088
- this.hoverChoiceKey = null
1089
- }
931
+ const choiceMouseLeave = () => {
932
+ hoverChoiceKey.value = null
933
+ }
1090
934
 
1091
- handleTouchStart (event: TouchEvent) {
1092
- this.swipeStart = {
1093
- x: event?.touches[0]?.clientX || null,
1094
- y: event?.touches[0]?.clientY || null,
1095
- }
935
+ const handleTouchStart = (event: TouchEvent) => {
936
+ swipeStart.value = {
937
+ x: event?.touches[0]?.clientX || null,
938
+ y: event?.touches[0]?.clientY || null,
1096
939
  }
940
+ }
1097
941
 
1098
- handleTouchMove (event: TouchEvent) {
1099
- if (event.cancelable) {
1100
- const changedY = event?.changedTouches[0]?.clientY || null
1101
- const changedX = event?.changedTouches[0]?.clientX || null
1102
- if (
1103
- changedY !== null
1104
- && changedX !== null
1105
- && this.swipeStart.y !== null
1106
- && this.swipeStart.x !== null
1107
- && Math.abs(changedX - this.swipeStart.x) > 26
1108
- ) {
1109
- event.stopPropagation()
1110
- event.preventDefault()
1111
- }
942
+ const handleTouchMove = (event: TouchEvent) => {
943
+ if (event.cancelable) {
944
+ const changedY = event?.changedTouches[0]?.clientY || null
945
+ const changedX = event?.changedTouches[0]?.clientX || null
946
+ if (
947
+ changedY !== null
948
+ && changedX !== null
949
+ && swipeStart.value.y !== null
950
+ && swipeStart.value.x !== null
951
+ && Math.abs(changedX - swipeStart.value.x) > 26
952
+ ) {
953
+ event.stopPropagation()
954
+ event.preventDefault()
1112
955
  }
1113
956
  }
957
+ }
1114
958
 
1115
- handleTouchEnd (option: {choiceKey: TChoiceKey; event: TouchEvent}) {
1116
- const choiceKey = option.choiceKey
1117
- const event = option.event
1118
- const targetEl = event.target as Ref
1119
- const swipeEnd = {
1120
- x: event?.changedTouches[0]?.clientX || null,
1121
- y: event?.changedTouches[0]?.clientY || null,
959
+ const handleTouchEnd = (option: { choiceKey: TChoiceKey; event: TouchEvent }) => {
960
+ const choiceKey = option.choiceKey
961
+ const event = option.event
962
+ const targetEl = event.target as Ref
963
+ const swipeEnd = {
964
+ x: event?.changedTouches[0]?.clientX || null,
965
+ y: event?.changedTouches[0]?.clientY || null,
966
+ }
967
+ if (
968
+ targetEl instanceof HTMLElement
969
+ && swipeEnd.x !== null
970
+ && swipeEnd.y !== null
971
+ && swipeStart.value.x !== null
972
+ && Math.abs(swipeEnd.x - swipeStart.value.x) > 80
973
+ ) {
974
+ const choiceEls = choicesContainerRef?.value?.choiceEls
975
+ const parent = choiceEls?.find(choiceEl => choiceEl.contains(targetEl))
976
+ const finalElement = document.elementFromPoint(swipeEnd.x, swipeEnd.y)
977
+ if (parent?.contains(finalElement)) {
978
+ clickChoiceStrike(choiceKey)
979
+ }
980
+ } else if (!showAnswers.value) {
981
+ if (event.cancelable) {
982
+ event.preventDefault()
1122
983
  }
1123
984
  if (
1124
- targetEl instanceof HTMLElement
1125
- && swipeEnd.x !== null
1126
- && swipeEnd.y !== null
1127
- && this.swipeStart.x !== null
1128
- && Math.abs(swipeEnd.x - this.swipeStart.x) > 80
985
+ swipeEnd.y !== null
986
+ && swipeStart.value.y !== null
987
+ && Math.abs(swipeEnd.y - swipeStart.value.y) < 20
1129
988
  ) {
1130
- const choicesContainerComp = this.$refs[
1131
- 'uikit-question__choices-container'
1132
- ] as ComponentPublicInstance | undefined
1133
- const choiceEls = choicesContainerComp?.$refs.choices as HTMLElement[] | undefined
1134
- const parent = choiceEls?.find(choiceEl => choiceEl.contains(targetEl))
1135
- const finalElement = document.elementFromPoint(swipeEnd.x, swipeEnd.y)
1136
- if (parent?.contains(finalElement)) {
1137
- this.clickChoiceStrike(choiceKey)
1138
- }
1139
- } else if (!this.showAnswers) {
1140
- if (event.cancelable) {
1141
- event.preventDefault()
1142
- }
1143
- if (
1144
- swipeEnd.y !== null
1145
- && this.swipeStart.y !== null
1146
- && Math.abs(swipeEnd.y - this.swipeStart.y) < 20
1147
- ) {
1148
- this.selectChoice(choiceKey)
1149
- }
989
+ selectChoice(choiceKey)
1150
990
  }
1151
-
1152
- this.swipeStart = { x: null, y: null }
1153
991
  }
1154
992
 
1155
- clickChoiceStrike (choiceKey: TChoiceKey) {
1156
- if (this.showAnswers) {
1157
- return
1158
- }
993
+ swipeStart.value = { x: null, y: null }
994
+ }
1159
995
 
1160
- const choiceStrikes = this.choiceStrikes
1161
- const indexOfChoiceStrike = choiceStrikes.indexOf(choiceKey)
1162
- if (indexOfChoiceStrike !== -1) {
1163
- choiceStrikes.splice(indexOfChoiceStrike, 1)
1164
- } else {
1165
- choiceStrikes.push(choiceKey)
1166
- // If striking a selected choice, unselect it
1167
- const selectedChoices = this.selectedChoices
1168
- const indexOfSelectedChoice = selectedChoices.indexOf(choiceKey)
1169
- if (indexOfSelectedChoice !== -1) {
1170
- selectedChoices.splice(indexOfSelectedChoice, 1)
1171
- }
1172
- }
996
+ const clickChoiceStrike = (choiceKey: TChoiceKey) => {
997
+ if (showAnswers.value) {
998
+ return
1173
999
  }
1174
1000
 
1175
- selectChoice (choiceKey: TChoiceKey, focusChoice?: boolean) {
1176
- if (this.showAnswers) {
1177
- return
1178
- }
1179
- const selectedChoices = this.selectedChoices
1180
- const indexOfSelectedChoice = selectedChoices.indexOf(choiceKey)
1001
+ const indexOfChoiceStrike = choiceStrikes.value.indexOf(choiceKey)
1002
+ if (indexOfChoiceStrike !== -1) {
1003
+ choiceStrikes.value.splice(indexOfChoiceStrike, 1)
1004
+ } else {
1005
+ choiceStrikes.value.push(choiceKey)
1006
+ // If striking a selected choice, unselect it
1007
+ const indexOfSelectedChoice = selectedChoices.value.indexOf(choiceKey)
1181
1008
  if (indexOfSelectedChoice !== -1) {
1182
- selectedChoices.splice(indexOfSelectedChoice, 1)
1009
+ selectedChoices.value.splice(indexOfSelectedChoice, 1)
1010
+ }
1011
+ }
1012
+ }
1013
+
1014
+ const selectChoice = (choiceKey: TChoiceKey, focusChoice?: boolean) => {
1015
+ if (showAnswers.value) {
1016
+ return
1017
+ }
1018
+ const indexOfSelectedChoice = selectedChoices.value.indexOf(choiceKey)
1019
+ if (indexOfSelectedChoice !== -1) {
1020
+ selectedChoices.value.splice(indexOfSelectedChoice, 1)
1021
+ } else {
1022
+ if (isMCR.value) {
1023
+ selectedChoices.value.push(choiceKey)
1183
1024
  } else {
1184
- if (this.isMCR) {
1185
- selectedChoices.push(choiceKey)
1186
- } else {
1187
- this.hoverChoiceKey = null
1188
- this.selectedChoices = [ choiceKey ]
1189
- if (!this.showCheckAnswer) {
1190
- this.clickCheckAnswer()
1191
- }
1192
- }
1193
- // If selecting a striked choice, unstrike it
1194
- const choiceStrikes = this.choiceStrikes
1195
- const indexOfChoiceStrike = this.choiceStrikes.indexOf(choiceKey)
1196
- if (indexOfChoiceStrike !== -1) {
1197
- choiceStrikes.splice(indexOfChoiceStrike, 1)
1025
+ hoverChoiceKey.value = null
1026
+ selectedChoices.value = [ choiceKey ]
1027
+ if (!props.showCheckAnswer) {
1028
+ clickCheckAnswer()
1198
1029
  }
1199
1030
  }
1200
-
1201
- if (focusChoice) {
1202
- const choiceContainerComp =
1203
- this.$refs['uikit-question__choices-container'] as ComponentPublicInstance | undefined
1204
- const choiceEl = choiceContainerComp?.$refs[`choice-${choiceKey}`] as HTMLElement | HTMLElement[];
1205
- ('length' in choiceEl)
1206
- ? choiceEl[0]?.focus()
1207
- : choiceEl.focus()
1031
+ // If selecting a striked choice, unstrike it
1032
+ const indexOfChoiceStrike = choiceStrikes.value.indexOf(choiceKey)
1033
+ if (indexOfChoiceStrike !== -1) {
1034
+ choiceStrikes.value.splice(indexOfChoiceStrike, 1)
1208
1035
  }
1209
1036
  }
1210
1037
 
1211
- selectMatrixChoice (matrixChoiceKeys: TMatrixChoiceKey[]) {
1212
- if (this.showMatrixAnswers) {
1213
- return
1038
+ if (focusChoice) {
1039
+ const choiceEls = choicesContainerRef?.value?.choiceEls
1040
+ if (choiceEls && 'length' in choiceEls) {
1041
+ choiceEls[0]?.focus()
1214
1042
  }
1215
- this.selectedMatrixChoices = matrixChoiceKeys
1216
1043
  }
1044
+ }
1217
1045
 
1218
- togglePassageImageLongAlt () {
1219
- this.showPassageImageLongAlt = !this.showPassageImageLongAlt
1220
-
1221
- if (this.showPassageImageLongAlt) {
1222
- setTimeout(() => {
1223
- const mobileLongAltComp =
1224
- this.$refs['uikit-question__passage-and-image-dropdown'] as ComponentPublicInstance | undefined
1225
- const mobileImgDropdownImgDescription =
1226
- // eslint-disable-next-line max-len
1227
- mobileLongAltComp?.$refs['uikit-question-passage-and-image-dropdown__img-dropdown-img-description'] as HTMLElement | undefined
1228
-
1229
- const passageAndImageComp =
1230
- this.$refs['uikit-question__passage-and-image'] as ComponentPublicInstance | undefined
1231
- const longAlt =
1232
- passageAndImageComp?.$refs['uikit-question-passage-and-image__passage-image-description'] as Ref
1233
-
1234
- // Checking offsetParent tells us which element is visible
1235
- if (mobileImgDropdownImgDescription?.offsetParent) {
1236
- mobileImgDropdownImgDescription.focus()
1237
- } else if (longAlt?.offsetParent) {
1238
- longAlt.focus()
1239
- }
1240
- }, 0)
1241
- }
1046
+ const selectMatrixChoice = (matrixChoiceKeys: TMatrixChoiceKey[]) => {
1047
+ if (showMatrixAnswers.value) {
1048
+ return
1242
1049
  }
1050
+ selectedMatrixChoices.value = matrixChoiceKeys
1051
+ }
1243
1052
 
1244
- toggleExplanationImageLongAlt () {
1245
- this.showExplanationImageLongAlt = !this.showExplanationImageLongAlt
1246
- if (this.showExplanationImageLongAlt) {
1247
- setTimeout(() => {
1248
- // checks for mobile / tablet portrait uikit-question-summary
1249
- const mcrLongAltComp = this.$refs['uikit-question__summary'] as ComponentPublicInstance | undefined
1250
- const mcrLongAlt =
1251
- // eslint-disable-next-line max-len
1252
- mcrLongAltComp?.$refs['uikit-question-summary__summary-dropdown-explanation-img-description'] as HTMLElement | undefined
1253
-
1254
- const choiceContainerComp =
1255
- this.$refs['uikit-question__choices-container'] as ComponentPublicInstance | undefined
1256
- if (choiceContainerComp) {
1257
- const dropdownExplanationComp =
1258
- // eslint-disable-next-line max-len
1259
- choiceContainerComp.$refs['uikit-question-choices-container__dropdown-explanation'] as ComponentPublicInstance[]
1260
- if (dropdownExplanationComp) {
1261
- const dropdownEl = dropdownExplanationComp[0] as ComponentPublicInstance | undefined
1262
- const mobileImgDropdownImgDescription =
1263
- // eslint-disable-next-line max-len
1264
- dropdownEl?.$refs['uikit-question-dropdown-explanation__dropdown-explanation-img-description'] as HTMLElement | undefined
1265
-
1266
- if (mobileImgDropdownImgDescription?.offsetParent) {
1267
- mobileImgDropdownImgDescription.focus()
1268
- }
1269
- }
1270
- }
1053
+ const togglePassageImageLongAlt = () => {
1054
+ showPassageImageLongAlt.value = !showPassageImageLongAlt.value
1271
1055
 
1272
- const explanationComp =
1273
- this.$refs['uikit-question__explanation'] as ComponentPublicInstance | undefined
1274
- const longAlt = explanationComp?.$refs['uikit-question-explanation__explanation-img-description'] as Ref
1056
+ if (showPassageImageLongAlt.value) {
1057
+ const mobileImgDropdownImgDescriptionEl = passageImageDropdownRef?.value?.mobileImgDropdownImgDescriptionEl
1058
+ const longAltEl = passageAndImageRef?.value?.longAltEl
1275
1059
 
1276
- // Checking offsetParent tells us which element is visible
1277
- if (mcrLongAlt?.offsetParent) {
1278
- mcrLongAlt.focus()
1279
- } else if (longAlt?.offsetParent) {
1280
- longAlt.focus()
1281
- }
1282
- }, 0)
1060
+ // Checking offsetParent tells us which element is visible
1061
+ if (mobileImgDropdownImgDescriptionEl?.offsetParent) {
1062
+ mobileImgDropdownImgDescriptionEl.focus()
1063
+ } else if (longAltEl?.offsetParent) {
1064
+ longAltEl.focus()
1283
1065
  }
1284
1066
  }
1067
+ }
1285
1068
 
1286
- toggleExplanation () {
1287
- this.showExplanation = !this.showExplanation
1288
- if (!this.showExplanation) {
1289
- setTimeout(() => {
1290
- const summaryMCRComp =
1291
- this.$refs['uikit-question__summary'] as ComponentPublicInstance | undefined
1292
- const summaryMCRExplanation =
1293
- // eslint-disable-next-line max-len
1294
- summaryMCRComp?.$refs['uikit-question-summary__summary-toggle-explanation-text'] as HTMLElement | undefined
1295
-
1296
- const choiceContainerComp =
1297
- this.$refs['uikit-question__choices-container'] as ComponentPublicInstance | undefined
1298
-
1299
- const showExplanationRef = (
1300
- choiceContainerComp?.$refs['uikit-question-choices-container__toggle-explanation-text']
1301
- ) as Element[] | Element | undefined
1302
-
1303
- if (summaryMCRExplanation) {
1304
- summaryMCRExplanation?.focus()
1305
- } else if (showExplanationRef) {
1306
- const showExplanationEl = (
1307
- showExplanationRef instanceof Array
1308
- ? showExplanationRef[0]
1309
- : showExplanationRef
1310
- ) as HTMLElement | undefined
1311
- showExplanationEl?.focus()
1312
- }
1313
- }, 0)
1314
- } else {
1315
- setTimeout(() => {
1316
- const explanationComp =
1317
- this.$refs['uikit-question__explanation'] as ComponentPublicInstance | undefined
1318
- const explanationTitle = explanationComp?.$refs['explanation'] as HTMLElement | undefined
1319
- if (explanationTitle) {
1320
- explanationTitle?.focus()
1321
- }
1322
- }, 0)
1069
+ const toggleExplanationImageLongAlt = () => {
1070
+ showExplanationImageLongAlt.value = !showExplanationImageLongAlt.value
1071
+ if (showExplanationImageLongAlt.value) {
1072
+ // checks for mobile / tablet portrait uikit-question-summary
1073
+ const mcrLongAltEl = questionSummaryRef?.value?.mcrLongAltEl
1074
+ const mobileImgDropdownImgDescriptionEl = choicesContainerRef?.value?.mobileImgDropdownImgDescriptionEl
1075
+ if (mobileImgDropdownImgDescriptionEl?.offsetParent) {
1076
+ mobileImgDropdownImgDescriptionEl.focus()
1323
1077
  }
1324
- }
1078
+ const longAltEl = questionExplanationRef?.value?.longAltEl
1325
1079
 
1326
- clickCheckAnswer () {
1327
- if (!this.hideAnswer) {
1328
- this.showAnswers = true
1329
- this.emitCheckAnswer({
1330
- isCorrect: this.isCorrect,
1331
- selectedChoices: this.selectedChoices,
1332
- questionSerial: this.question.serial,
1333
- })
1334
- setTimeout(() => {
1335
- const summaryMCRComp =
1336
- this.$refs['uikit-question__summary'] as ComponentPublicInstance | undefined
1337
- const summaryMCRExplanation =
1338
- // eslint-disable-next-line max-len
1339
- summaryMCRComp?.$refs['uikit-question-summary__summary-toggle-explanation-text'] as HTMLElement | undefined
1340
-
1341
- const choiceContainerComp =
1342
- this.$refs['uikit-question__choices-container'] as ComponentPublicInstance | undefined
1343
-
1344
- const showExplanationRef = (
1345
- choiceContainerComp?.$refs['uikit-question-choices-container__toggle-explanation-text']
1346
- ) as Element[] | Element | undefined
1347
-
1348
- if (summaryMCRExplanation) {
1349
- summaryMCRExplanation?.focus()
1350
- } else if (showExplanationRef) {
1351
- const showExplanationEl = (
1352
- showExplanationRef instanceof Array
1353
- ? showExplanationRef[0]
1354
- : showExplanationRef
1355
- ) as HTMLElement | undefined
1356
- showExplanationEl?.focus()
1357
- }
1358
- }, 500)
1080
+ // Checking offsetParent tells us which element is visible
1081
+ if (mcrLongAltEl?.offsetParent) {
1082
+ mcrLongAltEl.focus()
1083
+ } else if (longAltEl?.offsetParent) {
1084
+ longAltEl.focus()
1359
1085
  }
1360
1086
  }
1087
+ }
1361
1088
 
1362
- clickCheckMatrixAnswer () {
1363
- if (!this.hideAnswer) {
1364
- this.showMatrixAnswers = true
1365
-
1366
- this.emitCheckAnswer({
1367
- isCorrect: this.isMatrixQuestionCorrect,
1368
- selectedChoices: this.selectedMatrixChoices,
1369
- questionSerial: this.question.serial,
1370
- })
1371
-
1372
- setTimeout(() => {
1373
- const summaryMatrixComp =
1374
- this.$refs['uikit-question__summary'] as ComponentPublicInstance | undefined
1375
- const summaryMatrixExplanation =
1376
- // eslint-disable-next-line max-len
1377
- summaryMatrixComp?.$refs['uikit-question-summary__summary-toggle-explanation-text'] as HTMLElement | undefined
1378
- if (summaryMatrixExplanation) {
1379
- summaryMatrixExplanation?.focus()
1380
- }
1381
- }, 500)
1089
+ const toggleExplanation = () => {
1090
+ showExplanation.value = !showExplanation.value
1091
+ if (!showExplanation.value) {
1092
+ const summaryMCRExplanationEl = questionSummaryRef?.value?.summaryMCRExplanationEl
1093
+ const showExplanationEls = choicesContainerRef?.value?.showExplanationEls
1094
+
1095
+ if (summaryMCRExplanationEl) {
1096
+ summaryMCRExplanationEl?.focus()
1097
+ } else if (showExplanationEls) {
1098
+ const showExplanationEl = (
1099
+ showExplanationEls instanceof Array
1100
+ ? showExplanationEls[0]
1101
+ : showExplanationEls
1102
+ ) as HTMLElement | undefined
1103
+ showExplanationEl?.focus()
1104
+ }
1105
+ } else {
1106
+ const explanationTitleEl = questionExplanationRef?.value?.explanationTitleEl
1107
+ if (explanationTitleEl) {
1108
+ explanationTitleEl?.focus()
1382
1109
  }
1383
1110
  }
1111
+ }
1384
1112
 
1385
- mappedNameRows (choiceKey: string) {
1386
- /*
1387
- There is a specific order in which we have to fill the rows in the table for visible names.
1388
- Here are the rules for filling the names in:
1389
-
1390
- 1. The first two names go in the first column
1391
- 2. The third and fourth names go in the second column
1392
- 3. Any subsequent names fill out the first two rows
1393
- 4. Any additional names after those fill out the last row
1394
- 5. Alphabetical by col
1395
- 6. Names truncate when they don't fit the width of the table cell
1396
- */
1397
- const rows: TNamesRow[] = []
1398
- const students = this.showNames && this.showNames[choiceKey] || []
1399
- if (students.length) {
1400
- // Row length is dynamic depending on how many students we have
1401
- const rowLength = students.length < 3 ? 1 :
1402
- students.length >= 3 && students.length < 5 ? 2 :
1403
- 3
1404
- for (let i = 0; i < students.length; i += rowLength) {
1405
- const row = students.slice(i, i + rowLength).reduce((acc, student) => {
1406
- if (!acc.id) {
1407
- // Need a unique ID for the row
1408
- acc.id = student.id
1409
- }
1410
- if (!acc.nameOne && !acc.isFlaggedByNameOne) {
1411
- // Does our row have a nameOne filled in?
1412
- acc.nameOne = student.name
1413
- acc.isFlaggedByNameOne = student.isFlaggedByStudent
1414
- } else if (!acc.nameTwo && !acc.isFlaggedByNameTwo) {
1415
- // Does our row have a nameTwo filled in?
1416
- acc.nameTwo = student.name
1417
- acc.isFlaggedByNameTwo = student.isFlaggedByStudent
1418
- } else if (!acc.nameThree && !acc.isFlaggedByNameThree) {
1419
- // Does our row have a nameThree filled in?
1420
- acc.nameThree = student.name
1421
- acc.isFlaggedByNameThree = student.isFlaggedByStudent
1422
- }
1423
- return acc
1424
- }, {} as TNamesRow)
1425
- rows.push(row)
1426
- }
1113
+ const clickCheckAnswer = () => {
1114
+ if (!props.hideAnswer) {
1115
+ showAnswers.value = true
1116
+ emitCheckAnswer({
1117
+ isCorrect: isCorrect.value,
1118
+ selectedChoices: selectedChoices.value,
1119
+ questionSerial: props.question.serial,
1120
+ })
1121
+ const summaryMCRExplanation = questionSummaryRef?.value?.summaryMCRExplanationEl
1122
+ const showExplanationEls = choicesContainerRef?.value?.showExplanationEls
1123
+
1124
+ if (summaryMCRExplanation) {
1125
+ summaryMCRExplanation?.focus()
1126
+ } else if (showExplanationEls) {
1127
+ const showExplanationEl = (
1128
+ showExplanationEls instanceof Array
1129
+ ? showExplanationEls[0]
1130
+ : showExplanationEls
1131
+ ) as HTMLElement | undefined
1132
+ showExplanationEl?.focus()
1427
1133
  }
1428
- return rows
1429
1134
  }
1135
+ }
1430
1136
 
1431
- @Watch('reviewMode')
1432
- reviewModeChanged (reviewMode: boolean) {
1433
- if (reviewMode) {
1434
- this.startReviewMode()
1435
- } else {
1436
- this.stopReviewMode()
1137
+ const clickCheckMatrixAnswer = () => {
1138
+ if (!props.hideAnswer) {
1139
+ showMatrixAnswers.value = true
1140
+
1141
+ emitCheckAnswer({
1142
+ isCorrect: isMatrixQuestionCorrect.value,
1143
+ selectedChoices: selectedMatrixChoices.value,
1144
+ questionSerial: props.question.serial,
1145
+ })
1146
+
1147
+ const summaryMatrixExplanationEl = questionSummaryRef?.value?.summaryMatrixExplanationEl
1148
+ if (summaryMatrixExplanationEl) {
1149
+ summaryMatrixExplanationEl?.focus()
1437
1150
  }
1438
1151
  }
1152
+ }
1439
1153
 
1440
- @Watch('previousChoices', { deep: true })
1441
- previousChoicesChanged (choices: TChoiceKey[]) {
1442
- this.updateSelectedChoices(choices)
1154
+ watch(reviewMode, () => {
1155
+ if (reviewMode.value) {
1156
+ startReviewMode()
1157
+ } else {
1158
+ stopReviewMode()
1443
1159
  }
1160
+ })
1444
1161
 
1445
- @Watch('previousMatrixChoices', { deep: true })
1446
- previousMatrixChoicesChanged (matrixChoice: TMatrixChoiceKey[]) {
1447
- this.updateSelectedMatrixChoices(matrixChoice)
1448
- }
1162
+ watch(() => props.previousChoices, (previousChoices: TChoiceKey[] | undefined | null) => {
1163
+ if (previousChoices) {
1164
+ updateSelectedChoices(previousChoices)
1165
+ }
1166
+ }, { deep: true })
1167
+
1168
+ watch(() => props.previousMatrixChoices, (previousMatrixChoices: TMatrixChoiceKey[] | undefined | null) => {
1169
+ if (previousMatrixChoices) {
1170
+ updateSelectedMatrixChoices(previousMatrixChoices)
1171
+ }
1172
+ }, { deep: true })
1173
+
1174
+ watch(selectedChoices, () => {
1175
+ emitSelectedChoices({
1176
+ isCorrect: selectedChoices.value.length === answerKeys.value.length
1177
+ && !selectedChoices.value.join(' ').includes('d'),
1178
+ selectedChoices: selectedChoices.value,
1179
+ questionSerial: props.question.serial,
1180
+ } as Study.Cloud.IQuizAnswer)
1181
+ }, { deep: true })
1182
+
1183
+ watch(selectedMatrixChoices, () => {
1184
+ emitSelectedChoices({
1185
+ isCorrect: selectedMatrixChoices.value.length === matrixAnswerKeys.value.length
1186
+ && !selectedMatrixChoices.value.join(' ').includes('d'),
1187
+ selectedChoices: selectedMatrixChoices.value,
1188
+ questionSerial: props.question.serial,
1189
+ } as Study.Cloud.IQuizAnswer)
1190
+ }, { deep: true })
1191
+
1192
+ watch(showExplanation, () => {
1193
+ emitUpdateShowExplanation()
1194
+ })
1449
1195
 
1450
- @Emit('selectedChoices')
1451
- emitSelectedChoices (selectedChoices: Study.Cloud.IQuizAnswer) {
1452
- return selectedChoices
1453
- }
1196
+ const emitSelectedChoices = (selectedChoicesToEmit: Study.Cloud.IQuizAnswer) => {
1197
+ emit('selectedChoices', selectedChoicesToEmit)
1198
+ }
1454
1199
 
1455
- @Watch('selectedChoices', { deep: true })
1456
- selectedChoicesChanged () {
1457
- this.emitSelectedChoices({
1458
- isCorrect: this.selectedChoices.length === this.answerKeys.length
1459
- && !this.selectedChoices.join(' ').includes('d'),
1460
- selectedChoices: this.selectedChoices,
1461
- questionSerial: this.question.serial,
1462
- } as Study.Cloud.IQuizAnswer)
1463
- }
1200
+ const emitClose = () => {
1201
+ emit('close')
1202
+ }
1464
1203
 
1465
- @Watch('selectedMatrixChoices', { deep: true })
1466
- selectedMatrixChoicesChanged () {
1467
- this.emitSelectedChoices({
1468
- isCorrect: this.selectedMatrixChoices.length === this.matrixAnswerKeys.length
1469
- && !this.selectedMatrixChoices.join(' ').includes('d'),
1470
- selectedChoices: this.selectedMatrixChoices,
1471
- questionSerial: this.question.serial,
1472
- } as Study.Cloud.IQuizAnswer)
1473
- }
1204
+ const emitUpgrade = () => {
1205
+ emit('upgrade')
1206
+ }
1474
1207
 
1475
- @Watch('showExplanation')
1476
- showExplanationChanged () {
1477
- this.emitUpdateShowExplanation()
1478
- }
1208
+ const emitSubmitQuiz = () => {
1209
+ emit('submitQuiz')
1210
+ }
1479
1211
 
1480
- @Emit('close')
1481
- emitClose () {
1482
- return true
1483
- }
1212
+ const emitNextQuestion = () => {
1213
+ emit('nextQuestion')
1214
+ }
1484
1215
 
1485
- @Emit('upgrade')
1486
- emitUpgrade () {
1487
- return true
1488
- }
1216
+ const emitPreviousQuestion = () => {
1217
+ emit('previousQuestion')
1218
+ }
1489
1219
 
1490
- @Emit('submitQuiz')
1491
- emitSubmitQuiz () {
1492
- return true
1493
- }
1220
+ const emitCheckAnswer = (answer: Study.Cloud.IQuizAnswer) => {
1221
+ emit('checkAnswer', answer)
1222
+ }
1494
1223
 
1495
- @Emit('nextQuestion')
1496
- emitNextQuestion () {
1497
- return true
1224
+ const emitUpdateShowExplanation = () => {
1225
+ emit('update:showExplanation', showExplanation.value)
1226
+ }
1227
+
1228
+ if (props.reviewMode) {
1229
+ startReviewMode()
1230
+ }
1231
+
1232
+ if (!isMatrixQuestion.value && props.previousChoices) {
1233
+ updateSelectedChoices(props.previousChoices)
1234
+ }
1235
+
1236
+ if (isMatrixQuestion.value && props.previousMatrixChoices) {
1237
+ updateSelectedMatrixChoices(props.previousMatrixChoices)
1238
+ }
1239
+
1240
+ onMounted(() => {
1241
+ questionEl.value = props.containerEl || questionRef.value
1242
+
1243
+ if (props.initialShowAnswers) {
1244
+ showAnswers.value = props.initialShowAnswers
1245
+ showMatrixAnswers.value = props.initialShowAnswers
1498
1246
  }
1499
1247
 
1500
- @Emit('previousQuestion')
1501
- emitPreviousQuestion () {
1502
- return true
1248
+ if (props.allowKeyboardShortcuts) {
1249
+ window.addEventListener('keydown', keydownListener)
1503
1250
  }
1504
1251
 
1505
- @Emit('checkAnswer')
1506
- emitCheckAnswer (answer: Study.Cloud.IQuizAnswer) {
1507
- return answer
1252
+ const promptElements = promptEl?.value?.querySelectorAll('p')
1253
+ if (promptElements?.length) {
1254
+ promptElements.forEach(el => el.setAttribute('tabindex', '0'))
1508
1255
  }
1509
1256
 
1510
- @Emit('update:showExplanation')
1511
- emitUpdateShowExplanation () {
1512
- return this.showExplanation
1257
+ if (props.autoFocusPrompt) {
1258
+ moveFocusToPrompt()
1259
+ } else if (props.autoFocusPrompt === false) {
1260
+ const contextEl = questionContextRef?.value?.contextEl
1261
+ contextEl?.focus()
1513
1262
  }
1514
- }
1263
+ })
1264
+
1265
+ onBeforeUnmount(() => {
1266
+ window.removeEventListener('keydown', keydownListener)
1267
+ })
1268
+
1269
+ // Provide question context once, instead of passing as props to every child
1270
+ provide(InjectionKeys.questionKey, question)
1271
+ provide(InjectionKeys.choicesKey, choices)
1272
+ provide(InjectionKeys.questionElKey, questionEl)
1273
+ provide(InjectionKeys.breakpointsWithElKey, breakpointsWithEl)
1274
+ provide(InjectionKeys.quizLengthKey, quizLength)
1275
+ provide(InjectionKeys.quizModeKey, quizMode)
1276
+ provide(InjectionKeys.questionNumberKey, questionNumber)
1277
+ provide(InjectionKeys.isDarkModeKey, isDarkMode)
1278
+ provide(InjectionKeys.isCorrectKey, isCorrect)
1279
+ provide(InjectionKeys.contextIconTypeKey, contextIconType)
1280
+ provide(InjectionKeys.showAnswersKey, showAnswers)
1281
+ provide(InjectionKeys.showMatrixAnswersKey, showMatrixAnswers)
1282
+ provide(InjectionKeys.imageUrlPrefixKey, imageUrlPrefix)
1283
+ provide(InjectionKeys.passageImageUrlKey, passageImageUrl)
1284
+ provide(InjectionKeys.passageImageAltKey, passageImageAlt)
1285
+ provide(InjectionKeys.passageImageLongAltKey, passageImageLongAlt)
1286
+ provide(InjectionKeys.showPassageImageLongAltKey, showPassageImageLongAlt)
1287
+ provide(InjectionKeys.passageAndImageTitleKey, passageAndImageTitle)
1288
+ provide(InjectionKeys.showExplanationKey, showExplanation)
1289
+ provide(InjectionKeys.isMCRKey, isMCR)
1290
+ provide(InjectionKeys.isUnansweredKey, isUnanswered)
1291
+ provide(InjectionKeys.answerKeysKey, answerKeys)
1292
+ provide(InjectionKeys.hoverChoiceKeyKey, hoverChoiceKey)
1293
+ provide(InjectionKeys.focusChoiceKeyKey, focusChoiceKey)
1294
+ provide(InjectionKeys.selectedChoicesKey, selectedChoices)
1295
+ provide(InjectionKeys.distractorKeysKey, distractorKeys)
1296
+ provide(InjectionKeys.choiceStrikesKey, choiceStrikes)
1297
+ provide(InjectionKeys.choiceScoresKey, choiceScores)
1298
+ provide(InjectionKeys.globalMetricsKey, globalMetrics)
1299
+ provide(InjectionKeys.reviewModeKey, reviewMode)
1300
+ provide(InjectionKeys.showExplanationImageLongAltKey, showExplanationImageLongAlt)
1301
+ provide(InjectionKeys.explanationImageUrlKey, explanationImageUrl)
1302
+ provide(InjectionKeys.explanationImageAltKey, explanationImageAlt)
1303
+ provide(InjectionKeys.explanationImageLongAltKey, explanationImageLongAlt)
1304
+ provide(InjectionKeys.referenceKey, reference)
1305
+ provide(InjectionKeys.hideReferencesKey, hideReferences)
1306
+ provide(InjectionKeys.keywordDefinitionsKey, keywordDefinitions)
1307
+ provide(InjectionKeys.showPaywallKey, showPaywall)
1308
+ provide(InjectionKeys.showPassageAndImageKey, showPassageAndImage)
1309
+ provide(InjectionKeys.isMatrixQuestionKey, isMatrixQuestion)
1310
+ provide(InjectionKeys.matrixChoiceScoresKey, matrixChoiceScores)
1311
+ provide(InjectionKeys.isMatrixQuestionCorrectKey, isMatrixQuestionCorrect)
1312
+ provide(InjectionKeys.matrixAnswerKeysKey, matrixAnswerKeys)
1313
+ provide(InjectionKeys.selectedMatrixChoicesKey, selectedMatrixChoices)
1314
+ provide(InjectionKeys.isTeachGroupReviewKey, isTeachGroupReview)
1515
1315
  </script>
1516
1316
 
1517
1317
  <style lang="scss">
1518
- @import '../../styles/colors';
1519
- @import '../../styles/breakpoints';
1318
+ @use '@/styles/breakpoints' as *;
1319
+ @use '@/styles/colors' as *;
1520
1320
 
1521
1321
  .uikit-question {
1522
1322
  position: relative;
@@ -2012,6 +1812,7 @@ export default class Question extends Vue {
2012
1812
 
2013
1813
  &__matrix-choices-container {
2014
1814
  display: block;
1815
+
2015
1816
  @include breakpoint(black-bear) {
2016
1817
  display: none;
2017
1818
  }