index.ts 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import { getCurrentInstance, ref, shallowRef, watch } from 'vue'
  2. import { useEventListener } from '@vueuse/core'
  3. import type { ShallowRef } from 'vue'
  4. interface UseFocusControllerOptions {
  5. afterFocus?: () => void
  6. afterBlur?: () => void
  7. }
  8. export function useFocusController<T extends HTMLElement>(
  9. target: ShallowRef<T | undefined>,
  10. { afterFocus, afterBlur }: UseFocusControllerOptions = {}
  11. ) {
  12. const instance = getCurrentInstance()!
  13. const { emit } = instance
  14. const wrapperRef = shallowRef<HTMLElement>()
  15. const isFocused = ref(false)
  16. const handleFocus = (event: FocusEvent) => {
  17. if (isFocused.value) return
  18. isFocused.value = true
  19. emit('focus', event)
  20. afterFocus?.()
  21. }
  22. const handleBlur = (event: FocusEvent) => {
  23. if (
  24. event.relatedTarget &&
  25. wrapperRef.value?.contains(event.relatedTarget as Node)
  26. )
  27. return
  28. isFocused.value = false
  29. emit('blur', event)
  30. afterBlur?.()
  31. }
  32. const handleClick = () => {
  33. target.value?.focus()
  34. }
  35. watch(wrapperRef, (el) => {
  36. if (el) {
  37. el.setAttribute('tabindex', '-1')
  38. }
  39. })
  40. // TODO: using useEventListener will fail the test
  41. // useEventListener(target, 'focus', handleFocus)
  42. // useEventListener(target, 'blur', handleBlur)
  43. useEventListener(wrapperRef, 'click', handleClick)
  44. return {
  45. wrapperRef,
  46. isFocused,
  47. handleFocus,
  48. handleBlur,
  49. }
  50. }