Skip to content

Commit ab480c9

Browse files
authored
fix: Tapping outside overlay triggers elements behind underlay on Android (#8275)
* fix: Tapping outside overlay triggers elements behind underlay on Android * fix tests * fix more tests
1 parent 58e3257 commit ab480c9

File tree

4 files changed

+24
-4
lines changed

4 files changed

+24
-4
lines changed

packages/@react-aria/interactions/src/useInteractOutside.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,22 @@ export function useInteractOutside(props: InteractOutsideProps): void {
6464

6565
// Use pointer events if available. Otherwise, fall back to mouse and touch events.
6666
if (typeof PointerEvent !== 'undefined') {
67-
let onPointerUp = (e) => {
67+
let onClick = (e) => {
6868
if (state.isPointerDown && isValidEvent(e, ref)) {
6969
triggerInteractOutside(e);
7070
}
7171
state.isPointerDown = false;
7272
};
7373

7474
// changing these to capture phase fixed combobox
75+
// Use click instead of pointerup to avoid Android Chrome issue
76+
// https://issues.chromium.org/issues/40732224
7577
documentObject.addEventListener('pointerdown', onPointerDown, true);
76-
documentObject.addEventListener('pointerup', onPointerUp, true);
78+
documentObject.addEventListener('click', onClick, true);
7779

7880
return () => {
7981
documentObject.removeEventListener('pointerdown', onPointerDown, true);
80-
documentObject.removeEventListener('pointerup', onPointerUp, true);
82+
documentObject.removeEventListener('click', onClick, true);
8183
};
8284
} else if (process.env.NODE_ENV === 'test') {
8385
let onMouseUp = (e) => {

packages/@react-aria/interactions/test/useInteractOutside.test.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {fireEvent, installPointerEvent, render, waitFor} from '@react-spectrum/test-utils-internal';
1414
import React, {useEffect, useRef} from 'react';
15-
import ReactDOM, {createPortal, render as ReactDOMRender} from 'react-dom';
15+
import ReactDOM, {createPortal} from 'react-dom';
1616
import {useInteractOutside} from '../';
1717

1818
function Example(props) {
@@ -42,10 +42,12 @@ describe('useInteractOutside', function () {
4242
let el = res.getByText('test');
4343
fireEvent(el, pointerEvent('pointerdown'));
4444
fireEvent(el, pointerEvent('pointerup'));
45+
fireEvent.click(el);
4546
expect(onInteractOutside).not.toHaveBeenCalled();
4647

4748
fireEvent(document.body, pointerEvent('pointerdown'));
4849
fireEvent(document.body, pointerEvent('pointerup'));
50+
fireEvent.click(document.body);
4951
expect(onInteractOutside).toHaveBeenCalledTimes(1);
5052
});
5153

@@ -57,10 +59,12 @@ describe('useInteractOutside', function () {
5759

5860
fireEvent(document.body, pointerEvent('pointerdown', {button: 1}));
5961
fireEvent(document.body, pointerEvent('pointerup', {button: 1}));
62+
fireEvent.click(document.body, {button: 1});
6063
expect(onInteractOutside).not.toHaveBeenCalled();
6164

6265
fireEvent(document.body, pointerEvent('pointerdown', {button: 0}));
6366
fireEvent(document.body, pointerEvent('pointerup', {button: 0}));
67+
fireEvent.click(document.body, {button: 0});
6468
expect(onInteractOutside).toHaveBeenCalledTimes(1);
6569
});
6670

@@ -74,6 +78,7 @@ describe('useInteractOutside', function () {
7478
);
7579

7680
fireEvent(document.body, pointerEvent('pointerup'));
81+
fireEvent.click(document.body);
7782
expect(onInteractOutside).not.toHaveBeenCalled();
7883
});
7984
});
@@ -246,10 +251,12 @@ describe('useInteractOutside (iframes)', function () {
246251
const el = document.querySelector('iframe').contentWindow.document.body.querySelector('div[data-testid="example"]');
247252
fireEvent(el, pointerEvent('pointerdown'));
248253
fireEvent(el, pointerEvent('pointerup'));
254+
fireEvent.click(el);
249255
expect(onInteractOutside).not.toHaveBeenCalled();
250256

251257
fireEvent(iframeDocument.body, pointerEvent('pointerdown'));
252258
fireEvent(iframeDocument.body, pointerEvent('pointerup'));
259+
fireEvent.click(iframeDocument.body);
253260
expect(onInteractOutside).toHaveBeenCalledTimes(1);
254261
});
255262

@@ -265,10 +272,12 @@ describe('useInteractOutside (iframes)', function () {
265272

266273
fireEvent(iframeDocument.body, pointerEvent('pointerdown', {button: 1}));
267274
fireEvent(iframeDocument.body, pointerEvent('pointerup', {button: 1}));
275+
fireEvent.click(iframeDocument.body, {button: 0});
268276
expect(onInteractOutside).not.toHaveBeenCalled();
269277

270278
fireEvent(iframeDocument.body, pointerEvent('pointerdown', {button: 0}));
271279
fireEvent(iframeDocument.body, pointerEvent('pointerup', {button: 0}));
280+
fireEvent.click(iframeDocument.body, {button: 0});
272281
expect(onInteractOutside).toHaveBeenCalledTimes(1);
273282
});
274283

@@ -285,6 +294,7 @@ describe('useInteractOutside (iframes)', function () {
285294
expect(document.querySelector('iframe').contentWindow.document.body.querySelector('div[data-testid="example"]')).toBeTruthy();
286295
});
287296
fireEvent(iframeDocument.body, pointerEvent('pointerup'));
297+
fireEvent.click(iframeDocument.body);
288298
expect(onInteractOutside).not.toHaveBeenCalled();
289299
});
290300
});

packages/@react-aria/overlays/test/useModalOverlay.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('useModalOverlay', function () {
4040
render(<Example isOpen onOpenChange={onOpenChange} isDismissable shouldCloseOnInteractOutside={target => target === document.body} />);
4141
pressStart(document.body);
4242
pressEnd(document.body);
43+
fireEvent.click(document.body);
4344
expect(onOpenChange).toHaveBeenCalledWith(false);
4445
});
4546

@@ -48,6 +49,7 @@ describe('useModalOverlay', function () {
4849
render(<Example isOpen onOpenChange={onOpenChange} isDismissable shouldCloseOnInteractOutside={target => target !== document.body} />);
4950
pressStart(document.body);
5051
pressEnd(document.body);
52+
fireEvent.click(document.body);
5153
expect(onOpenChange).not.toHaveBeenCalled();
5254
});
5355
});

packages/@react-aria/overlays/test/useOverlay.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe('useOverlay', function () {
6161
render(<Example isOpen onClose={onClose} isDismissable />);
6262
pressStart(document.body);
6363
pressEnd(document.body);
64+
fireEvent.click(document.body);
6465
expect(onClose).toHaveBeenCalledTimes(1);
6566
});
6667

@@ -69,6 +70,7 @@ describe('useOverlay', function () {
6970
render(<Example isOpen onClose={onClose} isDismissable shouldCloseOnInteractOutside={target => target === document.body} />);
7071
pressStart(document.body);
7172
pressEnd(document.body);
73+
fireEvent.click(document.body);
7274
expect(onClose).toHaveBeenCalledTimes(1);
7375
});
7476

@@ -77,6 +79,7 @@ describe('useOverlay', function () {
7779
render(<Example isOpen onClose={onClose} isDismissable shouldCloseOnInteractOutside={target => target !== document.body} />);
7880
pressStart(document.body);
7981
pressEnd(document.body);
82+
fireEvent.click(document.body);
8083
expect(onClose).toHaveBeenCalledTimes(0);
8184
});
8285

@@ -85,6 +88,7 @@ describe('useOverlay', function () {
8588
render(<Example isOpen onClose={onClose} isDismissable={false} />);
8689
pressStart(document.body);
8790
pressEnd(document.body);
91+
fireEvent.click(document.body);
8892
expect(onClose).toHaveBeenCalledTimes(0);
8993
});
9094

@@ -96,13 +100,15 @@ describe('useOverlay', function () {
96100

97101
pressStart(document.body);
98102
pressEnd(document.body);
103+
fireEvent.click(document.body);
99104
expect(onCloseSecond).toHaveBeenCalledTimes(1);
100105
expect(onCloseFirst).not.toHaveBeenCalled();
101106

102107
second.unmount();
103108

104109
pressStart(document.body);
105110
pressEnd(document.body);
111+
fireEvent.click(document.body);
106112
expect(onCloseFirst).toHaveBeenCalledTimes(1);
107113
});
108114
});

0 commit comments

Comments
 (0)