130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
|
import { onMounted, onUnmounted, watch } from 'vue';
|
||
|
import type { Ref } from 'vue';
|
||
|
|
||
|
interface Constraints {
|
||
|
top: number;
|
||
|
right: number;
|
||
|
bottom: number;
|
||
|
left: number;
|
||
|
}
|
||
|
|
||
|
interface ResizeOptions {
|
||
|
onResize: (data: { x: number; y: number; width: number; height: number; }) => void;
|
||
|
onResizeStart?: () => void;
|
||
|
onResizeEnd?: () => void;
|
||
|
initialSize: { width: number; height: number; };
|
||
|
initialPosition: { x: number; y: number; };
|
||
|
constraints?: Ref<Constraints | undefined>;
|
||
|
enabled: Ref<boolean>;
|
||
|
}
|
||
|
|
||
|
export function useResizable(
|
||
|
target: Ref<HTMLElement | null>,
|
||
|
options: ResizeOptions
|
||
|
) {
|
||
|
const { onResize, onResizeStart, onResizeEnd, initialSize, initialPosition, constraints, enabled } = options;
|
||
|
|
||
|
let activeHandle: string | null = null;
|
||
|
let initialMouseX = 0;
|
||
|
let initialMouseY = 0;
|
||
|
let initialRect = { x: 0, y: 0, width: 0, height: 0 };
|
||
|
|
||
|
const handleMouseDown = (event: MouseEvent) => {
|
||
|
if (!enabled.value) return;
|
||
|
|
||
|
const handle = (event.target as HTMLElement).dataset.direction;
|
||
|
if (!handle) return;
|
||
|
|
||
|
event.preventDefault();
|
||
|
event.stopPropagation();
|
||
|
|
||
|
if (onResizeStart) onResizeStart();
|
||
|
|
||
|
activeHandle = handle;
|
||
|
initialMouseX = event.clientX;
|
||
|
initialMouseY = event.clientY;
|
||
|
initialRect = {
|
||
|
...initialSize,
|
||
|
...initialPosition,
|
||
|
};
|
||
|
|
||
|
document.addEventListener('mousemove', handleMouseMove);
|
||
|
document.addEventListener('mouseup', handleMouseUp);
|
||
|
};
|
||
|
|
||
|
const handleMouseMove = (event: MouseEvent) => {
|
||
|
if (!activeHandle) return;
|
||
|
|
||
|
const dx = event.clientX - initialMouseX;
|
||
|
const dy = event.clientY - initialMouseY;
|
||
|
|
||
|
let { x, y, width, height } = initialRect;
|
||
|
|
||
|
if (activeHandle.includes('e')) {
|
||
|
width = initialRect.width + dx;
|
||
|
}
|
||
|
if (activeHandle.includes('w')) {
|
||
|
width = initialRect.width - dx;
|
||
|
x = initialRect.x + dx;
|
||
|
}
|
||
|
if (activeHandle.includes('s')) {
|
||
|
height = initialRect.height + dy;
|
||
|
}
|
||
|
if (activeHandle.includes('n')) {
|
||
|
height = initialRect.height - dy;
|
||
|
y = initialRect.y + dy;
|
||
|
}
|
||
|
|
||
|
if (constraints?.value) {
|
||
|
const { top, right, bottom, left } = constraints.value;
|
||
|
if (x < left) { width -= (left - x); x = left; }
|
||
|
if (y < top) { height -= (top - y); y = top; }
|
||
|
if (x + width > right) { width = right - x; }
|
||
|
if (y + height > bottom) { height = bottom - y; }
|
||
|
}
|
||
|
|
||
|
width = Math.max(200, width);
|
||
|
height = Math.max(150, height);
|
||
|
|
||
|
onResize({ x, y, width, height });
|
||
|
};
|
||
|
|
||
|
const handleMouseUp = () => {
|
||
|
if (onResizeEnd) onResizeEnd();
|
||
|
|
||
|
activeHandle = null;
|
||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||
|
};
|
||
|
|
||
|
const addListener = () => {
|
||
|
if (target.value) {
|
||
|
target.value.addEventListener('mousedown', handleMouseDown);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const removeListener = () => {
|
||
|
if (target.value) {
|
||
|
target.value.removeEventListener('mousedown', handleMouseDown);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// 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);
|
||
|
});
|
||
|
}
|