From 374964c37d3c0e5521c8e04c9c97e6c2646b136f Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Wed, 17 Jun 2026 16:46:56 -0700 Subject: [PATCH 1/2] fix: make sure trackpad taps are not considered as virtual clicks --- .../react-spectrum/test/list/ListView.test.js | 10 +++ .../react-spectrum/test/table/TableTests.js | 10 +++ .../react-aria/src/utils/isVirtualEvent.ts | 3 +- .../test/interactions/usePress.test.js | 85 +++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/packages/@adobe/react-spectrum/test/list/ListView.test.js b/packages/@adobe/react-spectrum/test/list/ListView.test.js index 1ba27cab1a2..3f096884cb0 100644 --- a/packages/@adobe/react-spectrum/test/list/ListView.test.js +++ b/packages/@adobe/react-spectrum/test/list/ListView.test.js @@ -1225,6 +1225,11 @@ describe('ListView', function () { }); it("should support single tap to perform row selection with screen reader if onAction isn't provided", async function () { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest + .spyOn(navigator, 'userAgent', 'get') + .mockImplementation(() => 'Android'); + let tree = renderSelectionList({ onSelectionChange, selectionMode: 'multiple', @@ -1281,6 +1286,11 @@ describe('ListView', function () { }); it('should support single tap to perform onAction with screen reader', async function () { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest + .spyOn(navigator, 'userAgent', 'get') + .mockImplementation(() => 'Android'); + let tree = renderSelectionList({ onSelectionChange, selectionMode: 'multiple', diff --git a/packages/@adobe/react-spectrum/test/table/TableTests.js b/packages/@adobe/react-spectrum/test/table/TableTests.js index 25e502c6bdd..c1649a821fa 100644 --- a/packages/@adobe/react-spectrum/test/table/TableTests.js +++ b/packages/@adobe/react-spectrum/test/table/TableTests.js @@ -3414,6 +3414,11 @@ export let tableTests = () => { describe('needs pointerEvents', function () { installPointerEvent(); it("should support single tap to perform row selection with screen reader if onAction isn't provided", function () { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest + .spyOn(navigator, 'userAgent', 'get') + .mockImplementation(() => 'Android'); + let onSelectionChange = jest.fn(); let tree = renderTable({onSelectionChange, selectionStyle: 'highlight'}); @@ -3482,6 +3487,11 @@ export let tableTests = () => { }); it('should support single tap to perform onAction with screen reader', function () { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest + .spyOn(navigator, 'userAgent', 'get') + .mockImplementation(() => 'Android'); + let onSelectionChange = jest.fn(); let onAction = jest.fn(); let tree = renderTable({onSelectionChange, selectionStyle: 'highlight', onAction}); diff --git a/packages/react-aria/src/utils/isVirtualEvent.ts b/packages/react-aria/src/utils/isVirtualEvent.ts index 77053781f1e..29dc6ffaf36 100644 --- a/packages/react-aria/src/utils/isVirtualEvent.ts +++ b/packages/react-aria/src/utils/isVirtualEvent.ts @@ -48,7 +48,8 @@ export function isVirtualPointerEvent(event: PointerEvent): boolean { // Talkback double tap from Windows Firefox touch screen press return ( (!isAndroid() && event.width === 0 && event.height === 0) || - (event.width === 1 && + (isAndroid() && + event.width === 1 && event.height === 1 && event.pressure === 0 && event.detail === 0 && diff --git a/packages/react-aria/test/interactions/usePress.test.js b/packages/react-aria/test/interactions/usePress.test.js index 4aa431633c2..2ac25bcc4d8 100644 --- a/packages/react-aria/test/interactions/usePress.test.js +++ b/packages/react-aria/test/interactions/usePress.test.js @@ -1218,6 +1218,9 @@ describe('usePress', function () { }); it('should detect Android TalkBack double tap', function () { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest.spyOn(navigator, 'userAgent', 'get').mockImplementation(() => 'Android'); + let events = []; let addEvent = e => events.push(e); let res = render( @@ -1311,6 +1314,45 @@ describe('usePress', function () { ]); }); + it('should fire if pressure is 0 but is not android', function () { + let events = []; + let addEvent = e => events.push(e); + let res = render( + addEvent({type: e.type, target: e.target})} + /> + ); + + let el = res.getByText('test'); + fireEvent( + el, + pointerEvent('pointerdown', { + pointerId: 1, + width: 1, + height: 1, + pressure: 0, + detail: 0, + pointerType: 'mouse' + }) + ); + fireEvent( + el, + pointerEvent('pointerup', { + pointerId: 1, + width: 1, + height: 1, + pressure: 0, + detail: 0, + pointerType: 'mouse' + }) + ); + expect(events).not.toEqual([]); + }); + it('should not fire press/click events for disabled elements', function () { let events = []; let addEvent = e => events.push(e); @@ -5038,6 +5080,9 @@ describe('usePress', function () { }); it('should detect Android TalkBack double tap', function () { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest.spyOn(navigator, 'userAgent', 'get').mockImplementation(() => 'Android'); + const shadowRoot = setupShadowDOMTest({onPressChange: null}); const el = shadowRoot.getElementById('testElement'); @@ -5111,6 +5156,46 @@ describe('usePress', function () { ]); }); + it('should fire if pressure is 0 but is not android', function () { + let events = []; + let addEvent = e => events.push(e); + let res = render( + addEvent({type: e.type, target: e.target})} + /> + ); + + let el = res.getByText('test'); + fireEvent( + el, + pointerEvent('pointerdown', { + pointerId: 1, + width: 1, + height: 1, + pressure: 0, + detail: 0, + pointerType: 'mouse' + }) + ); + fireEvent( + el, + pointerEvent('pointerup', { + pointerId: 1, + width: 1, + height: 1, + pressure: 0, + detail: 0, + pointerType: 'mouse' + }) + ); + + expect(events).not.toEqual([]); + }); + it('should not fire press/click events for disabled elements', function () { const shadowRoot = setupShadowDOMTest({ isDisabled: true, From cbef46e14576eebb72216a5286aa2d585219ae5f Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Wed, 17 Jun 2026 16:55:58 -0700 Subject: [PATCH 2/2] didnt save? --- packages/react-aria/test/dnd/dnd.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/react-aria/test/dnd/dnd.test.js b/packages/react-aria/test/dnd/dnd.test.js index 848278cc8a3..20295155174 100644 --- a/packages/react-aria/test/dnd/dnd.test.js +++ b/packages/react-aria/test/dnd/dnd.test.js @@ -2882,6 +2882,9 @@ describe('useDrag and useDrop', function () { }); it('should support clicking the original drag target to cancel drag (virtual pointer event)', async () => { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest.spyOn(navigator, 'userAgent', 'get').mockImplementation(() => 'Android'); + let tree = render( <> @@ -2942,6 +2945,9 @@ describe('useDrag and useDrop', function () { }); it('should support double tapping the drop target to complete drag (virtual pointer event)', async () => { + // oxlint-disable-next-line no-unused-vars + using uaMock = jest.spyOn(navigator, 'userAgent', 'get').mockImplementation(() => 'Android'); + let onDrop = jest.fn(); let tree = render( <>