From c7d4021bdea316d07b50706e0b8048eeedf74537 Mon Sep 17 00:00:00 2001 From: rahim Date: Tue, 14 Apr 2026 12:26:32 -0700 Subject: [PATCH] fix(core): ignore non-primary pointer buttons in tap gesture (#1329) --- packages/core/src/dom/gesture/coordinator.ts | 4 ++- .../src/dom/gesture/tests/gesture.test.ts | 33 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/core/src/dom/gesture/coordinator.ts b/packages/core/src/dom/gesture/coordinator.ts index d8d8c6d9..ae34291e 100644 --- a/packages/core/src/dom/gesture/coordinator.ts +++ b/packages/core/src/dom/gesture/coordinator.ts @@ -48,7 +48,8 @@ export class GestureCoordinator { listen( this.#target, 'pointerdown', - () => { + (event) => { + if (event.button !== 0) return; pointerDownTime = Date.now(); }, { signal } @@ -58,6 +59,7 @@ export class GestureCoordinator { this.#target, 'pointerup', (event) => { + if (event.button !== 0) return; if (Date.now() - pointerDownTime > TAP_THRESHOLD) return; if (isInteractiveTarget(event)) return; diff --git a/packages/core/src/dom/gesture/tests/gesture.test.ts b/packages/core/src/dom/gesture/tests/gesture.test.ts index 2b108033..dc9703a4 100644 --- a/packages/core/src/dom/gesture/tests/gesture.test.ts +++ b/packages/core/src/dom/gesture/tests/gesture.test.ts @@ -48,6 +48,30 @@ describe('createTapGesture', () => { expect(handler).not.toHaveBeenCalled(); }); + it('does not fire on secondary button (right-click)', () => { + const container = setup(); + const handler = vi.fn(); + createTapGesture(container, handler); + + pointerDown(container, { button: 2 }); + vi.advanceTimersByTime(50); + pointerUp(container, { pointerType: 'mouse', clientX: 150, button: 2 }); + + expect(handler).not.toHaveBeenCalled(); + }); + + it('does not fire on auxiliary button (middle-click)', () => { + const container = setup(); + const handler = vi.fn(); + createTapGesture(container, handler); + + pointerDown(container, { button: 1 }); + vi.advanceTimersByTime(50); + pointerUp(container, { pointerType: 'mouse', clientX: 150, button: 1 }); + + expect(handler).not.toHaveBeenCalled(); + }); + it('does not fire when disabled', () => { const container = setup(); const handler = vi.fn(); @@ -456,13 +480,16 @@ describe('interactive child filtering', () => { // Helpers // --------------------------------------------------------------------------- -function pointerDown(target: HTMLElement): void { - target.dispatchEvent(new Event('pointerdown', { bubbles: true })); +function pointerDown(target: HTMLElement, init: { button?: number } = {}): void { + const event = new Event('pointerdown', { bubbles: true }); + Object.defineProperty(event, 'button', { value: init.button ?? 0 }); + target.dispatchEvent(event); } -function pointerUp(target: HTMLElement, init: { pointerType: string; clientX: number }): void { +function pointerUp(target: HTMLElement, init: { pointerType: string; clientX: number; button?: number }): void { const event = new Event('pointerup', { bubbles: true }); Object.defineProperty(event, 'pointerType', { value: init.pointerType }); Object.defineProperty(event, 'clientX', { value: init.clientX }); + Object.defineProperty(event, 'button', { value: init.button ?? 0 }); target.dispatchEvent(event); }