useResizeWidthHandle.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import {
  3. type MaybeComputedElementRef,
  4. type MaybeElement,
  5. onKeyStroke,
  6. useElementBounding,
  7. useWindowSize,
  8. } from '@vueuse/core'
  9. import { ref, onUnmounted, type Ref } from 'vue'
  10. import { EnumTextDirection } from '#shared/graphql/types.ts'
  11. import { useLocaleStore } from '#shared/stores/locale.ts'
  12. export const useResizeWidthHandle = (
  13. resizeCallback: (positionX: number) => void,
  14. handleRef: MaybeComputedElementRef<MaybeElement>,
  15. keyStrokeCallback: (e: KeyboardEvent, adjustment: number) => void,
  16. options?: {
  17. calculateFromRight?: boolean
  18. },
  19. ) => {
  20. const isResizingHorizontal = ref(false)
  21. const locale = useLocaleStore()
  22. const { width } = useElementBounding(
  23. handleRef as MaybeComputedElementRef<MaybeElement>,
  24. )
  25. const { width: screenWidth } = useWindowSize()
  26. const resize = (event: MouseEvent | TouchEvent) => {
  27. // Position the cursor as close to the handle center as possible.
  28. let positionX = Math.round(width.value / 2)
  29. if (event instanceof MouseEvent) {
  30. positionX += event.pageX
  31. } else if (event.targetTouches[0]) {
  32. positionX += event.targetTouches[0].pageX
  33. }
  34. // In case of RTL locale, subtract the reported position from the current screen width.
  35. if (
  36. locale.localeData?.dir === EnumTextDirection.Rtl &&
  37. !options?.calculateFromRight // If the option is set, do not calculate from the right.
  38. )
  39. positionX = screenWidth.value - positionX
  40. // In case of LTR locale and resizer is used from right side of the window, subtract the reported position from the current screen width.
  41. if (
  42. locale.localeData?.dir === EnumTextDirection.Ltr &&
  43. options?.calculateFromRight
  44. )
  45. positionX = screenWidth.value - positionX
  46. resizeCallback(positionX)
  47. }
  48. const endResizing = () => {
  49. // eslint-disable-next-line no-use-before-define
  50. removeListeners()
  51. isResizingHorizontal.value = false
  52. }
  53. const removeListeners = () => {
  54. document.removeEventListener('touchmove', resize)
  55. document.removeEventListener('touchend', endResizing)
  56. document.removeEventListener('mousemove', resize)
  57. document.removeEventListener('mouseup', endResizing)
  58. }
  59. const addEventListeners = () => {
  60. document.addEventListener('touchend', endResizing)
  61. document.addEventListener('touchmove', resize)
  62. document.addEventListener('mouseup', endResizing)
  63. document.addEventListener('mousemove', resize)
  64. }
  65. const startResizing = (e: MouseEvent) => {
  66. // Do not react on double click event.
  67. if (e.detail > 1) return
  68. e.preventDefault()
  69. isResizingHorizontal.value = true
  70. addEventListeners()
  71. }
  72. onUnmounted(() => {
  73. removeListeners()
  74. })
  75. // a11y keyboard navigation horizontal resize
  76. onKeyStroke(
  77. 'ArrowLeft',
  78. (e: KeyboardEvent) => {
  79. if (options?.calculateFromRight) {
  80. keyStrokeCallback(e, locale.localeData?.dir === 'rtl' ? -5 : 5)
  81. } else {
  82. keyStrokeCallback(e, locale.localeData?.dir === 'rtl' ? 5 : -5)
  83. }
  84. },
  85. { target: handleRef as Ref<EventTarget> },
  86. )
  87. onKeyStroke(
  88. 'ArrowRight',
  89. (e: KeyboardEvent) => {
  90. if (options?.calculateFromRight) {
  91. keyStrokeCallback(e, locale.localeData?.dir === 'rtl' ? 5 : -5)
  92. } else {
  93. keyStrokeCallback(e, locale.localeData?.dir === 'rtl' ? -5 : 5)
  94. }
  95. },
  96. { target: handleRef as Ref<EventTarget> },
  97. )
  98. return {
  99. isResizingHorizontal,
  100. startResizing,
  101. }
  102. }