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.
master
a2nr 2026-04-09 11:04:05 +07:00
parent 52321dffe8
commit 1973b0adbc
2 changed files with 27 additions and 11 deletions

View File

@ -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;

View File

@ -84,6 +84,7 @@ export const WireLayer: React.FC<WireLayerProps> = ({
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)}
/>