useTrapTab.ts 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { onKeyStroke } from '@vueuse/core'
  3. import { ref, type Ref, type ShallowRef } from 'vue'
  4. import { getFocusableElements } from '#shared/utils/getFocusableElements.ts'
  5. export const useTrapTab = <T extends HTMLElement>(
  6. container: Readonly<ShallowRef<T | null>>,
  7. noAutoActivation = false,
  8. ) => {
  9. const trapFocus = (e: KeyboardEvent) => {
  10. const focusableElements = getFocusableElements(container.value)
  11. const firstFocusableElement = focusableElements[0]
  12. const lastFocusableElement = focusableElements[focusableElements.length - 1]
  13. if (e.shiftKey) {
  14. // if shift key pressed for shift + tab combination
  15. if (document.activeElement === firstFocusableElement) {
  16. lastFocusableElement.focus() // add focus for the last focusable element
  17. e.preventDefault()
  18. }
  19. return
  20. }
  21. if (document.activeElement === lastFocusableElement) {
  22. // if focused has reached to last focusable element then focus first focusable element after pressing tab
  23. firstFocusableElement.focus() // add focus for the first focusable element
  24. e.preventDefault()
  25. }
  26. }
  27. const active = ref(!noAutoActivation)
  28. const activateTabTrap = () => {
  29. active.value = true
  30. }
  31. const deactivateTabTrap = () => {
  32. active.value = false
  33. }
  34. onKeyStroke(
  35. (e) => {
  36. if (!active.value) return
  37. const isTab = e.key === 'Tab' || e.keyCode === 9
  38. if (!isTab) return
  39. trapFocus(e)
  40. },
  41. { target: container as Ref<EventTarget> },
  42. )
  43. const moveNextFocusToTrap = () => {
  44. if (!container.value) return
  45. const dummyElement = document.createElement('div')
  46. dummyElement.tabIndex = 0
  47. requestAnimationFrame(() => {
  48. container.value?.prepend(dummyElement)
  49. dummyElement.focus()
  50. dummyElement.remove()
  51. })
  52. }
  53. return {
  54. activateTabTrap,
  55. deactivateTabTrap,
  56. moveNextFocusToTrap,
  57. }
  58. }