ResizeLine.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. <!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script lang="ts" setup>
  3. import { useEventListener } from '@vueuse/core'
  4. import { computed, readonly, ref } from 'vue'
  5. import getUuid from '#shared/utils/getUuid.ts'
  6. interface Props {
  7. label: string
  8. /**
  9. * horizontal line or vertical line
  10. * */
  11. orientation?: 'horizontal' | 'vertical'
  12. values?: {
  13. /**
  14. * Maximum width/height in px value of what the container can be resized to
  15. * */
  16. max?: number | string
  17. /**
  18. * Minimum width/height in px value of what the container can be resized to
  19. * */
  20. min?: number | string
  21. /**
  22. * Current width/height in px value of the container
  23. * */
  24. current?: number | string
  25. }
  26. disabled?: boolean
  27. buttonClass?: string
  28. }
  29. const props = withDefaults(defineProps<Props>(), {
  30. orientation: 'vertical',
  31. })
  32. const resizeLine = ref<HTMLDivElement>()
  33. const resizing = ref(false)
  34. const emit = defineEmits<{
  35. 'mousedown-event': [MouseEvent]
  36. 'touchstart-event': [TouchEvent]
  37. 'dblclick-event': [MouseEvent]
  38. }>()
  39. const resizeOrientation = computed(() =>
  40. // Vertical resize line should have horizontal aria-orientation -> container width
  41. // Horizontal resize line should have vertical aria-orientation -> container height
  42. props.orientation === 'horizontal' ? 'vertical' : 'horizontal',
  43. )
  44. const addRemoveResizingListener = (event: 'mouseup' | 'touchend') => {
  45. useEventListener(
  46. event,
  47. () => {
  48. resizing.value = false
  49. },
  50. { once: true },
  51. )
  52. }
  53. const handleMousedown = (event: MouseEvent) => {
  54. if (props.disabled) return
  55. emit('mousedown-event', event)
  56. resizing.value = true
  57. addRemoveResizingListener('mouseup')
  58. }
  59. const handleTouchstart = (event: TouchEvent) => {
  60. if (props.disabled) return
  61. emit('touchstart-event', event)
  62. resizing.value = true
  63. addRemoveResizingListener('touchend')
  64. }
  65. const handleDoubleClick = (event: MouseEvent) => {
  66. if (props.disabled) return
  67. emit('dblclick-event', event)
  68. resizeLine.value?.blur()
  69. }
  70. const id = getUuid()
  71. defineExpose({
  72. resizeLine,
  73. resizing: readonly(resizing),
  74. })
  75. </script>
  76. <template>
  77. <div
  78. class="flex justify-center opacity-0 focus-within:opacity-100 hover:opacity-100"
  79. :class="
  80. {
  81. horizontal: 'h-[12px] w-full',
  82. vertical: 'h-full w-[12px]',
  83. }[orientation]
  84. "
  85. >
  86. <button
  87. ref="resizeLine"
  88. v-tooltip="!disabled ? label : undefined"
  89. :aria-describedby="id"
  90. :disabled="disabled"
  91. tabindex="0"
  92. class="not-disabled:bg-neutral-500 focus-within:bg-blue-800! not-disabled:hover:bg-blue-600 focus:outline-hidden disabled:pointer-events-none not-disabled:dark:bg-gray-200 not-disabled:dark:hover:bg-blue-900"
  93. :class="[
  94. { 'bg-blue-800!': resizing },
  95. {
  96. horizontal: 'h-1 w-full enabled:cursor-row-resize!',
  97. vertical: 'h-full w-1 enabled:cursor-col-resize!',
  98. }[orientation],
  99. buttonClass,
  100. ]"
  101. @mousedown="handleMousedown"
  102. @blur="resizing = false"
  103. @touchstart="handleTouchstart"
  104. @dblclick="handleDoubleClick"
  105. />
  106. <span
  107. v-if="!disabled"
  108. :id="id"
  109. role="separator"
  110. class="invisible absolute -z-20"
  111. :aria-orientation="resizeOrientation"
  112. :aria-valuenow="values?.current ?? undefined"
  113. :aria-valuemax="values?.max ?? undefined"
  114. :aria-valuemin="values?.min ?? undefined"
  115. />
  116. </div>
  117. </template>