ResizeLine.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <!-- Copyright (C) 2012-2024 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 class="hover-area" :class="[`hover-area--${props.orientation}`]">
  78. <button
  79. ref="resizeLine"
  80. v-tooltip="!disabled ? label : undefined"
  81. :aria-describedby="id"
  82. :disabled="disabled"
  83. tabindex="0"
  84. class="line"
  85. :class="[{ '!bg-blue-800': resizing }, buttonClass]"
  86. @mousedown="handleMousedown"
  87. @blur="resizing = false"
  88. @touchstart="handleTouchstart"
  89. @dblclick="handleDoubleClick"
  90. />
  91. <span
  92. v-if="!disabled"
  93. :id="id"
  94. role="separator"
  95. class="invisible absolute -z-20"
  96. :aria-orientation="resizeOrientation"
  97. :aria-valuenow="values?.current ?? undefined"
  98. :aria-valuemax="values?.max ?? undefined"
  99. :aria-valuemin="values?.min ?? undefined"
  100. />
  101. </div>
  102. </template>
  103. <style scoped>
  104. .line {
  105. @apply focus:outline-none;
  106. &:focus-within {
  107. background-color: theme('colors.blue.800') !important;
  108. }
  109. }
  110. .hover-area {
  111. @apply flex justify-center opacity-0 focus-within:opacity-100 hover:opacity-100;
  112. .line:not(:disabled) {
  113. @apply bg-neutral-500 hover:bg-blue-600 dark:bg-gray-200 dark:hover:bg-blue-900;
  114. }
  115. &--horizontal {
  116. @apply -:w-full h-[12px];
  117. .line {
  118. @apply h-1 w-full enabled:cursor-row-resize;
  119. }
  120. }
  121. &--vertical {
  122. @apply -:h-full w-[12px];
  123. .line {
  124. @apply h-full w-1 enabled:cursor-col-resize;
  125. }
  126. }
  127. }
  128. </style>