Skip to content

PressResponder for Web #1591

Closed
Closed
@necolas

Description

@necolas

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-level click event which is not correlated with pointer state. As a result, on the web the press responder fails to call onPress 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 in click events falling through to background views if the press target is removed from the DOM during an earlier pointerup 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 native click event being dispatched. Also allows for preventDefault 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 be SyntheticMouseEvent and not SyntheticResponderEvent.
  • The click event (and therefore onPress) 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)

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions