119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import { onMounted, onUnmounted, watch } from 'vue';
|
|
import type { Ref } from 'vue';
|
|
|
|
export type SnapType = 'left' | 'right' | 'top' | null;
|
|
|
|
interface Constraints {
|
|
top: number;
|
|
right: number;
|
|
bottom: number;
|
|
left: number;
|
|
}
|
|
|
|
interface UseDraggableOptions {
|
|
onDrag: (x: number, y: number) => void;
|
|
onDragStart?: () => void;
|
|
onDragEnd?: (snapType: SnapType) => void;
|
|
onSnap?: (snapType: SnapType) => void;
|
|
position: Ref<{ x: number; y: number; }>;
|
|
constraints?: Ref<Constraints | undefined>;
|
|
targetSize: Ref<{ width: number; height: number; }>;
|
|
enabled: Ref<boolean>;
|
|
}
|
|
|
|
export function useDraggable(target: Ref<HTMLElement | null>, options: UseDraggableOptions) {
|
|
const { onDrag, onDragStart, onDragEnd, onSnap, position, constraints, targetSize, enabled } = options;
|
|
|
|
let startX = 0;
|
|
let startY = 0;
|
|
let initialMouseX = 0;
|
|
let initialMouseY = 0;
|
|
let currentSnapType: SnapType = null;
|
|
|
|
const handleMouseDown = (event: MouseEvent) => {
|
|
if (!target.value || !enabled.value) return;
|
|
|
|
if (onDragStart) onDragStart();
|
|
|
|
startX = position.value.x;
|
|
startY = position.value.y;
|
|
initialMouseX = event.clientX;
|
|
initialMouseY = event.clientY;
|
|
currentSnapType = null;
|
|
|
|
document.addEventListener('mousemove', handleMouseMove);
|
|
document.addEventListener('mouseup', handleMouseUp);
|
|
};
|
|
|
|
const handleMouseMove = (event: MouseEvent) => {
|
|
const dx = event.clientX - initialMouseX;
|
|
const dy = event.clientY - initialMouseY;
|
|
|
|
let newX = startX + dx;
|
|
let newY = startY + dy;
|
|
|
|
let snapType: SnapType = null;
|
|
if (typeof window !== 'undefined') {
|
|
if (event.clientX <= 1) {
|
|
snapType = 'left';
|
|
} else if (event.clientX >= window.innerWidth - 1) {
|
|
snapType = 'right';
|
|
} else if (event.clientY <= 1) {
|
|
snapType = 'top';
|
|
}
|
|
}
|
|
|
|
if (snapType !== currentSnapType) {
|
|
currentSnapType = snapType;
|
|
if (onSnap) onSnap(currentSnapType);
|
|
}
|
|
|
|
if (!currentSnapType && constraints?.value) {
|
|
const { top, right, bottom, left } = constraints.value;
|
|
const { width, height } = targetSize.value;
|
|
newX = Math.max(left, Math.min(newX, right - width));
|
|
newY = Math.max(top, Math.min(newY, bottom - height));
|
|
}
|
|
|
|
onDrag(newX, newY);
|
|
};
|
|
|
|
const handleMouseUp = () => {
|
|
if (onDragEnd) onDragEnd(currentSnapType);
|
|
|
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
};
|
|
|
|
const addListener = () => {
|
|
if (target.value) {
|
|
target.value.addEventListener('mousedown', handleMouseDown);
|
|
target.value.style.cursor = 'move';
|
|
}
|
|
};
|
|
|
|
const removeListener = () => {
|
|
if (target.value) {
|
|
target.value.removeEventListener('mousedown', handleMouseDown);
|
|
target.value.style.cursor = 'default';
|
|
}
|
|
};
|
|
|
|
// Watch both the target and the enabled flag
|
|
watch([target, enabled], ([newTarget, isEnabled]) => {
|
|
if (newTarget) {
|
|
if (isEnabled) {
|
|
addListener();
|
|
} else {
|
|
removeListener();
|
|
}
|
|
}
|
|
}, { immediate: true });
|
|
|
|
onUnmounted(() => {
|
|
removeListener();
|
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
});
|
|
}
|