[Web] Add hover callbacks to Touchable - #4398
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
110fe61 to
c24a2eb
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the web implementation of GestureHandlerButton to emit onButtonHoverIn / onButtonHoverOut callbacks directly from onPointerEnter / onPointerLeave, with event payloads aligned to the same coordinate basis used by the web pointer event pipeline.
Changes:
- Added
onButtonHoverIn/onButtonHoverOutprops to the web button component and dispatch logic that masks hover byenabledwhile still tracking hover state. - Introduced a lightweight pointer-event-to-
ButtonEventsampler (gated behind the presence of hover callbacks) to avoid unnecessary layout reads. - Ensured hover transitions are emitted even when leave/re-enter events are batched, and when
enabledtoggles while the pointer remains inside.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
c24a2eb to
eab5d27
Compare
95a1352 to
bd37b23
Compare
## Description
Hover on web comes straight from the `onPointerEnter`/`onPointerLeave`
the button already uses for its own animation, rather than through the
press pipeline.
- Reported from the pointer handlers rather than an effect, so a leave
and a re-enter batched into one render still produce both events. The
effect only covers `enabled` flipping while the pointer is inside.
- `hovered` is now tracked regardless of `enabled` and masked at render,
so hover resumes on its own when `enabled` flips back with the pointer
still inside — matching the native platforms.
- The payload uses the same coordinate basis as `PointerEventManager`'s
`mapEvent`. Both reads force a layout flush, so it's only built when a
hover callback is actually present.
## Test plan
The `.web` variant isn't resolved by the React Native Jest preset, so
this needs a browser — with a mouse and with a pen.
<details>
<summary>Example code</summary>
```tsx
import React, { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import {
GestureHandlerRootView,
Touchable,
} from 'react-native-gesture-handler';
export default function Example() {
const [log, setLog] = useState<string[]>([]);
const callbacks = (source: string) => ({
onHoverIn: () => setLog((l) => [`${source} onHoverIn`, ...l]),
onHoverOut: () => setLog((l) => [`${source} onHoverOut`, ...l]),
onPressIn: () => setLog((l) => [`${source} onPressIn`, ...l]),
onPressOut: () => setLog((l) => [`${source} onPressOut`, ...l]),
});
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.row}>
<Touchable style={styles.box} {...callbacks('Touchable')}>
<Text style={styles.text}>Touchable</Text>
</Touchable>
<Pressable style={styles.box} {...callbacks('Pressable')}>
<Text style={styles.text}>Pressable</Text>
</Pressable>
</View>
{log.slice(0, 12).map((entry, i) => (
<Text key={i}>{entry}</Text>
))}
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
row: { flexDirection: 'row', gap: 24, marginBottom: 24 },
box: {
width: 120,
height: 120,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#6941C6',
},
text: { color: 'white' },
});
```
</details>
bd37b23 to
718880f
Compare
Description
Hover on web comes straight from the
onPointerEnter/onPointerLeavethe button already uses for its own animation, rather than through the
press pipeline.
and a re-enter batched into one render still produce both events. The
effect only covers
enabledflipping while the pointer is inside.hoveredis now tracked regardless ofenabledand masked at render,so hover resumes on its own when
enabledflips back with the pointerstill inside — matching the native platforms.
PointerEventManager'smapEvent. Both reads force a layout flush, so it's only built when ahover callback is actually present.
Test plan
The
.webvariant isn't resolved by the React Native Jest preset, sothis needs a browser — with a mouse and with a pen.
Example code