From d9ef377c2ab9e9ae188324d5881be773b36412d1 Mon Sep 17 00:00:00 2001 From: Vaclav Vancura Date: Tue, 5 May 2026 17:10:57 +0200 Subject: [PATCH] fix(input): release pointer captures on detach and record final mouse position on pointerup In detach(), iterate pointerIdToSlot and call releasePointerCapture() for each active capture before removing event listeners. Without this, any in-flight touch or pen contacts leave dangling browser-side capture state when the input system is torn down. In handlePointerUp(), call updateSlotPosition() before setMouseButton() for mouse events, matching the existing touch/pen path. The terminal position from the pointerup event was previously discarded, leaving the slot with a stale coordinate on the release frame. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Vaclav Vancura --- src/input/PointerInput.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/input/PointerInput.ts b/src/input/PointerInput.ts index c061eeb..0a13ac8 100644 --- a/src/input/PointerInput.ts +++ b/src/input/PointerInput.ts @@ -214,6 +214,16 @@ export class PointerInput { const canvas = this.canvas; if (canvas !== null) { + for (const [pointerId] of this.pointerIdToSlot) { + try { + if (canvas.hasPointerCapture(pointerId)) { + canvas.releasePointerCapture(pointerId); + } + } catch { + // Element may have been removed from the DOM already. + } + } + canvas.removeEventListener('pointermove', this.onPointerMove); canvas.removeEventListener('pointerdown', this.onPointerDown); canvas.removeEventListener('pointerup', this.onPointerUp); @@ -579,6 +589,7 @@ export class PointerInput { if (event.pointerType === 'mouse') { const slot = this.slots[0]; + this.updateSlotPosition(slot, event.clientX, event.clientY); this.setMouseButton(slot, event.button, false); return; -- 2.51.2