Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions packages/@react-aria/interactions/src/usePress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,7 @@ export function usePress(props: PressHookProps): PressResult {
preventFocusOnPress,
shouldCancelOnPointerExit,
allowTextSelectionOnPress,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ref: _, // Removing `ref` from `domProps` because TypeScript is dumb
ref: domRef,
...domProps
} = usePressResponderContext(props);

Expand Down Expand Up @@ -814,13 +813,26 @@ export function usePress(props: PressHookProps): PressResult {
triggerSyntheticClick
]);

// Remove user-select: none in case component unmounts immediately after pressStart
// Avoid onClick delay for double tap to zoom by default.
useEffect(() => {
let element = domRef?.current;
if (element && (element instanceof getOwnerWindow(element).Element)) {
// Only apply touch-action if not already set by another CSS rule.
let style = getOwnerWindow(element).getComputedStyle(element);
if (style.touchAction === 'auto') {
// touchAction: 'manipulation' is supposed to be equivalent, but in
// Safari it causes onPointerCancel not to fire on scroll.
// https://bugs.webkit.org/show_bug.cgi?id=240917
(element as HTMLElement).style.touchAction = 'pan-x pan-y pinch-zoom';
}
}
}, [domRef]);

// Remove user-select: none in case component unmounts immediately after pressStart
useEffect(() => {
let state = ref.current;
return () => {
if (!allowTextSelectionOnPress) {

restoreTextSelection(state.target ?? undefined);
}
for (let dispose of state.disposables) {
Expand Down
69 changes: 68 additions & 1 deletion packages/@react-aria/interactions/stories/usePress.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
Modal,
ModalOverlay
} from 'react-aria-components';
import React from 'react';
import React, {useState} from 'react';
import styles from './usePress-stories.css';
import {usePress} from '@react-aria/interactions';

Expand Down Expand Up @@ -234,3 +234,70 @@ export function SoftwareKeyboardIssue() {
</div>
);
}

export function AndroidUnmountIssue() {
let [showButton, setShowButton] = useState(true);

return (
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
<p>This story tests an Android issue where tapping a button that unmounts causes the element behind it to receive onClick.</p>
<div style={{position: 'relative', width: 100, height: 100}}>
<button
type="button"
onClick={() => {
alert('button underneath was pressed');
}}
style={{position: 'absolute', top: 0}}>
Test 2
</button>
{showButton && (
<Button
className="foo"
style={{position: 'absolute', top: 0}}
onPress={() => {
console.log('ra Button pressed');
setShowButton(false);
}}>
Test
</Button>
)}
</div>
</div>
);
}

export function IOSScrollIssue() {
return (
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
<p>This story tests an iOS Safari issue that causes onPointerCancel not to be fired with touch-action: manipulation. Scrolling the list should not trigger onPress.</p>
<div
style={{
marginTop: 10,
width: 500,
height: 100,
overflowY: 'hidden',
overflowX: 'auto',
border: '1px solid black',
display: 'flex',
gap: 8
}}>
{Array.from({length: 10}).map((_, i) => (
<Card key={i} />
))}
</div>
</div>
);
}

function Card() {
return (
<Button
className="foo"
style={{height: 80, width: 150, flexShrink: 0}}
onPress={() => {
alert('pressed');
}}>
Test
</Button>
);
}
4 changes: 2 additions & 2 deletions packages/@react-aria/selection/src/useSelectableItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import {DOMAttributes, DOMProps, FocusableElement, Key, LongPressEvent, PointerType, PressEvent, RefObject} from '@react-types/shared';
import {focusSafely, PressProps, useLongPress, usePress} from '@react-aria/interactions';
import {focusSafely, PressHookProps, useLongPress, usePress} from '@react-aria/interactions';
import {getCollectionId, isNonContiguousSelectionModifier} from './utils';
import {isCtrlKeyPressed, mergeProps, openLink, useId, useRouter} from '@react-aria/utils';
import {moveVirtualFocus} from '@react-aria/focus';
Expand Down Expand Up @@ -239,7 +239,7 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
// we want to be able to have the pointer down on the trigger that opens the menu and
// the pointer up on the menu item rather than requiring a separate press.
// For keyboard events, selection still occurs on key down.
let itemPressProps: PressProps = {};
let itemPressProps: PressHookProps = {ref};
if (shouldSelectOnPressUp) {
itemPressProps.onPressStart = (e) => {
modality.current = e.pointerType;
Expand Down