import { ref } from 'vue'; interface DragOptions { max: number; min: number; } interface DragElements { dragBar: HTMLElement | null; target: HTMLElement | null; } type DragCallback = (newWidth: number) => void; export function useSidebarDrag() { const isDragging = ref(false); let cleanup: (() => void) | null = null; let dragOverlay: HTMLElement | null = null; const startDrag = ( e: MouseEvent, options: DragOptions, elements: DragElements, onDrag: DragCallback, ) => { const { min, max } = options; const { dragBar, target } = elements; if (isDragging.value || !dragBar || !target) return; e.preventDefault(); e.stopPropagation(); isDragging.value = true; const startX = e.clientX; const startWidth = target.getBoundingClientRect().width; const startLeft = dragBar.offsetLeft; dragBar.classList.add('bg-primary'); dragBar.classList.remove('bg-primary/30'); const dragBarTransition = dragBar.style.transition; const targetTransition = target.style.transition; dragBar.style.transition = 'none'; target.style.transition = 'none'; dragOverlay = document.createElement('div'); dragOverlay.style.position = 'fixed'; dragOverlay.style.inset = '0'; dragOverlay.style.zIndex = '9999'; dragOverlay.style.cursor = 'col-resize'; dragOverlay.style.userSelect = 'none'; dragOverlay.style.outline = 'none'; dragOverlay.tabIndex = -1; dragOverlay.style.background = 'rgba(0,0,0,0)'; document.body.append(dragOverlay); const onMouseMove = (moveEvent: MouseEvent) => { if (!isDragging.value || !dragBar || !target) { endDrag(); return; } const deltaX = moveEvent.clientX - startX; let currentWidth = startWidth + deltaX; const isOutOfMin = currentWidth < min; const isOutOfMax = currentWidth > max; const isOutOfBounds = isOutOfMin || isOutOfMax; if (isOutOfMin) currentWidth = min; if (isOutOfMax) currentWidth = max; const newLeft = startLeft + (currentWidth - startWidth); if (dragOverlay) dragOverlay.style.cursor = isOutOfBounds ? 'not-allowed' : 'col-resize'; dragBar.style.left = `${newLeft}px`; if (isOutOfBounds) { dragBar.classList.add('bg-primary/30'); dragBar.classList.remove('bg-primary'); } else { dragBar.classList.add('bg-primary'); dragBar.classList.remove('bg-primary/30'); } }; const onMouseUp = (upEvent: MouseEvent) => { if (!isDragging.value || !dragBar || !target) { endDrag(); return; } const deltaX = upEvent.clientX - startX; let newWidth = startWidth + deltaX; newWidth = Math.min(max, Math.max(min, newWidth)); dragBar.classList.remove('bg-primary', 'bg-primary/30'); try { onDrag?.(Math.round(newWidth)); } finally { endDrag(); } }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); cleanup = () => { if (!cleanup) return; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); if (dragBar) { dragBar.style.transition = dragBarTransition; dragBar.style.left = ''; dragBar.classList.remove('bg-primary', 'bg-primary/30'); } if (target) { target.style.transition = targetTransition; } if (dragOverlay) { dragOverlay.remove(); dragOverlay = null; } isDragging.value = false; cleanup = null; }; }; const endDrag = () => { cleanup?.(); }; return { startDrag, endDrag, get isDragging() { return isDragging.value; }, }; }