@pocketprep/ui-kit 3.7.3 → 3.7.5
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 +5845 -5796
- package/dist/@pocketprep/ui-kit.js.map +1 -1
- package/dist/@pocketprep/ui-kit.umd.cjs +10 -10
- package/dist/@pocketprep/ui-kit.umd.cjs.map +1 -1
- package/lib/components/Quiz/Question/BuildListChoicesContainer.vue +127 -3
- package/lib/components/Quiz/Question/QuestionContext.vue +1 -1
- package/lib/components/Quiz/Question.vue +10 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="uikit-question-build-list-choices-container-root">
|
|
2
|
+
<div class="uikit-question-build-list-choices-container-root" @click="clearFocusState">
|
|
3
3
|
<!--
|
|
4
4
|
Visually hidden aria-live region for choice move announcements:
|
|
5
5
|
"Moved Photosynthesis to position 2 of 6."
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<TransitionGroup
|
|
18
18
|
v-dark="isDarkMode"
|
|
19
19
|
v-breakpoint="breakpointsWithEl"
|
|
20
|
-
name="list"
|
|
20
|
+
:name="shouldDisableTransitions ? '' : 'list'"
|
|
21
21
|
tag="ol"
|
|
22
22
|
class="uikit-question-build-list-choices-container"
|
|
23
23
|
:class="{
|
|
@@ -126,6 +126,9 @@
|
|
|
126
126
|
:disabled="index === 0"
|
|
127
127
|
:tabindex="buttonsAreFocusable && index !== 0 ? 0 : -1"
|
|
128
128
|
@click.prevent.stop="moveChoiceUp(index)"
|
|
129
|
+
@keydown.enter.prevent.stop="moveChoiceUp(index)"
|
|
130
|
+
@focus="setFocusedArrowButton(choice.key, 'up')"
|
|
131
|
+
@blur="clearFocusState"
|
|
129
132
|
:aria-label="`Move '${stripHtmlTags(choice.text || '')}' up`"
|
|
130
133
|
:title="'Move up'"
|
|
131
134
|
>
|
|
@@ -146,6 +149,9 @@
|
|
|
146
149
|
:disabled="index === orderedChoices.length - 1"
|
|
147
150
|
:tabindex="buttonsAreFocusable && index !== orderedChoices.length - 1 ? 0 : -1"
|
|
148
151
|
@click.prevent.stop="moveChoiceDown(index)"
|
|
152
|
+
@keydown.enter.prevent.stop="moveChoiceDown(index)"
|
|
153
|
+
@focus="setFocusedArrowButton(choice.key, 'down')"
|
|
154
|
+
@blur="clearFocusState"
|
|
149
155
|
:aria-label="`Move '${stripHtmlTags(choice.text || '')}' down`"
|
|
150
156
|
:title="'Move down'"
|
|
151
157
|
>
|
|
@@ -195,11 +201,17 @@ const floatingChoiceKey = ref<string | null>(null)
|
|
|
195
201
|
const choiceRefs = ref<Map<string, HTMLDivElement>>(new Map())
|
|
196
202
|
const announcementMessage = ref<string>('')
|
|
197
203
|
const isMoveTriggeredByArrowKey = ref<boolean>(false)
|
|
204
|
+
const focusedArrowButton = ref<{ choiceKey: string; direction: 'up' | 'down' } | null>(null)
|
|
205
|
+
const isMoveInProgress = ref<boolean>(false)
|
|
198
206
|
|
|
199
207
|
const buttonsAreFocusable = computed(() => {
|
|
200
208
|
return !showBuildListOrder.value && !reviewMode.value
|
|
201
209
|
})
|
|
202
210
|
|
|
211
|
+
const shouldDisableTransitions = computed(() => {
|
|
212
|
+
return reviewMode.value || showBuildListOrder.value
|
|
213
|
+
})
|
|
214
|
+
|
|
203
215
|
onMounted(() => {
|
|
204
216
|
updateOrderedChoices()
|
|
205
217
|
})
|
|
@@ -232,12 +244,56 @@ const setCardRef = (el: Element | ComponentPublicInstance | null, choiceKey: str
|
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
246
|
|
|
247
|
+
const clearFocusState = () => {
|
|
248
|
+
// Don't clear focus state if we're in the middle of animation
|
|
249
|
+
// This prevents clearing focus when the button temporarily loses focus during animation
|
|
250
|
+
if (!isMoveInProgress.value) {
|
|
251
|
+
focusedArrowButton.value = null
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const setFocusedArrowButton = (choiceKey: string, direction: 'up' | 'down') => {
|
|
256
|
+
focusedArrowButton.value = { choiceKey, direction }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const restoreFocusToArrowButton = (choiceKey: string, direction: 'up' | 'down') => {
|
|
260
|
+
const choiceEl = choiceRefs.value.get(choiceKey)
|
|
261
|
+
if (choiceEl) {
|
|
262
|
+
const buttonSelector = direction === 'up'
|
|
263
|
+
? '.uikit-question-build-list-choices-container__choice-up-button'
|
|
264
|
+
: '.uikit-question-build-list-choices-container__choice-down-button'
|
|
265
|
+
const button = choiceEl.querySelector(buttonSelector) as HTMLButtonElement
|
|
266
|
+
if (button && !button.disabled) {
|
|
267
|
+
button.focus()
|
|
268
|
+
setFocusedArrowButton(choiceKey, direction)
|
|
269
|
+
} else {
|
|
270
|
+
// Move focus to opposite button if focused button becomes disabled
|
|
271
|
+
const oppositeDirection = direction === 'up' ? 'down' : 'up'
|
|
272
|
+
const oppositeButtonSelector = oppositeDirection === 'up'
|
|
273
|
+
? '.uikit-question-build-list-choices-container__choice-up-button'
|
|
274
|
+
: '.uikit-question-build-list-choices-container__choice-down-button'
|
|
275
|
+
const oppositeButton = choiceEl.querySelector(oppositeButtonSelector) as HTMLButtonElement
|
|
276
|
+
if (oppositeButton && !oppositeButton.disabled) {
|
|
277
|
+
oppositeButton.focus()
|
|
278
|
+
setFocusedArrowButton(choiceKey, oppositeDirection)
|
|
279
|
+
} else {
|
|
280
|
+
clearFocusState()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
235
286
|
const restoreFocusAfterMove = (movedChoiceKey: string) => {
|
|
236
287
|
// Don't restore focus if we're in review mode or showing the build list order
|
|
237
288
|
if (showBuildListOrder.value || reviewMode.value) {
|
|
238
289
|
return
|
|
239
290
|
}
|
|
240
291
|
|
|
292
|
+
// Don't restore focus to choice element if there's a focused arrow button
|
|
293
|
+
if (focusedArrowButton.value && focusedArrowButton.value.choiceKey === movedChoiceKey) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
241
297
|
const delay = prefersReducedMotion() ? 0 : 50
|
|
242
298
|
setTimeout(() => {
|
|
243
299
|
const choiceEl = choiceRefs.value.get(movedChoiceKey)
|
|
@@ -262,7 +318,8 @@ const removeFocusAfterAnimation = (movedChoiceKey: string) => {
|
|
|
262
318
|
}
|
|
263
319
|
|
|
264
320
|
// Only remove focus if the move was NOT triggered by arrow keys
|
|
265
|
-
|
|
321
|
+
// AND there's no focused arrow button that should maintain focus
|
|
322
|
+
if (!isMoveTriggeredByArrowKey.value && !focusedArrowButton.value) {
|
|
266
323
|
const animationDelay = prefersReducedMotion() ? 0 : 350 // Slightly after animation completes
|
|
267
324
|
setTimeout(() => {
|
|
268
325
|
const choiceEl = choiceRefs.value.get(movedChoiceKey)
|
|
@@ -293,6 +350,9 @@ const moveChoiceUp = (index: number) => {
|
|
|
293
350
|
const previousItem = newChoices[ index - 1 ]
|
|
294
351
|
|
|
295
352
|
if (currentItem && previousItem) {
|
|
353
|
+
// Set move in progress flag
|
|
354
|
+
isMoveInProgress.value = true
|
|
355
|
+
|
|
296
356
|
// Set floating state for the clicked choice
|
|
297
357
|
floatingChoiceKey.value = currentItem.key
|
|
298
358
|
|
|
@@ -309,6 +369,25 @@ const moveChoiceUp = (index: number) => {
|
|
|
309
369
|
// Remove focus after animation if not triggered by arrow key
|
|
310
370
|
removeFocusAfterAnimation(currentItem.key)
|
|
311
371
|
|
|
372
|
+
// Restore focus to arrow button if it was previously focused
|
|
373
|
+
if (focusedArrowButton.value && focusedArrowButton.value.choiceKey === currentItem.key) {
|
|
374
|
+
const delay = prefersReducedMotion() ? 0 : 400 // After animation completes
|
|
375
|
+
setTimeout(() => {
|
|
376
|
+
const focusedButton = focusedArrowButton.value
|
|
377
|
+
if (focusedButton) {
|
|
378
|
+
restoreFocusToArrowButton(currentItem.key, focusedButton.direction)
|
|
379
|
+
}
|
|
380
|
+
// Clear move in progress flag after focus restoration
|
|
381
|
+
isMoveInProgress.value = false
|
|
382
|
+
}, delay)
|
|
383
|
+
} else {
|
|
384
|
+
// Clear move in progress flag if no focus restoration needed
|
|
385
|
+
const delay = prefersReducedMotion() ? 0 : 400
|
|
386
|
+
setTimeout(() => {
|
|
387
|
+
isMoveInProgress.value = false
|
|
388
|
+
}, delay)
|
|
389
|
+
}
|
|
390
|
+
|
|
312
391
|
// Clear floating state after animation completes
|
|
313
392
|
const animationDelay = prefersReducedMotion() ? 0 : 300
|
|
314
393
|
setTimeout(() => {
|
|
@@ -325,6 +404,9 @@ const moveChoiceDown = (index: number) => {
|
|
|
325
404
|
const nextItem = newChoices[ index + 1 ]
|
|
326
405
|
|
|
327
406
|
if (currentItem && nextItem) {
|
|
407
|
+
// Set move in progress flag
|
|
408
|
+
isMoveInProgress.value = true
|
|
409
|
+
|
|
328
410
|
// Set floating state for the clicked choice
|
|
329
411
|
floatingChoiceKey.value = currentItem.key
|
|
330
412
|
|
|
@@ -342,6 +424,25 @@ const moveChoiceDown = (index: number) => {
|
|
|
342
424
|
// Remove focus after animation
|
|
343
425
|
removeFocusAfterAnimation(currentItem.key)
|
|
344
426
|
|
|
427
|
+
// Restore focus to arrow button if it was previously focused
|
|
428
|
+
if (focusedArrowButton.value && focusedArrowButton.value.choiceKey === currentItem.key) {
|
|
429
|
+
const delay = prefersReducedMotion() ? 0 : 400 // After animation completes
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
const focusedButton = focusedArrowButton.value
|
|
432
|
+
if (focusedButton) {
|
|
433
|
+
restoreFocusToArrowButton(currentItem.key, focusedButton.direction)
|
|
434
|
+
}
|
|
435
|
+
// Clear move in progress flag after focus restoration
|
|
436
|
+
isMoveInProgress.value = false
|
|
437
|
+
}, delay)
|
|
438
|
+
} else {
|
|
439
|
+
// Clear move in progress flag if no focus restoration needed
|
|
440
|
+
const delay = prefersReducedMotion() ? 0 : 400
|
|
441
|
+
setTimeout(() => {
|
|
442
|
+
isMoveInProgress.value = false
|
|
443
|
+
}, delay)
|
|
444
|
+
}
|
|
445
|
+
|
|
345
446
|
// Clear floating state after animation completes
|
|
346
447
|
const animationDelay = prefersReducedMotion() ? 0 : 300
|
|
347
448
|
setTimeout(() => {
|
|
@@ -523,6 +624,29 @@ watch(selectedBuildListChoiceOrder, () => {
|
|
|
523
624
|
}
|
|
524
625
|
}
|
|
525
626
|
|
|
627
|
+
// Disable transitions when in review mode or showing build list order
|
|
628
|
+
&--review-mode,
|
|
629
|
+
&--correct,
|
|
630
|
+
&--incorrect,
|
|
631
|
+
&--teach-group-review {
|
|
632
|
+
.uikit-question-build-list-choices-container__choice-container {
|
|
633
|
+
transition: none;
|
|
634
|
+
|
|
635
|
+
&--floating {
|
|
636
|
+
transform: none;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.uikit-question-build-list-choices-container__choice {
|
|
641
|
+
transition: none;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.uikit-question-build-list-choices-container__choice-up-button,
|
|
645
|
+
.uikit-question-build-list-choices-container__choice-down-button {
|
|
646
|
+
transition: none;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
526
650
|
&__choice-number,
|
|
527
651
|
&__mobile-choice-number {
|
|
528
652
|
display: flex;
|
|
@@ -93,7 +93,11 @@
|
|
|
93
93
|
v-if="question.passage || passageImageUrl"
|
|
94
94
|
ref="uikit-question__passage-and-image-dropdown"
|
|
95
95
|
class="uikit-question__passage-and-image-dropdown"
|
|
96
|
-
:class="{
|
|
96
|
+
:class="{
|
|
97
|
+
'uikit-question__passage-and-image-dropdown--review-mode': reviewMode,
|
|
98
|
+
'uikit-question__passage-and-image-dropdown--build-list': isBuildListQuestion,
|
|
99
|
+
|
|
100
|
+
}"
|
|
97
101
|
@togglePassageImageLongAltDropdown="togglePassageImageLongAlt"
|
|
98
102
|
/>
|
|
99
103
|
<Paywall
|
|
@@ -2075,6 +2079,11 @@ provide(InjectionKeys.isTeachGroupReviewKey, isTeachGroupReview)
|
|
|
2075
2079
|
&--review-mode {
|
|
2076
2080
|
display: block;
|
|
2077
2081
|
}
|
|
2082
|
+
|
|
2083
|
+
&--build-list:not(#{&}--review-mode):not(#{&}--mobile) {
|
|
2084
|
+
max-width: 452px;
|
|
2085
|
+
margin-left: -8px;
|
|
2086
|
+
}
|
|
2078
2087
|
}
|
|
2079
2088
|
|
|
2080
2089
|
&__unanswered-teach-review-label {
|