터치로 전략컨트롤 동작
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
bindPaletteDragOverlayElement,
|
||||
@@ -6,53 +6,67 @@ import {
|
||||
notifyPaletteDragEnd,
|
||||
notifyPaletteDragMove,
|
||||
registerPaletteDragOverlay,
|
||||
subscribePaletteDrag,
|
||||
type PaletteDragOverlaySession,
|
||||
} from '../../utils/paletteDragSession';
|
||||
import { clientXYFromReact } from '../../utils/pointerDrag';
|
||||
|
||||
/**
|
||||
* Tauri WebView — 드래그 overlay (라벨 위치는 ref 갱신, move 마다 React re-render 없음)
|
||||
* pointer/touch 드래그 overlay (sessionRef 로 stale closure 방지, window touch 백업과 연동)
|
||||
*/
|
||||
export default function PaletteDragOverlay() {
|
||||
const [session, setSession] = useState<PaletteDragOverlaySession | null>(null);
|
||||
const overlayRef = useRef<HTMLDivElement | null>(null);
|
||||
const sessionRef = useRef<PaletteDragOverlaySession | null>(null);
|
||||
const labelRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
sessionRef.current = session;
|
||||
|
||||
useEffect(() => {
|
||||
registerPaletteDragOverlay({
|
||||
mount: next => {
|
||||
sessionRef.current = next;
|
||||
setSession(next);
|
||||
requestAnimationFrame(() => {
|
||||
const label = labelRef.current;
|
||||
if (label) {
|
||||
label.style.left = `${next.x}px`;
|
||||
label.style.top = `${next.y}px`;
|
||||
}
|
||||
});
|
||||
},
|
||||
unmount: () => setSession(null),
|
||||
bindElement: el => {
|
||||
overlayRef.current = el as HTMLDivElement | null;
|
||||
unmount: () => {
|
||||
sessionRef.current = null;
|
||||
setSession(null);
|
||||
bindPaletteDragOverlayElement(null);
|
||||
},
|
||||
bindElement: () => { /* ref callback 에서 처리 */ },
|
||||
});
|
||||
return () => registerPaletteDragOverlay(null);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
bindPaletteDragOverlayElement(overlayRef.current);
|
||||
return () => bindPaletteDragOverlayElement(null);
|
||||
}, [session]);
|
||||
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`;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!session || !overlayRef.current) return;
|
||||
try {
|
||||
overlayRef.current.setPointerCapture(session.pointerId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
const overlayRefCallback = useCallback((el: HTMLDivElement | null) => {
|
||||
bindPaletteDragOverlayElement(el);
|
||||
const s = sessionRef.current;
|
||||
if (el && s) {
|
||||
try {
|
||||
el.setPointerCapture(s.pointerId);
|
||||
} catch {
|
||||
/* window backup in paletteDragSession */
|
||||
}
|
||||
const label = labelRef.current;
|
||||
if (label) {
|
||||
label.style.left = `${s.x}px`;
|
||||
label.style.top = `${s.y}px`;
|
||||
}
|
||||
}
|
||||
}, [session]);
|
||||
}, []);
|
||||
|
||||
const onPointerMove = useCallback((e: React.PointerEvent) => {
|
||||
if (!session || e.pointerId !== session.pointerId) return;
|
||||
const s = sessionRef.current;
|
||||
if (!s || e.pointerId !== s.pointerId) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const label = labelRef.current;
|
||||
@@ -61,31 +75,60 @@ export default function PaletteDragOverlay() {
|
||||
label.style.top = `${e.clientY}px`;
|
||||
}
|
||||
notifyPaletteDragMove(e.clientX, e.clientY);
|
||||
}, [session]);
|
||||
}, []);
|
||||
|
||||
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
||||
if (!session || e.pointerId !== session.pointerId) return;
|
||||
const s = sessionRef.current;
|
||||
if (!s || e.pointerId !== s.pointerId) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
notifyPaletteDragEnd(e.clientX, e.clientY);
|
||||
}, [session]);
|
||||
}, []);
|
||||
|
||||
const onPointerCancel = useCallback((e: React.PointerEvent) => {
|
||||
if (!session || e.pointerId !== session.pointerId) return;
|
||||
const s = sessionRef.current;
|
||||
if (!s || e.pointerId !== s.pointerId) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
notifyPaletteDragCancel(e.clientX, e.clientY);
|
||||
}, [session]);
|
||||
}, []);
|
||||
|
||||
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(
|
||||
<div
|
||||
ref={overlayRef}
|
||||
ref={overlayRefCallback}
|
||||
className="se-palette-drag-overlay"
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerCancel}
|
||||
onTouchMove={onTouchMove}
|
||||
onTouchEnd={onTouchEnd}
|
||||
onTouchCancel={onTouchCancel}
|
||||
>
|
||||
<div
|
||||
ref={labelRef}
|
||||
|
||||
Reference in New Issue
Block a user