From 1973b0adbca8881e38d5deadde0b8ceebe893d84 Mon Sep 17 00:00:00 2001 From: a2nr Date: Thu, 9 Apr 2026 11:04:05 +0700 Subject: [PATCH] fix(simulator): prevent crosshair from activating during wire segment editing on mobile - Add data-segment-handle attribute to segment handle circles in WireLayer - Skip long-press aiming timer when touch targets segment handle - Add safety checks to cancel pending timer when segment drag starts - Ensure segment drag always takes priority over crosshair activation This fixes the bug where accidentally holding still while dragging a wire segment would activate the crosshair overlay and interrupt the editing workflow. --- .../components/simulator/SimulatorCanvas.tsx | 37 +++++++++++++------ .../src/components/simulator/WireLayer.tsx | 1 + 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index 57f8c78..3e33bd9 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -402,6 +402,9 @@ export const SimulatorCanvas = () => { // Any phase that uses aiming: cancel previous timer first if (wireAimingTimerRef.current) { clearTimeout(wireAimingTimerRef.current); wireAimingTimerRef.current = null; } + // Check if touch is on a segment handle - if so, skip aiming timer (handle drag takes priority) + const isOnSegmentHandle = target?.closest('[data-segment-handle]'); + const aimPhase = wireAimingPhaseRef.current; // Already in an aiming phase (aiming_start or aiming_end) → touch updates crosshair immediately @@ -415,16 +418,21 @@ export const SimulatorCanvas = () => { // wire_started → long press to aim for endpoint, short drag = pan if (aimPhase === 'wire_started') { - wireAimingTimerRef.current = setTimeout(() => { - wireAimingPhaseRef.current = 'aiming_end'; - setWireAiming(true); - if (navigator.vibrate) navigator.vibrate(30); - const world = toWorld(currentTouchRef.current.x, currentTouchRef.current.y); - const aimX = world.x; - const aimY = world.y + AIMING_OFFSET_Y; - setAimPosition({ x: aimX, y: aimY }); - useSimulatorStore.getState().updateWireInProgress(aimX, aimY); - }, AIMING_LONG_PRESS_MS); + // Only start aiming timer if NOT on a segment handle + if (!isOnSegmentHandle) { + wireAimingTimerRef.current = setTimeout(() => { + // Double-check: only activate if segment drag hasn't started + if (segmentDragRef.current) return; + wireAimingPhaseRef.current = 'aiming_end'; + setWireAiming(true); + if (navigator.vibrate) navigator.vibrate(30); + const world = toWorld(currentTouchRef.current.x, currentTouchRef.current.y); + const aimX = world.x; + const aimY = world.y + AIMING_OFFSET_Y; + setAimPosition({ x: aimX, y: aimY }); + useSimulatorStore.getState().updateWireInProgress(aimX, aimY); + }, AIMING_LONG_PRESS_MS); + } isPanningRef.current = true; panStartRef.current = { @@ -479,8 +487,10 @@ export const SimulatorCanvas = () => { } } else { // ── 6. Empty canvas → start pan + long press starts aiming (idle phase only) ── - if (aimPhase === 'idle') { + if (aimPhase === 'idle' && !isOnSegmentHandle) { wireAimingTimerRef.current = setTimeout(() => { + // Double-check: only activate if segment drag hasn't started + if (segmentDragRef.current) return; wireAimingPhaseRef.current = 'aiming_start'; setWireAiming(true); if (navigator.vibrate) navigator.vibrate(30); @@ -538,6 +548,11 @@ export const SimulatorCanvas = () => { // ── Segment drag (wire editing) via touch ── if (segmentDragRef.current) { + // Cancel any pending aiming timer - segment drag takes priority + if (wireAimingTimerRef.current) { + clearTimeout(wireAimingTimerRef.current); + wireAimingTimerRef.current = null; + } const world = toWorld(touch.clientX, touch.clientY); const sd = segmentDragRef.current; sd.isDragging = true; diff --git a/frontend/src/components/simulator/WireLayer.tsx b/frontend/src/components/simulator/WireLayer.tsx index 179c3dc..55b199c 100644 --- a/frontend/src/components/simulator/WireLayer.tsx +++ b/frontend/src/components/simulator/WireLayer.tsx @@ -84,6 +84,7 @@ export const WireLayer: React.FC = ({ stroke="#007acc" strokeWidth={sw} style={{ pointerEvents: 'all', cursor: handle.axis === 'horizontal' ? 'ns-resize' : 'ew-resize', touchAction: 'none' }} + data-segment-handle={handle.segIndex} onMouseDown={(e) => onHandleMouseDown(e, handle.segIndex)} onTouchStart={(e) => onHandleTouchStart?.(e, handle.segIndex)} />