@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.
@@ -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
- if (!isMoveTriggeredByArrowKey.value) {
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;
@@ -95,7 +95,7 @@ const isCorrectlyAnswered = computed(() => {
95
95
  return isMatrixQuestionCorrect.value
96
96
  }
97
97
 
98
- if (isBuildListQuestion) {
98
+ if (isBuildListQuestion.value) {
99
99
  return isBuildListOrderCorrect.value
100
100
  }
101
101
 
@@ -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="{ 'uikit-question__passage-and-image-dropdown--review-mode': reviewMode }"
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocketprep/ui-kit",
3
- "version": "3.7.3",
3
+ "version": "3.7.5",
4
4
  "description": "Pocket Prep UI Kit",
5
5
  "author": "pocketprep",
6
6
  "scripts": {