diff --git a/packages/hooks/src/useLongPress/__tests__/index.spec.ts b/packages/hooks/src/useLongPress/__tests__/index.spec.ts index 1232a051ec..68ae16ce65 100644 --- a/packages/hooks/src/useLongPress/__tests__/index.spec.ts +++ b/packages/hooks/src/useLongPress/__tests__/index.spec.ts @@ -100,13 +100,79 @@ describe('useLongPress', () => { }); test(`should not work when target don't support addEventListener method`, () => { - Object.defineProperty(mockTarget, 'addEventListener', { - get() { - return false; - }, - }); + const customTarget = { + addEventListener: false, + removeEventListener: vi.fn(), + }; - setup(() => {}, mockTarget); + setup(() => {}, customTarget as any); expect(Object.keys(events)).toHaveLength(0); }); + + test('should ignore right-click (button 2)', () => { + setup(mockCallback, mockTarget, { + onClick: mockClickCallback, + onLongPressEnd: mockLongPressEndCallback, + }); + expect(mockTarget.addEventListener).toBeCalled(); + // Simulate right-click + events['mousedown'](new MouseEvent('mousedown', { button: 2 })); + vi.advanceTimersByTime(350); + events['mouseup'](new MouseEvent('mouseup', { button: 2 })); + expect(mockCallback).toBeCalledTimes(0); + expect(mockLongPressEndCallback).toBeCalledTimes(0); + expect(mockClickCallback).toBeCalledTimes(0); + }); + + test('should ignore middle-click (button 1)', () => { + setup(mockCallback, mockTarget, { + onClick: mockClickCallback, + onLongPressEnd: mockLongPressEndCallback, + }); + expect(mockTarget.addEventListener).toBeCalled(); + // Simulate middle-click + events['mousedown'](new MouseEvent('mousedown', { button: 1 })); + vi.advanceTimersByTime(350); + events['mouseup'](new MouseEvent('mouseup', { button: 1 })); + expect(mockCallback).toBeCalledTimes(0); + expect(mockLongPressEndCallback).toBeCalledTimes(0); + expect(mockClickCallback).toBeCalledTimes(0); + }); + + test('should prevent context menu when long press is triggered', () => { + setup(mockCallback, mockTarget, { + onClick: mockClickCallback, + onLongPressEnd: mockLongPressEndCallback, + }); + expect(mockTarget.addEventListener).toBeCalled(); + expect(events['contextmenu']).toBeDefined(); + + // Trigger long press + events['mousedown'](new MouseEvent('mousedown', { button: 0 })); + vi.advanceTimersByTime(350); + + // Now context menu event should be prevented + const mockEvent = { preventDefault: vi.fn() }; + events['contextmenu'](mockEvent); + expect(mockEvent.preventDefault).toBeCalledTimes(1); + + events['mouseup'](new MouseEvent('mouseup', { button: 0 })); + }); + + test('should not prevent context menu when long press is not triggered', () => { + setup(mockCallback, mockTarget, { + onClick: mockClickCallback, + onLongPressEnd: mockLongPressEndCallback, + }); + expect(mockTarget.addEventListener).toBeCalled(); + + // Don't trigger long press - just a quick click + events['mousedown'](new MouseEvent('mousedown', { button: 0 })); + events['mouseup'](new MouseEvent('mouseup', { button: 0 })); + + // Context menu event should not be prevented + const mockEvent = { preventDefault: vi.fn() }; + events['contextmenu'](mockEvent); + expect(mockEvent.preventDefault).toBeCalledTimes(0); + }); }); diff --git a/packages/hooks/src/useLongPress/index.ts b/packages/hooks/src/useLongPress/index.ts index 1edcda784c..1a2f8679c8 100644 --- a/packages/hooks/src/useLongPress/index.ts +++ b/packages/hooks/src/useLongPress/index.ts @@ -93,6 +93,13 @@ function useLongPress( return; } + // Only handle left mouse button (button 0) + // Ignore right-click (button 2) and middle-click (button 1) + // If button is undefined (e.g., in tests), default to left button + if (event?.button !== undefined && event.button !== 0) { + return; + } + mousePressed.current = true; if (hasMoveThreshold) { @@ -166,11 +173,19 @@ function useLongPress( } }; + const onContextMenu = (event: Event) => { + // Prevent context menu if long press was triggered + if (isTriggeredRef.current) { + event.preventDefault(); + } + }; + targetElement.addEventListener('mousedown', onMouseDown as EventListener); targetElement.addEventListener('mouseup', onMouseUp as EventListener); targetElement.addEventListener('mouseleave', onMouseLeave as EventListener); targetElement.addEventListener('touchstart', onTouchStart as EventListener); targetElement.addEventListener('touchend', onTouchEnd as EventListener); + targetElement.addEventListener('contextmenu', onContextMenu as EventListener); if (hasMoveThreshold) { targetElement.addEventListener('mousemove', onMove as EventListener); @@ -188,6 +203,7 @@ function useLongPress( targetElement.removeEventListener('mouseleave', onMouseLeave as EventListener); targetElement.removeEventListener('touchstart', onTouchStart as EventListener); targetElement.removeEventListener('touchend', onTouchEnd as EventListener); + targetElement.removeEventListener('contextmenu', onContextMenu as EventListener); if (hasMoveThreshold) { targetElement.removeEventListener('mousemove', onMove as EventListener);