use-sidebar-drag.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { ref } from 'vue';
  2. interface DragOptions {
  3. max: number;
  4. min: number;
  5. }
  6. interface DragElements {
  7. dragBar: HTMLElement | null;
  8. target: HTMLElement | null;
  9. }
  10. type DragCallback = (newWidth: number) => void;
  11. export function useSidebarDrag() {
  12. const isDragging = ref(false);
  13. let cleanup: (() => void) | null = null;
  14. let dragOverlay: HTMLElement | null = null;
  15. const startDrag = (
  16. e: MouseEvent,
  17. options: DragOptions,
  18. elements: DragElements,
  19. onDrag: DragCallback,
  20. ) => {
  21. const { min, max } = options;
  22. const { dragBar, target } = elements;
  23. if (isDragging.value || !dragBar || !target) return;
  24. e.preventDefault();
  25. e.stopPropagation();
  26. isDragging.value = true;
  27. const startX = e.clientX;
  28. const startWidth = target.getBoundingClientRect().width;
  29. const startLeft = dragBar.offsetLeft;
  30. dragBar.classList.add('bg-primary');
  31. dragBar.classList.remove('bg-primary/30');
  32. const dragBarTransition = dragBar.style.transition;
  33. const targetTransition = target.style.transition;
  34. dragBar.style.transition = 'none';
  35. target.style.transition = 'none';
  36. dragOverlay = document.createElement('div');
  37. dragOverlay.style.position = 'fixed';
  38. dragOverlay.style.inset = '0';
  39. dragOverlay.style.zIndex = '9999';
  40. dragOverlay.style.cursor = 'col-resize';
  41. dragOverlay.style.userSelect = 'none';
  42. dragOverlay.style.outline = 'none';
  43. dragOverlay.tabIndex = -1;
  44. dragOverlay.style.background = 'rgba(0,0,0,0)';
  45. document.body.append(dragOverlay);
  46. const onMouseMove = (moveEvent: MouseEvent) => {
  47. if (!isDragging.value || !dragBar || !target) {
  48. endDrag();
  49. return;
  50. }
  51. const deltaX = moveEvent.clientX - startX;
  52. let currentWidth = startWidth + deltaX;
  53. const isOutOfMin = currentWidth < min;
  54. const isOutOfMax = currentWidth > max;
  55. const isOutOfBounds = isOutOfMin || isOutOfMax;
  56. if (isOutOfMin) currentWidth = min;
  57. if (isOutOfMax) currentWidth = max;
  58. const newLeft = startLeft + (currentWidth - startWidth);
  59. if (dragOverlay)
  60. dragOverlay.style.cursor = isOutOfBounds ? 'not-allowed' : 'col-resize';
  61. dragBar.style.left = `${newLeft}px`;
  62. if (isOutOfBounds) {
  63. dragBar.classList.add('bg-primary/30');
  64. dragBar.classList.remove('bg-primary');
  65. } else {
  66. dragBar.classList.add('bg-primary');
  67. dragBar.classList.remove('bg-primary/30');
  68. }
  69. };
  70. const onMouseUp = (upEvent: MouseEvent) => {
  71. if (!isDragging.value || !dragBar || !target) {
  72. endDrag();
  73. return;
  74. }
  75. const deltaX = upEvent.clientX - startX;
  76. let newWidth = startWidth + deltaX;
  77. newWidth = Math.min(max, Math.max(min, newWidth));
  78. dragBar.classList.remove('bg-primary', 'bg-primary/30');
  79. try {
  80. onDrag?.(Math.round(newWidth));
  81. } finally {
  82. endDrag();
  83. }
  84. };
  85. document.addEventListener('mousemove', onMouseMove);
  86. document.addEventListener('mouseup', onMouseUp);
  87. cleanup = () => {
  88. if (!cleanup) return;
  89. document.removeEventListener('mousemove', onMouseMove);
  90. document.removeEventListener('mouseup', onMouseUp);
  91. if (dragBar) {
  92. dragBar.style.transition = dragBarTransition;
  93. dragBar.style.left = '';
  94. dragBar.classList.remove('bg-primary', 'bg-primary/30');
  95. }
  96. if (target) {
  97. target.style.transition = targetTransition;
  98. }
  99. if (dragOverlay) {
  100. dragOverlay.remove();
  101. dragOverlay = null;
  102. }
  103. isDragging.value = false;
  104. cleanup = null;
  105. };
  106. };
  107. const endDrag = () => {
  108. cleanup?.();
  109. };
  110. return {
  111. startDrag,
  112. endDrag,
  113. get isDragging() {
  114. return isDragging.value;
  115. },
  116. };
  117. }