Skip to content

Commit 2a2d571

Browse files
authored
fix(usePress): add global touch-action style for pressable elements (#8200)
* add global touch-action style for pressable elements * remove :where() * add comment back * add story to test * check viewport meta tag * update story * move constant * const for data attribute * remove meta viewport check
1 parent e8bf328 commit 2a2d571

File tree

2 files changed

+61
-11
lines changed

2 files changed

+61
-11
lines changed

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class PressEvent implements IPressEvent {
157157
}
158158

159159
const LINK_CLICKED = Symbol('linkClicked');
160+
const STYLE_ID = 'react-aria-pressable-style';
161+
const PRESSABLE_ATTRIBUTE = 'data-react-aria-pressable';
160162

161163
/**
162164
* Handles press interactions across mouse, touch, keyboard, and screen readers.
@@ -815,17 +817,28 @@ export function usePress(props: PressHookProps): PressResult {
815817

816818
// Avoid onClick delay for double tap to zoom by default.
817819
useEffect(() => {
818-
let element = domRef?.current;
819-
if (element && (element instanceof getOwnerWindow(element).Element)) {
820-
// Only apply touch-action if not already set by another CSS rule.
821-
let style = getOwnerWindow(element).getComputedStyle(element);
822-
if (style.touchAction === 'auto') {
823-
// touchAction: 'manipulation' is supposed to be equivalent, but in
824-
// Safari it causes onPointerCancel not to fire on scroll.
825-
// https://bugs.webkit.org/show_bug.cgi?id=240917
826-
(element as HTMLElement).style.touchAction = 'pan-x pan-y pinch-zoom';
827-
}
820+
if (!domRef || process.env.NODE_ENV === 'test') {
821+
return;
822+
}
823+
824+
const ownerDocument = getOwnerDocument(domRef.current);
825+
if (!ownerDocument || !ownerDocument.head || ownerDocument.getElementById(STYLE_ID)) {
826+
return;
828827
}
828+
829+
const style = ownerDocument.createElement('style');
830+
style.id = STYLE_ID;
831+
// touchAction: 'manipulation' is supposed to be equivalent, but in
832+
// Safari it causes onPointerCancel not to fire on scroll.
833+
// https://bugs.webkit.org/show_bug.cgi?id=240917
834+
style.textContent = `
835+
@layer {
836+
[${PRESSABLE_ATTRIBUTE}] {
837+
touch-action: pan-x pan-y pinch-zoom;
838+
}
839+
}
840+
`.trim();
841+
ownerDocument.head.prepend(style);
829842
}, [domRef]);
830843

831844
// Remove user-select: none in case component unmounts immediately after pressStart
@@ -844,7 +857,7 @@ export function usePress(props: PressHookProps): PressResult {
844857

845858
return {
846859
isPressed: isPressedProp || isPressed,
847-
pressProps: mergeProps(domProps, pressProps)
860+
pressProps: mergeProps(domProps, pressProps, {[PRESSABLE_ATTRIBUTE]: true})
848861
};
849862
}
850863

packages/react-aria-components/stories/Button.stories.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,40 @@ function RippleButton(props) {
119119
</Button>
120120
);
121121
}
122+
123+
function ButtonPerformanceExample() {
124+
const [count, setCount] = useState(0);
125+
const [showButtons, setShowButtons] = useState(false);
126+
127+
const handlePress = () => {
128+
if (!showButtons) {
129+
setShowButtons(true);
130+
} else {
131+
setCount(count + 1);
132+
}
133+
};
134+
135+
return (
136+
<div>
137+
<Button style={{marginTop: 24, marginBottom: 16}} onPress={handlePress}>
138+
{showButtons ? 'Re-render' : 'Render'}
139+
</Button>
140+
{showButtons && (
141+
<div style={{display: 'flex', gap: 2, flexWrap: 'wrap'}} key={count}>
142+
{new Array(20000).fill(0).map((_, i) => (
143+
<Button key={i}>Press me</Button>
144+
))}
145+
</div>
146+
)}
147+
</div>
148+
);
149+
}
150+
151+
export const ButtonPerformance = {
152+
render: (args) => <ButtonPerformanceExample {...args} />,
153+
parameters: {
154+
description: {
155+
data: 'When usePress is used on the page, there should be a <style> tag placed in the head of the document that applies touch-action: pan-x pan-y pinch-zoom to the [data-react-aria-pressable] elements.'
156+
}
157+
}
158+
};

0 commit comments

Comments
 (0)