@pocketprep/ui-kit 3.5.30 → 3.7.0
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 +12594 -11931
- package/dist/@pocketprep/ui-kit.js.map +1 -1
- package/dist/@pocketprep/ui-kit.umd.cjs +12 -11
- package/dist/@pocketprep/ui-kit.umd.cjs.map +1 -1
- package/lib/components/Forms/Radio.vue +1 -8
- package/lib/components/Quiz/Question/BuildListChoicesContainer.vue +714 -0
- package/lib/components/Quiz/Question/ChoicesContainer.vue +1 -9
- package/lib/components/Quiz/Question/MPMCChoicesContainer.vue +472 -0
- package/lib/components/Quiz/Question/MPMCRadioGroup.vue +169 -0
- package/lib/components/Quiz/Question/MobileMatrixChoicesContainer.vue +1 -9
- package/lib/components/Quiz/Question/MobileMatrixRadioGroup.vue +1 -9
- package/lib/components/Quiz/Question/StatsSummary.vue +22 -6
- package/lib/components/Quiz/Question/Summary.vue +31 -5
- package/lib/components/Quiz/Question/composables.ts +18 -1
- package/lib/components/Quiz/Question/injectionSymbols.ts +15 -0
- package/lib/components/Quiz/Question.vue +394 -24
- package/lib/components/Quiz/question.d.ts +7 -0
- package/lib/utils.ts +8 -0
- package/package.json +2 -2
|
@@ -277,6 +277,7 @@ import PocketButton from '../../Buttons/Button.vue'
|
|
|
277
277
|
import { dark as vDark, breakpoint as vBreakpoint } from '../../../directives'
|
|
278
278
|
import { useQuestionContext } from './composables'
|
|
279
279
|
import type { TChoiceKey } from '../question'
|
|
280
|
+
import { stripHtmlTags } from '../../../utils'
|
|
280
281
|
|
|
281
282
|
const emit = defineEmits<{
|
|
282
283
|
'emitChoiceFocusIn': [ choiceKey: TChoiceKey ]
|
|
@@ -318,15 +319,6 @@ const {
|
|
|
318
319
|
breakpointsWithEl,
|
|
319
320
|
} = useQuestionContext()
|
|
320
321
|
|
|
321
|
-
const stripHtmlTags = (string?: string) => {
|
|
322
|
-
if (string) {
|
|
323
|
-
const div = document.createElement('div')
|
|
324
|
-
div.innerHTML = string
|
|
325
|
-
return div.textContent || ''
|
|
326
|
-
}
|
|
327
|
-
return ''
|
|
328
|
-
}
|
|
329
|
-
|
|
330
322
|
const emitChoiceFocusIn = (choiceKey: TChoiceKey) => {
|
|
331
323
|
emit('emitChoiceFocusIn', choiceKey)
|
|
332
324
|
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import { dark as vDark } from '../../../directives'
|
|
4
|
+
import type { TChoiceKey } from './../question'
|
|
5
|
+
import { useQuestionContext } from './composables'
|
|
6
|
+
import { stripHtmlTags } from '../../../utils'
|
|
7
|
+
import Icon from '../../Icons/Icon.vue'
|
|
8
|
+
import MPMCRadioGroup from './MPMCRadioGroup.vue'
|
|
9
|
+
|
|
10
|
+
interface IMPMCRadioOptions {
|
|
11
|
+
choices: TChoiceKey[]
|
|
12
|
+
value: TChoiceKey | null
|
|
13
|
+
label?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits<{
|
|
17
|
+
'emitSelectedMPMCChoice': [mpmcChoiceKeys: TChoiceKey[]]
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
// questionEl is used by the breakpoint directive
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
|
+
questionEl,
|
|
24
|
+
question,
|
|
25
|
+
isMPMCQuestionCorrect,
|
|
26
|
+
reviewMode,
|
|
27
|
+
isDarkMode,
|
|
28
|
+
showMPMCAnswers,
|
|
29
|
+
selectedMPMCChoices,
|
|
30
|
+
} = useQuestionContext()
|
|
31
|
+
|
|
32
|
+
const mpmcRadioGrid = ref<IMPMCRadioOptions[] | undefined>(undefined)
|
|
33
|
+
const expandedPartNumbers = ref<number[]>([])
|
|
34
|
+
|
|
35
|
+
const mpmcLabels = computed(() => {
|
|
36
|
+
return question.value.mpmcLabels || []
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const questionChoices = computed(() => {
|
|
40
|
+
const choices = question.value.choices
|
|
41
|
+
|
|
42
|
+
// Shuffle mpmc choices
|
|
43
|
+
return [ ...choices ].sort(() => Math.random() - 0.5)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const choiceKeysByLabelIndexObj = computed(() => {
|
|
47
|
+
// Create an object with the keys being the mpmc label indexes
|
|
48
|
+
// and the values being an array of corresponding choiceKeys to the mpmc label
|
|
49
|
+
const groupedChoiceKeyByLabelIndex: { [key: number]: TChoiceKey[] } = {}
|
|
50
|
+
|
|
51
|
+
questionChoices.value?.forEach(choice => {
|
|
52
|
+
const labelIndex = choice.labelIndex
|
|
53
|
+
|
|
54
|
+
if (labelIndex !== undefined) {
|
|
55
|
+
if (!groupedChoiceKeyByLabelIndex[labelIndex]) {
|
|
56
|
+
groupedChoiceKeyByLabelIndex[labelIndex] = []
|
|
57
|
+
}
|
|
58
|
+
groupedChoiceKeyByLabelIndex[labelIndex].push(choice.id as TChoiceKey)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return groupedChoiceKeyByLabelIndex
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const choiceTextsByLabelIndex = computed(() => {
|
|
66
|
+
const grouped: { [key: number]: string[] } = {}
|
|
67
|
+
|
|
68
|
+
questionChoices.value?.forEach(choice => {
|
|
69
|
+
const labelIndex = choice.labelIndex
|
|
70
|
+
|
|
71
|
+
if (labelIndex !== undefined) {
|
|
72
|
+
if (!grouped[labelIndex]) {
|
|
73
|
+
grouped[labelIndex] = []
|
|
74
|
+
}
|
|
75
|
+
grouped[labelIndex].push(choice.text || '')
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return grouped
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const selectedChoiceTextsByLabelIndex = computed(() => {
|
|
83
|
+
const selectedTexts: { [key: number]: string } = {}
|
|
84
|
+
|
|
85
|
+
mpmcRadioGrid.value?.forEach((part, labelIndex) => {
|
|
86
|
+
if (part.value) {
|
|
87
|
+
const choiceIndex = choiceKeysByLabelIndexObj.value[labelIndex]?.findIndex(key => key === part.value)
|
|
88
|
+
if (choiceIndex !== undefined && choiceIndex !== -1) {
|
|
89
|
+
const choiceText = choiceTextsByLabelIndex.value[labelIndex]?.[choiceIndex]
|
|
90
|
+
if (choiceText) {
|
|
91
|
+
selectedTexts[labelIndex] = stripHtmlTags(choiceText)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return selectedTexts
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const defaultRadioButtonGrid = computed(() => {
|
|
101
|
+
return mpmcLabels.value.map((_label, labelIndex) => {
|
|
102
|
+
const choices = choiceKeysByLabelIndexObj.value[labelIndex] || []
|
|
103
|
+
return {
|
|
104
|
+
choices: choices,
|
|
105
|
+
value: null as TChoiceKey | null,
|
|
106
|
+
} as IMPMCRadioOptions
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
onMounted(() => {
|
|
111
|
+
mpmcRadioGrid.value = convertSelectedMPMCChoiceToRadioBtnGrid()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const toggleChoiceDropdown = (labelIndex: number) => {
|
|
115
|
+
const includedPartNumberIndex = expandedPartNumbers.value.findIndex(part => part === labelIndex)
|
|
116
|
+
if (includedPartNumberIndex === -1) {
|
|
117
|
+
expandedPartNumbers.value.push(labelIndex)
|
|
118
|
+
} else {
|
|
119
|
+
expandedPartNumbers.value.splice(includedPartNumberIndex, 1)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const openChoiceDropdown = (labelIndex: number) => {
|
|
124
|
+
const includedPartNumberIndex = expandedPartNumbers.value.findIndex(part => part === labelIndex)
|
|
125
|
+
if (includedPartNumberIndex === -1) {
|
|
126
|
+
expandedPartNumbers.value.push(labelIndex)
|
|
127
|
+
} else {
|
|
128
|
+
expandedPartNumbers.value.splice(includedPartNumberIndex, 1)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const getRadioGroupChoiceKeySelection = (labelIndex: number) => {
|
|
133
|
+
return mpmcRadioGrid.value?.[labelIndex]?.value
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const getRadioGroupChoiceKeys = (labelIndex: number) => {
|
|
137
|
+
const choices = mpmcRadioGrid.value?.[labelIndex]?.choices
|
|
138
|
+
return choices
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const getRadioGroupChoicesText = (labelIndex: number) => {
|
|
142
|
+
return choiceTextsByLabelIndex.value[labelIndex] || []
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const updateRadioPartSelection = (labelIndex: number, choiceKey: TChoiceKey | null) => {
|
|
146
|
+
const part = mpmcRadioGrid.value?.[labelIndex]
|
|
147
|
+
if (part) {
|
|
148
|
+
part.value = choiceKey
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const correctPart = (labelIndex: number) => {
|
|
153
|
+
if (mpmcRadioGrid.value?.[labelIndex]) {
|
|
154
|
+
const partVal = mpmcRadioGrid.value[labelIndex]?.value
|
|
155
|
+
if (partVal) {
|
|
156
|
+
return partVal.startsWith('a')
|
|
157
|
+
}
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const convertSelectedMPMCChoiceToRadioBtnGrid = () => {
|
|
164
|
+
const radioBtnGrid = defaultRadioButtonGrid.value
|
|
165
|
+
|
|
166
|
+
selectedMPMCChoices.value.forEach(choice => {
|
|
167
|
+
const choiceObj = questionChoices.value?.find(c => c.id === choice)
|
|
168
|
+
if (choiceObj?.labelIndex !== undefined) {
|
|
169
|
+
const labelIndex = choiceObj.labelIndex
|
|
170
|
+
const radioBtnGridPart = radioBtnGrid?.[labelIndex]
|
|
171
|
+
if (radioBtnGridPart) {
|
|
172
|
+
radioBtnGridPart.value = choice
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return radioBtnGrid
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const emitSelectedMPMCChoice = (mpmcChoiceKeys: TChoiceKey[]) => {
|
|
181
|
+
emit('emitSelectedMPMCChoice', mpmcChoiceKeys)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
watch(mpmcRadioGrid, () => {
|
|
185
|
+
if (mpmcRadioGrid.value && (!reviewMode.value || !showMPMCAnswers.value)) {
|
|
186
|
+
const selectedRadioButtonChoices: TChoiceKey[] = []
|
|
187
|
+
|
|
188
|
+
mpmcRadioGrid.value.forEach((part) => {
|
|
189
|
+
if (part.value) {
|
|
190
|
+
selectedRadioButtonChoices.push(part.value)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
emitSelectedMPMCChoice(selectedRadioButtonChoices)
|
|
195
|
+
}
|
|
196
|
+
}, { deep: true })
|
|
197
|
+
|
|
198
|
+
watch(showMPMCAnswers, () => {
|
|
199
|
+
if (showMPMCAnswers) {
|
|
200
|
+
expandedPartNumbers.value = []
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
watch(selectedMPMCChoices, () => {
|
|
205
|
+
if ((reviewMode.value || showMPMCAnswers.value)) {
|
|
206
|
+
const selectedRadioBtnGrid = convertSelectedMPMCChoiceToRadioBtnGrid()
|
|
207
|
+
mpmcRadioGrid.value = selectedRadioBtnGrid
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<template>
|
|
214
|
+
<div
|
|
215
|
+
class="uikit-question-mpmc-choices-container"
|
|
216
|
+
>
|
|
217
|
+
<div
|
|
218
|
+
class="uikit-question-mpmc-choices-container__choices-container"
|
|
219
|
+
:class="{
|
|
220
|
+
'uikit-question-mpmc-choices-container__choices-container--correct':
|
|
221
|
+
(showMPMCAnswers || reviewMode) && isMPMCQuestionCorrect,
|
|
222
|
+
'uikit-question-mpmc-choices-container__choices-container--incorrect':
|
|
223
|
+
(showMPMCAnswers || reviewMode) && !isMPMCQuestionCorrect,
|
|
224
|
+
}"
|
|
225
|
+
v-dark="isDarkMode"
|
|
226
|
+
>
|
|
227
|
+
<div
|
|
228
|
+
v-for="(label, labelIndex) in mpmcLabels"
|
|
229
|
+
class="uikit-question-mpmc-choices-container__part-container"
|
|
230
|
+
:key="labelIndex"
|
|
231
|
+
v-dark="isDarkMode"
|
|
232
|
+
:tabindex="0"
|
|
233
|
+
role="button"
|
|
234
|
+
:aria-expanded="expandedPartNumbers.includes(labelIndex)"
|
|
235
|
+
:aria-label="
|
|
236
|
+
`${stripHtmlTags(label)}. ${expandedPartNumbers.includes(labelIndex) ? 'Expanded' : 'Collapsed'}.`
|
|
237
|
+
"
|
|
238
|
+
@keydown.enter="openChoiceDropdown(labelIndex)"
|
|
239
|
+
@keydown.space.prevent="openChoiceDropdown(labelIndex)"
|
|
240
|
+
>
|
|
241
|
+
<div
|
|
242
|
+
class="uikit-question-mpmc-choices-container__part"
|
|
243
|
+
:class="{
|
|
244
|
+
'uikit-question-mpmc-choices-container__part--expanded':
|
|
245
|
+
expandedPartNumbers.includes(labelIndex),
|
|
246
|
+
}"
|
|
247
|
+
v-dark="isDarkMode"
|
|
248
|
+
@click.stop="toggleChoiceDropdown(labelIndex)"
|
|
249
|
+
>
|
|
250
|
+
<Icon
|
|
251
|
+
v-if="(showMPMCAnswers || reviewMode) && correctPart(labelIndex)"
|
|
252
|
+
v-dark="isDarkMode"
|
|
253
|
+
class="uikit-question-mpmc-choices-container__part-label-correct-icon"
|
|
254
|
+
type="check"
|
|
255
|
+
/>
|
|
256
|
+
<Icon
|
|
257
|
+
v-if="(showMPMCAnswers || reviewMode) && !correctPart(labelIndex)"
|
|
258
|
+
v-dark="isDarkMode"
|
|
259
|
+
class="uikit-question-mpmc-choices-container__part-label-incorrect-icon"
|
|
260
|
+
type="incorrect"
|
|
261
|
+
/>
|
|
262
|
+
<div
|
|
263
|
+
class="uikit-question-mpmc-choices-container__part-label"
|
|
264
|
+
:class="{
|
|
265
|
+
'uikit-question-mpmc-choices-container__part-label--review-mode':
|
|
266
|
+
showMPMCAnswers || reviewMode,
|
|
267
|
+
}"
|
|
268
|
+
v-dark="isDarkMode"
|
|
269
|
+
>
|
|
270
|
+
{{ stripHtmlTags(label) }}
|
|
271
|
+
<Icon
|
|
272
|
+
class="uikit-question-mpmc-choices-container__toggle-part-icon"
|
|
273
|
+
:class="{
|
|
274
|
+
'uikit-question-mpmc-choices-container__toggle-part-icon--up':
|
|
275
|
+
expandedPartNumbers.includes(labelIndex),
|
|
276
|
+
}"
|
|
277
|
+
v-dark="isDarkMode"
|
|
278
|
+
type="accordionArrow"
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
<div
|
|
282
|
+
v-if="selectedChoiceTextsByLabelIndex[labelIndex]"
|
|
283
|
+
class="uikit-question-mpmc-choices-container__selected-choice-text"
|
|
284
|
+
v-dark="isDarkMode"
|
|
285
|
+
>
|
|
286
|
+
{{ selectedChoiceTextsByLabelIndex[labelIndex] }}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
<div
|
|
290
|
+
v-if="expandedPartNumbers.includes(labelIndex)"
|
|
291
|
+
>
|
|
292
|
+
<MPMCRadioGroup
|
|
293
|
+
:modelValue="getRadioGroupChoiceKeySelection(labelIndex)"
|
|
294
|
+
:show-answers="showMPMCAnswers"
|
|
295
|
+
class="uikit-question-mpmc-choices-container__radio-btns"
|
|
296
|
+
:choices="getRadioGroupChoiceKeys(labelIndex)"
|
|
297
|
+
:choices-text="getRadioGroupChoicesText(labelIndex)"
|
|
298
|
+
:is-dark-mode="isDarkMode"
|
|
299
|
+
:disabled="false"
|
|
300
|
+
@update:modelValue="updateRadioPartSelection(labelIndex, $event)"
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
</template>
|
|
307
|
+
|
|
308
|
+
<style lang="scss">
|
|
309
|
+
@use '@/styles/breakpoints' as *;
|
|
310
|
+
@use '@/styles/colors' as *;
|
|
311
|
+
|
|
312
|
+
.uikit-question-mpmc-choices-container {
|
|
313
|
+
width: 100%;
|
|
314
|
+
max-width: 492px;
|
|
315
|
+
|
|
316
|
+
&__choices-container {
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
position: relative;
|
|
319
|
+
outline: none;
|
|
320
|
+
transition: 0.1s width ease;
|
|
321
|
+
|
|
322
|
+
&::after {
|
|
323
|
+
content: '';
|
|
324
|
+
position: absolute;
|
|
325
|
+
top: -8px;
|
|
326
|
+
bottom: -8px;
|
|
327
|
+
left: -8px;
|
|
328
|
+
right: -8px;
|
|
329
|
+
border-radius: 11px;
|
|
330
|
+
pointer-events: none;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
&--incorrect {
|
|
334
|
+
&::after {
|
|
335
|
+
display: block;
|
|
336
|
+
border: 2px solid $pepper;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
&--dark::after {
|
|
340
|
+
border-color: $rosa;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
&--correct {
|
|
345
|
+
&::after {
|
|
346
|
+
display: block;
|
|
347
|
+
border: 2px solid $cadaverous;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
&--dark::after {
|
|
351
|
+
border-color: $jungle-green;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
&__part-container {
|
|
357
|
+
margin-bottom: 16px;
|
|
358
|
+
border-radius: 5px;
|
|
359
|
+
background: $white;
|
|
360
|
+
box-shadow: 0 1px 4px 0 rgba($ash, 0.30);
|
|
361
|
+
outline: none;
|
|
362
|
+
transition: box-shadow 0.2s ease;
|
|
363
|
+
|
|
364
|
+
&:focus {
|
|
365
|
+
border: 1px solid $brand-blue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
&--dark {
|
|
370
|
+
border-radius: 5px;
|
|
371
|
+
background: $brand-black;
|
|
372
|
+
box-shadow: 0 1px 4px 0 $jet;
|
|
373
|
+
|
|
374
|
+
&:focus {
|
|
375
|
+
border: 1px solid $banana-bread;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
&__part {
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
padding: 12px 36px 12px 15px;
|
|
383
|
+
align-items: center;
|
|
384
|
+
align-content: flex-start;
|
|
385
|
+
flex-wrap: wrap;
|
|
386
|
+
border-radius: 5px;
|
|
387
|
+
border: 0.5px solid rgba($pewter, 0.85);
|
|
388
|
+
background: $white;
|
|
389
|
+
|
|
390
|
+
&--dark {
|
|
391
|
+
background: $brand-black;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
&--expanded {
|
|
395
|
+
border-radius: 5px 5px 0px 0px;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
&__part-label-correct-icon,
|
|
400
|
+
&__part-label-incorrect-icon {
|
|
401
|
+
position: absolute;
|
|
402
|
+
right: 0;
|
|
403
|
+
margin-right: 12px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
&__part-label-correct-icon {
|
|
407
|
+
color: $cadaverous;
|
|
408
|
+
width: 18px;
|
|
409
|
+
height: 18px;
|
|
410
|
+
|
|
411
|
+
&--dark {
|
|
412
|
+
color: $jungle-green;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
&__part-label-incorrect-icon {
|
|
417
|
+
color: $pepper;
|
|
418
|
+
|
|
419
|
+
&--dark {
|
|
420
|
+
color: $rosa;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
&__part-label {
|
|
425
|
+
color: $brand-black;
|
|
426
|
+
font-size: 16px;
|
|
427
|
+
font-weight: 600;
|
|
428
|
+
line-height: 23px;
|
|
429
|
+
letter-spacing: -0.1px;
|
|
430
|
+
|
|
431
|
+
&--dark {
|
|
432
|
+
color: $barely-background;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
&--review-mode {
|
|
436
|
+
width: 285px;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
&__selected-choice-text {
|
|
441
|
+
color: $brand-black;
|
|
442
|
+
font-size: 14px;
|
|
443
|
+
font-weight: 400;
|
|
444
|
+
line-height: 19px;
|
|
445
|
+
margin-top: 4px;
|
|
446
|
+
|
|
447
|
+
&--dark {
|
|
448
|
+
color: $barely-background;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
&__toggle-part-icon {
|
|
453
|
+
position: absolute;
|
|
454
|
+
margin-top: 8px;
|
|
455
|
+
margin-left: 8px;
|
|
456
|
+
color: $brand-blue;
|
|
457
|
+
|
|
458
|
+
&--up {
|
|
459
|
+
transform: rotate(180deg);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
&--dark {
|
|
463
|
+
color: $banana-bread;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
&__radio-btns {
|
|
468
|
+
width: 100%;
|
|
469
|
+
max-width: 492px;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
</style>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<!-- eslint-disable max-len -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import RadioButton from '../../Forms/RadioButton.vue'
|
|
4
|
+
import { dark as vDark } from '../../../directives'
|
|
5
|
+
import type { TChoiceKey } from '../question'
|
|
6
|
+
import { stripHtmlTags } from '../../../utils'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
isDarkMode?: boolean
|
|
10
|
+
choices?: TChoiceKey[]
|
|
11
|
+
choicesText?: string[]
|
|
12
|
+
showAnswers?: boolean
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
isDarkMode: false,
|
|
18
|
+
choices: () => [],
|
|
19
|
+
choicesText: () => [],
|
|
20
|
+
showAnswers: false,
|
|
21
|
+
disabled: false,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const selectedChoice = defineModel<TChoiceKey | null>({ default: null })
|
|
25
|
+
|
|
26
|
+
const selectChoice = (choiceKey: TChoiceKey) => {
|
|
27
|
+
selectedChoice.value = choiceKey
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const radioButtonColor = (choice: TChoiceKey) => {
|
|
31
|
+
if (props.showAnswers) {
|
|
32
|
+
if (choice === selectedChoice.value && selectedChoice.value?.startsWith('a')) {
|
|
33
|
+
return 'green'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (choice === selectedChoice.value && selectedChoice.value?.startsWith('d')) {
|
|
37
|
+
return 'gray'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (choice.startsWith('a')) {
|
|
41
|
+
return 'green'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'blue'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<ul
|
|
52
|
+
v-if="choices && choices.length"
|
|
53
|
+
class="uikit-mpmc-radio-group"
|
|
54
|
+
:class="{
|
|
55
|
+
'uikit-mpmc-radio-group--show-answer': showAnswers
|
|
56
|
+
}"
|
|
57
|
+
v-dark="isDarkMode"
|
|
58
|
+
role="radiogroup"
|
|
59
|
+
>
|
|
60
|
+
<li
|
|
61
|
+
v-for="(choice, index) in choices"
|
|
62
|
+
class="uikit-mpmc-radio-group__option"
|
|
63
|
+
:class="{
|
|
64
|
+
'uikit-mpmc-radio-group__option--show-answer': showAnswers
|
|
65
|
+
}"
|
|
66
|
+
v-dark="isDarkMode"
|
|
67
|
+
role="radio"
|
|
68
|
+
:tabindex="disabled || showAnswers ? -1 : 0"
|
|
69
|
+
:aria-checked="choice === selectedChoice"
|
|
70
|
+
:aria-disabled="disabled || showAnswers"
|
|
71
|
+
:aria-label="`
|
|
72
|
+
${stripHtmlTags(choicesText[index])}. ${choice === selectedChoice ? 'Selected' : 'Not selected'}.`"
|
|
73
|
+
:key="choice"
|
|
74
|
+
@click="!disabled && !showAnswers && selectChoice(choice)"
|
|
75
|
+
@keydown.enter.stop="!disabled && !showAnswers && selectChoice(choice)"
|
|
76
|
+
@keydown.space.prevent.stop="!disabled && !showAnswers && selectChoice(choice)"
|
|
77
|
+
>
|
|
78
|
+
<RadioButton
|
|
79
|
+
class="uikit-mpmc-radio-group__radio-btn"
|
|
80
|
+
:selected="(choice === selectedChoice) || (showAnswers && choice.startsWith('a'))"
|
|
81
|
+
:disabled="disabled"
|
|
82
|
+
:isDarkMode="isDarkMode"
|
|
83
|
+
:color="radioButtonColor(choice)"
|
|
84
|
+
@click="!disabled && !showAnswers && selectChoice(choice)"
|
|
85
|
+
/>
|
|
86
|
+
<div
|
|
87
|
+
v-if="choicesText && choicesText.length > 0"
|
|
88
|
+
v-dark="props.isDarkMode"
|
|
89
|
+
class="uikit-mpmc-radio-group__choice-text"
|
|
90
|
+
:class="{
|
|
91
|
+
'uikit-mpmc-radio-group__choice-text--distractor':
|
|
92
|
+
showAnswers && choice?.startsWith('d')
|
|
93
|
+
}"
|
|
94
|
+
@click="!disabled && !showAnswers && selectChoice(choice)"
|
|
95
|
+
>
|
|
96
|
+
{{ stripHtmlTags(choicesText[index]) }}
|
|
97
|
+
</div>
|
|
98
|
+
</li>
|
|
99
|
+
</ul>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<style lang="scss" scoped>
|
|
103
|
+
@use '@/styles/breakpoints' as *;
|
|
104
|
+
@use '@/styles/colors' as *;
|
|
105
|
+
|
|
106
|
+
.uikit-mpmc-radio-group {
|
|
107
|
+
list-style: none;
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 0;
|
|
110
|
+
display: block;
|
|
111
|
+
background: $white;
|
|
112
|
+
border-radius: 0px 0px 5px 5px;
|
|
113
|
+
|
|
114
|
+
&--dark {
|
|
115
|
+
background: $brand-black;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&--show-answer {
|
|
119
|
+
cursor: default;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&__option {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: flex-start;
|
|
125
|
+
align-self: stretch;
|
|
126
|
+
padding: 11px 15px 12px 15px;
|
|
127
|
+
max-width: 492px;
|
|
128
|
+
border: 0.5px solid rgba($pewter, 0.85);
|
|
129
|
+
border-top: none;
|
|
130
|
+
outline: none;
|
|
131
|
+
transition: background-color 0.2s ease;
|
|
132
|
+
|
|
133
|
+
&:not(&--show-answer) {
|
|
134
|
+
&:focus {
|
|
135
|
+
border: 0.5px solid $brand-blue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&--dark {
|
|
140
|
+
&:focus:not(.uikit-mpmc-radio-group__option--show-answer) {
|
|
141
|
+
border: 0.5px solid $banana-bread;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&__choice-text {
|
|
147
|
+
display: block;
|
|
148
|
+
margin-left: 12px;
|
|
149
|
+
color: $brand-black;
|
|
150
|
+
font-size: 16px;
|
|
151
|
+
font-weight: 500;
|
|
152
|
+
line-height: 23px;
|
|
153
|
+
letter-spacing: -0.1px;
|
|
154
|
+
|
|
155
|
+
&--dark {
|
|
156
|
+
color: $barely-background;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&--distractor {
|
|
160
|
+
text-decoration: line-through;
|
|
161
|
+
color: $ash;
|
|
162
|
+
|
|
163
|
+
&--dark {
|
|
164
|
+
color: $fog;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
@@ -162,6 +162,7 @@ import { useQuestionContext } from './composables'
|
|
|
162
162
|
import type { IRadioOptions, TMatrixChoiceKey } from './../question'
|
|
163
163
|
import BrandColors from '../../../pocketprep-export.module.scss'
|
|
164
164
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
165
|
+
import { stripHtmlTags } from '../../../utils'
|
|
165
166
|
|
|
166
167
|
const emit = defineEmits<{
|
|
167
168
|
'emitSelectedMatrixChoice': [matrixChoiceKeys: TMatrixChoiceKey[]]
|
|
@@ -186,15 +187,6 @@ const expandedRowNumbers = ref<number[]>([])
|
|
|
186
187
|
const selectedColumnHeaders = ref<string[][]>([])
|
|
187
188
|
const brandColors = BrandColors
|
|
188
189
|
|
|
189
|
-
const stripHtmlTags = (string?: string) => {
|
|
190
|
-
if (string) {
|
|
191
|
-
const div = document.createElement('div')
|
|
192
|
-
div.innerHTML = string
|
|
193
|
-
return div.textContent || ''
|
|
194
|
-
}
|
|
195
|
-
return ''
|
|
196
|
-
}
|
|
197
|
-
|
|
198
190
|
const matrixChoiceLayout = computed(() => {
|
|
199
191
|
return question.value.matrixChoiceLayout
|
|
200
192
|
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import RadioButton from '../../Forms/RadioButton.vue'
|
|
3
3
|
import { dark as vDark } from '../../../directives'
|
|
4
4
|
import type { TMatrixChoiceKey } from '../question'
|
|
5
|
+
import { stripHtmlTags } from '../../../utils'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
isDarkMode?: boolean
|
|
@@ -42,15 +43,6 @@ const radioButtonColor = (choice: TMatrixChoiceKey) => {
|
|
|
42
43
|
|
|
43
44
|
return 'blue'
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
const stripHtmlTags = (string?: string) => {
|
|
47
|
-
if (string) {
|
|
48
|
-
const div = document.createElement('div')
|
|
49
|
-
div.innerHTML = string
|
|
50
|
-
return div.textContent || ''
|
|
51
|
-
}
|
|
52
|
-
return ''
|
|
53
|
-
}
|
|
54
46
|
</script>
|
|
55
47
|
|
|
56
48
|
<template>
|