Files
invyone/frontend/components/builder/hooks/useBlockDrag.ts
T
2026-04-10 13:33:37 +09:00

89 lines
3.0 KiB
TypeScript

"use client";
import { useCallback, useRef } from "react";
import { useBuilderState } from "./useBuilderState";
interface DragState {
id: string;
startX: number;
startY: number;
origX: number;
origY: number;
origW: number;
origH: number;
mode: "move" | "resize";
}
/**
* 블록 드래그(이동)/리사이즈 훅.
* mousedown → mousemove → mouseup 패턴.
* Shift 키: 8px 스냅.
*/
export function useBlockDrag() {
const dragRef = useRef<DragState | null>(null);
const moveBlock = useBuilderState((s) => s.moveBlock);
const resizeBlock = useBuilderState((s) => s.resizeBlock);
const selectBlock = useBuilderState((s) => s.selectBlock);
const startDrag = useCallback(
(e: React.MouseEvent, id: string, origX: number, origY: number, origW: number, origH: number) => {
e.preventDefault();
e.stopPropagation();
selectBlock(id);
dragRef.current = { id, startX: e.clientX, startY: e.clientY, origX, origY, origW, origH, mode: "move" };
document.body.style.cursor = "grabbing";
document.body.style.userSelect = "none";
const onMove = (ev: MouseEvent) => {
const d = dragRef.current;
if (!d || d.mode !== "move") return;
let nx = d.origX + (ev.clientX - d.startX);
let ny = d.origY + (ev.clientY - d.startY);
if (ev.shiftKey) { nx = Math.round(nx / 8) * 8; ny = Math.round(ny / 8) * 8; }
moveBlock(d.id, Math.round(nx), Math.round(ny));
};
const onUp = () => {
dragRef.current = null;
document.body.style.cursor = "";
document.body.style.userSelect = "";
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
},
[moveBlock, selectBlock]
);
const startResize = useCallback(
(e: React.MouseEvent, id: string, origX: number, origY: number, origW: number, origH: number) => {
e.preventDefault();
e.stopPropagation();
dragRef.current = { id, startX: e.clientX, startY: e.clientY, origX, origY, origW, origH, mode: "resize" };
document.body.style.cursor = "nwse-resize";
document.body.style.userSelect = "none";
const onMove = (ev: MouseEvent) => {
const d = dragRef.current;
if (!d || d.mode !== "resize") return;
let nw = d.origW + (ev.clientX - d.startX);
let nh = d.origH + (ev.clientY - d.startY);
if (ev.shiftKey) { nw = Math.round(nw / 8) * 8; nh = Math.round(nh / 8) * 8; }
resizeBlock(d.id, Math.round(nw), Math.round(nh));
};
const onUp = () => {
dragRef.current = null;
document.body.style.cursor = "";
document.body.style.userSelect = "";
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
},
[resizeBlock]
);
return { startDrag, startResize };
}