import React, { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { bindPaletteDragOverlayElement, notifyPaletteDragCancel, notifyPaletteDragEnd, notifyPaletteDragMove, registerPaletteDragOverlay, subscribePaletteDrag, type PaletteDragOverlaySession, } from '../../utils/paletteDragSession'; import { clientXYFromReact } from '../../utils/pointerDrag'; /** * pointer/touch 드래그 overlay (sessionRef 로 stale closure 방지, window touch 백업과 연동) */ export default function PaletteDragOverlay() { const [session, setSession] = useState(null); const sessionRef = useRef(null); const labelRef = useRef(null); sessionRef.current = session; useEffect(() => { registerPaletteDragOverlay({ mount: next => { sessionRef.current = next; setSession(next); }, unmount: () => { sessionRef.current = null; setSession(null); bindPaletteDragOverlayElement(null); }, bindElement: () => { /* ref callback 에서 처리 */ }, }); return () => registerPaletteDragOverlay(null); }, []); useEffect(() => { return subscribePaletteDrag(ev => { if (ev.phase !== 'move' && ev.phase !== 'start') return; const label = labelRef.current; if (!label) return; label.style.left = `${ev.x}px`; label.style.top = `${ev.y}px`; }); }, []); const overlayRefCallback = useCallback((el: HTMLDivElement | null) => { bindPaletteDragOverlayElement(el); const s = sessionRef.current; if (el && s) { const label = labelRef.current; if (label) { label.style.left = `${s.x}px`; label.style.top = `${s.y}px`; } } }, []); const onPointerMove = useCallback((e: React.PointerEvent) => { const s = sessionRef.current; if (!s || e.pointerId !== s.pointerId) return; e.preventDefault(); notifyPaletteDragMove(e.clientX, e.clientY); }, []); const onPointerUp = useCallback((e: React.PointerEvent) => { const s = sessionRef.current; if (!s || e.pointerId !== s.pointerId) return; e.preventDefault(); notifyPaletteDragEnd(e.clientX, e.clientY); }, []); const onPointerCancel = useCallback((e: React.PointerEvent) => { const s = sessionRef.current; if (!s || e.pointerId !== s.pointerId) return; e.preventDefault(); notifyPaletteDragCancel(e.clientX, e.clientY); }, []); const onTouchMove = useCallback((e: React.TouchEvent) => { const s = sessionRef.current; if (!s || e.touches.length !== 1) return; e.preventDefault(); const { x, y } = clientXYFromReact(e); notifyPaletteDragMove(x, y); }, []); const onTouchEnd = useCallback((e: React.TouchEvent) => { const s = sessionRef.current; if (!s) return; const { x, y } = clientXYFromReact(e); e.preventDefault(); notifyPaletteDragEnd(x, y); }, []); const onTouchCancel = useCallback((e: React.TouchEvent) => { const s = sessionRef.current; if (!s) return; const { x, y } = clientXYFromReact(e); e.preventDefault(); notifyPaletteDragCancel(x, y); }, []); if (!session) return null; return createPortal(
{session.payload.label}
, document.body, ); }