Skip to content

[Web] Fix Pinch and Rotation#4078

Merged
m-bert merged 4 commits intomainfrom
@mbert/pinch-reset-web
Apr 13, 2026
Merged

[Web] Fix Pinch and Rotation#4078
m-bert merged 4 commits intomainfrom
@mbert/pinch-reset-web

Conversation

@m-bert
Copy link
Copy Markdown
Contributor

@m-bert m-bert commented Apr 10, 2026

Description

I've noticed that Pinch and Rotation behave weirdly when it comes to ending gesture on web. I've took a look and fixed some minor issues.

Note

This PR may introduce mismatch between android and web versions of Rotation. I still think that this is how it is supposed to work, so we may want to make required changes to android as well (or drop this PR if you prefer to).

Pinch

I've noticed that Pinch is not reset when pointers are placed on view and then released. This was because for some reason Pinch simply ignored finished states if it had not activated earlier.

Rotation

Rotation, always finalized with true. This was because we always tried to move it to end state. Our internal event handler hadn't been sending onDeactivate, but true was still passed into onFinalize.

Test plan

Tested on Transformations example and code below:
import { StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  usePinchGesture,
  useRotationGesture,
} from 'react-native-gesture-handler';

export default function App() {
  const g = useRotationGesture({
    onBegin: () => console.log('onBegin'),
    onActivate: () => console.log('onActivate'),
    onUpdate: (e) => console.log('onUpdate', e.rotation),
    onDeactivate: () => console.log('onDeactivate'),
    onFinalize: (_, s) => console.log('onFinalize', s),
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <GestureDetector gesture={g}>
        <View style={styles.box} />
      </GestureDetector>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 200,
    height: 200,
    backgroundColor: 'blue',
    borderRadius: 12,
  },
});

Copilot AI review requested due to automatic review settings April 10, 2026 17:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect end/finalize behavior for Pinch and Rotation gestures on web, ensuring gestures properly transition to END only when they were active and otherwise transition to FAILED so onFinalize receives the correct success flag.

Changes:

  • Rotation: end the gesture only when State.ACTIVE, otherwise fail, and drive this via the detector’s onRotationEnd callback.
  • Pinch: ensure a gesture that began but never activated is failed on pointer release (instead of being left in a non-finished state).
  • Rotation detector: always call finish() on UP so end/fail logic reliably runs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts Moves end/fail decision to onRotationEnd and fails non-active rotations so finalize state is correct.
packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts Ensures pinch transitions to FAILED when it never became active but pointers are released.
packages/react-native-gesture-handler/src/web/detectors/RotationGestureDetector.ts Ensures finish() is invoked on UP and consistently triggers onRotationEnd.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

this.scaleGestureDetector.onTouchEvent(event, this.tracker);

if (this.state === State.ACTIVE) {
this.scaleGestureDetector.onTouchEvent(event, this.tracker);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that the scale detector will not be receiving up events in the active state. Is that intended?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that intended?

Yes, it was. If you look at the removed code it didn't make any sense and now it resembles the same logic as it had previously. I've also checked if it should be moved up, but if you look at ScaleGestureDetector it will simply return true in case of inactive handler, so we don't have to call it. I've added comment in 02ed360.

As for moving this logic to onScaleEnd it is not as easy as with Rotation gesture, as it is only called if handler was active.

m-bert added a commit that referenced this pull request Apr 13, 2026
## Description

Follow up for #4078 

I've noticed that when `Rotation` does not activate, but pointers are
released, `onFinalize` is called with `true`. This happens because we
try to move it to `END` state, but our internal logic does not send
`onDeactivate`.

Code in `onHandle` that I removed was effectively dead, as `ACTION_UP`
was handled in rotation detector - the only possible branch was `else`
statement which fails handler if only one pointer was present.


## Test plan

<details>
<summary>Tested on Transformations example and the following
code:</summary>

```tsx
import { StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  usePinchGesture,
  useRotationGesture,
} from 'react-native-gesture-handler';

export default function App() {
  const g = useRotationGesture({
    onBegin: () => console.log('onBegin'),
    onActivate: () => console.log('onActivate'),
    onUpdate: (e) => console.log('onUpdate', e.rotation),
    onDeactivate: () => console.log('onDeactivate'),
    onFinalize: (_, s) => console.log('onFinalize', s),
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <GestureDetector gesture={g}>
        <View style={styles.box} />
      </GestureDetector>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 200,
    height: 200,
    backgroundColor: 'blue',
    borderRadius: 12,
  },
});
```
</details>
@m-bert m-bert requested a review from j-piasecki April 13, 2026 13:02
@m-bert m-bert merged commit 5d60a09 into main Apr 13, 2026
2 checks passed
@m-bert m-bert deleted the @mbert/pinch-reset-web branch April 13, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants