Description
PressResponder for Web
A Press Responder provides the capability to respond to tapping/pressing gestures. It's a layer over the gesture responder system. It is based on an implementation in React Native to help support the development of native-quality press gestures.
Using existing Touchable and Pressability
The current Touchable
module is intended to be used as a mixin with a React class. Mixins are not part of the modern React API, and classes don't support hooks. The current Pressability
module can be used with functional components and hooks, but there are other problems on the web:
Problems
- The React Native press responders perform layout rect measurements on every pointer-down event. This causes layout thrash on the web and is particularly problematic in scrollable containers (Touchable: layout is measured during touch-scroll event #1166).
- This layout measurement doesn't account for changes in dimensions or position of the press target on screen, which can lead to incorrect calling/not-calling of callbacks when the pointer is released (TouchableOpacity onPress doesn't fire every time #1564, Touchable shouldn't be pressed while scrolling in a ScrollView #1534)
- React Native performs layout measurement to implement an iOS-like pattern of activating/deactivating a press target when the pointer moves inside/outside the
pressRetention
region of a target. But this doesn't feel right on the web. - The React Native press responders infer when to call
onPress
based on whether the pointer is released over the press target. However, on the web, "successful press" is communicated via the high-levelclick
event which is not correlated with pointer state. As a result, on the web the press responder fails to callonPress
in situations where it is expected (e.g., keyboard or screen-reader interaction), calls it in situations where it is NOT expected (e.g., during a touch-scroll Touchable: use within body scroller / outside of ScrollView #829), and can result inclick
events falling through to background views if the press target is removed from the DOM during an earlierpointerup
event (TouchableOpacity triggering <input> and <TextInput> #1164).
Replacing the Touchable
To address these issues the Press Responder will be simplified so that onPress
is associated with native click
events, and the behavior doesn't rely on layout measurement.
Benefits
- No layout measurement during a gesture. This improves performance and reduces code size.
- No "active press out" behavior. This is needed to avoid layout measurements, and also provides a press UX that is expected on the web.
- Correlation between
onPress
being called and a nativeclick
event being dispatched. Also allows forpreventDefault
control over native behavior. - Better integration with the native DOM environment
Costs
- No support for
pressRetentionOffset
prop or associated UX. - The
onPress
event type will beSyntheticMouseEvent
and notSyntheticResponderEvent
. - The
click
event (and thereforeonPress
) is natively dispatched in scenarios where no press start/end events occur.
Plan
Drop the rect measurement and inactive press behavior to improve runtime performance, reduce bytes, and align with expected web UX.
The intended use for the Hook is as follows:
function View(props) {
const pressEventHandlers = usePressEvents({
onPressStart: props.onPressStart,
onPressEnd: props.onPressEnd,
...
});
return (
<View {...pressEventHandlers} />
);
}
Unlike the useResponderEvents
hook, the usePressEvents
hook needs to return event handler props that are spread into a View
. (Note: if View
didn't incorporate useResponderEvents
the usePressEvents
hook could have the same usePressEvents(ref, callbacks)
API)