터치로 전략컨트롤 동작
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
handlePalettePointerDown,
|
||||
handlePaletteTouchStart,
|
||||
needsPointerPaletteDrag,
|
||||
shouldSuppressPaletteClick,
|
||||
writePaletteHtmlDragData,
|
||||
@@ -55,6 +56,10 @@ export default function PaletteChip({
|
||||
handlePalettePointerDown(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
||||
};
|
||||
|
||||
const onTouchStart = (e: React.TouchEvent) => {
|
||||
handlePaletteTouchStart(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
if (shouldSuppressPaletteClick()) return;
|
||||
if (onSelect) onSelect();
|
||||
@@ -83,6 +88,7 @@ export default function PaletteChip({
|
||||
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`}
|
||||
onDragStart={onDragStart}
|
||||
onPointerDown={onPointerDown}
|
||||
onTouchStart={onTouchStart}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
role="button"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
|
||||
import type { DefType } from '../../utils/strategyEditorShared';
|
||||
import {
|
||||
handlePalettePointerDown,
|
||||
handlePaletteTouchStart,
|
||||
needsPointerPaletteDrag,
|
||||
writePaletteHtmlDragData,
|
||||
type PaletteDragPayload,
|
||||
@@ -94,6 +95,16 @@ function SidewaysFilterChip({
|
||||
handlePalettePointerDown(e, payload, item.label);
|
||||
};
|
||||
|
||||
const onTouchStart = (e: React.TouchEvent) => {
|
||||
hideHint();
|
||||
const payload: PaletteDragPayload = {
|
||||
type: 'sidewaysFilter',
|
||||
value: item.id,
|
||||
label: item.label,
|
||||
};
|
||||
handlePaletteTouchStart(e, payload, item.label);
|
||||
};
|
||||
|
||||
const handleClick = () => onSelect();
|
||||
const handleDoubleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -113,6 +124,7 @@ function SidewaysFilterChip({
|
||||
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`}
|
||||
onDragStart={onDragStart}
|
||||
onPointerDown={onPointerDown}
|
||||
onTouchStart={onTouchStart}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onMouseEnter={e => showHint(e.currentTarget)}
|
||||
|
||||
Reference in New Issue
Block a user