diff --git a/frontend/src/interaction/index.ts b/frontend/src/interaction/index.ts index 54f1a72..138b666 100644 --- a/frontend/src/interaction/index.ts +++ b/frontend/src/interaction/index.ts @@ -55,6 +55,9 @@ export class InteractionEngine { private onTextEdit?: (el: CADElement) => void; private onSelectionChange?: (selectedIds: string[]) => void; private animationFrame: number | null = null; + private boundKeyDown: EventListener; + private boundMouseMove: EventListener; + private boundMouseUp: EventListener; constructor( canvas: HTMLCanvasElement, @@ -89,6 +92,9 @@ export class InteractionEngine { undoKey: 'z', redoKey: 'y', }; + this.boundKeyDown = this.onKeyDown.bind(this) as EventListener; + this.boundMouseMove = this.onMouseMove.bind(this) as EventListener; + this.boundMouseUp = this.onMouseUp.bind(this) as EventListener; } setElements(elements: CADElement[]): void { @@ -156,13 +162,13 @@ export class InteractionEngine { } attach(): void { - this.addListener('mousedown', this.onMouseDown.bind(this) as EventListener); - this.addListener('mousemove', this.onMouseMove.bind(this) as EventListener); - this.addListener('mouseup', this.onMouseUp.bind(this) as EventListener); + this.addListener('pointerdown', this.onMouseDown.bind(this) as EventListener); + document.addEventListener('pointermove', this.boundMouseMove); + document.addEventListener('pointerup', this.boundMouseUp); this.addListener('dblclick', this.onDoubleClick.bind(this) as EventListener); this.addListener('wheel', this.onWheel.bind(this) as EventListener, { passive: false } as AddEventListenerOptions); this.addListener('contextmenu', this.onContextMenu.bind(this) as EventListener); - document.addEventListener('keydown', this.onKeyDown.bind(this) as EventListener); + document.addEventListener('keydown', this.boundKeyDown); } detach(): void { @@ -170,7 +176,9 @@ export class InteractionEngine { this.canvas.removeEventListener(type, fn); } this.eventListeners = []; - document.removeEventListener('keydown', this.onKeyDown.bind(this) as EventListener); + document.removeEventListener('pointermove', this.boundMouseMove); + document.removeEventListener('pointerup', this.boundMouseUp); + document.removeEventListener('keydown', this.boundKeyDown); if (this.animationFrame !== null) { cancelAnimationFrame(this.animationFrame); this.animationFrame = null; @@ -215,6 +223,7 @@ export class InteractionEngine { private onMouseDown(e: MouseEvent): void { e.preventDefault(); + try { this.canvas.setPointerCapture((e as any).pointerId); } catch {} this.isMouseDown = true; this.lastMouseScreen = { x: e.clientX, y: e.clientY }; @@ -289,6 +298,10 @@ export class InteractionEngine { } private onMouseMove(e: MouseEvent): void { + if (this.state.phase === 'idle' && !this.isMouseDown) { + const rect = this.canvas.getBoundingClientRect(); + if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) return; + } this.lastMouseScreen = { x: e.clientX, y: e.clientY }; const raw = this.getWorldCoords(e); this.onCursorMoved?.(raw.x, raw.y); @@ -330,6 +343,7 @@ export class InteractionEngine { private onMouseUp(e: MouseEvent): void { e.preventDefault(); + try { this.canvas.releasePointerCapture((e as any).pointerId); } catch {} this.isMouseDown = false; if (e.button === 2 || (e.button === 1 && this.state.phase === 'panning')) { diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 710478f..ac1db63 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1458,7 +1458,7 @@ a:hover { text-decoration: underline; } .status-item.hide-mobile { display: none; } /* SVG canvas: ensure it's scrollable/zoomable on touch */ - .canvas-svg { touch-action: pinch-zoom pan-x pan-y; } + .canvas-svg { touch-action: none; } } /* Very small screens (<480px) */