fix: drawing tools use pointer events for mouse+touch support
- InteractionEngine: pointerdown on canvas, pointermove/pointerup on document - Pointer capture for smooth dragging outside canvas - CSS: touch-action: none on canvas to prevent browser touch interception - Fixes: tools not working on desktop (mouseup lost outside canvas) and mobile (no touch support)
This commit is contained in:
@@ -55,6 +55,9 @@ export class InteractionEngine {
|
|||||||
private onTextEdit?: (el: CADElement) => void;
|
private onTextEdit?: (el: CADElement) => void;
|
||||||
private onSelectionChange?: (selectedIds: string[]) => void;
|
private onSelectionChange?: (selectedIds: string[]) => void;
|
||||||
private animationFrame: number | null = null;
|
private animationFrame: number | null = null;
|
||||||
|
private boundKeyDown: EventListener;
|
||||||
|
private boundMouseMove: EventListener;
|
||||||
|
private boundMouseUp: EventListener;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
@@ -89,6 +92,9 @@ export class InteractionEngine {
|
|||||||
undoKey: 'z',
|
undoKey: 'z',
|
||||||
redoKey: 'y',
|
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 {
|
setElements(elements: CADElement[]): void {
|
||||||
@@ -156,13 +162,13 @@ export class InteractionEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attach(): void {
|
attach(): void {
|
||||||
this.addListener('mousedown', this.onMouseDown.bind(this) as EventListener);
|
this.addListener('pointerdown', this.onMouseDown.bind(this) as EventListener);
|
||||||
this.addListener('mousemove', this.onMouseMove.bind(this) as EventListener);
|
document.addEventListener('pointermove', this.boundMouseMove);
|
||||||
this.addListener('mouseup', this.onMouseUp.bind(this) as EventListener);
|
document.addEventListener('pointerup', this.boundMouseUp);
|
||||||
this.addListener('dblclick', this.onDoubleClick.bind(this) as EventListener);
|
this.addListener('dblclick', this.onDoubleClick.bind(this) as EventListener);
|
||||||
this.addListener('wheel', this.onWheel.bind(this) as EventListener, { passive: false } as AddEventListenerOptions);
|
this.addListener('wheel', this.onWheel.bind(this) as EventListener, { passive: false } as AddEventListenerOptions);
|
||||||
this.addListener('contextmenu', this.onContextMenu.bind(this) as EventListener);
|
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 {
|
detach(): void {
|
||||||
@@ -170,7 +176,9 @@ export class InteractionEngine {
|
|||||||
this.canvas.removeEventListener(type, fn);
|
this.canvas.removeEventListener(type, fn);
|
||||||
}
|
}
|
||||||
this.eventListeners = [];
|
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) {
|
if (this.animationFrame !== null) {
|
||||||
cancelAnimationFrame(this.animationFrame);
|
cancelAnimationFrame(this.animationFrame);
|
||||||
this.animationFrame = null;
|
this.animationFrame = null;
|
||||||
@@ -215,6 +223,7 @@ export class InteractionEngine {
|
|||||||
|
|
||||||
private onMouseDown(e: MouseEvent): void {
|
private onMouseDown(e: MouseEvent): void {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
try { this.canvas.setPointerCapture((e as any).pointerId); } catch {}
|
||||||
this.isMouseDown = true;
|
this.isMouseDown = true;
|
||||||
this.lastMouseScreen = { x: e.clientX, y: e.clientY };
|
this.lastMouseScreen = { x: e.clientX, y: e.clientY };
|
||||||
|
|
||||||
@@ -289,6 +298,10 @@ export class InteractionEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onMouseMove(e: MouseEvent): void {
|
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 };
|
this.lastMouseScreen = { x: e.clientX, y: e.clientY };
|
||||||
const raw = this.getWorldCoords(e);
|
const raw = this.getWorldCoords(e);
|
||||||
this.onCursorMoved?.(raw.x, raw.y);
|
this.onCursorMoved?.(raw.x, raw.y);
|
||||||
@@ -330,6 +343,7 @@ export class InteractionEngine {
|
|||||||
|
|
||||||
private onMouseUp(e: MouseEvent): void {
|
private onMouseUp(e: MouseEvent): void {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
try { this.canvas.releasePointerCapture((e as any).pointerId); } catch {}
|
||||||
this.isMouseDown = false;
|
this.isMouseDown = false;
|
||||||
|
|
||||||
if (e.button === 2 || (e.button === 1 && this.state.phase === 'panning')) {
|
if (e.button === 2 || (e.button === 1 && this.state.phase === 'panning')) {
|
||||||
|
|||||||
@@ -1458,7 +1458,7 @@ a:hover { text-decoration: underline; }
|
|||||||
.status-item.hide-mobile { display: none; }
|
.status-item.hide-mobile { display: none; }
|
||||||
|
|
||||||
/* SVG canvas: ensure it's scrollable/zoomable on touch */
|
/* 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) */
|
/* Very small screens (<480px) */
|
||||||
|
|||||||
Reference in New Issue
Block a user