From b94acbf2d9bda35b26fa9e308b7bbed66a4dd50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Y=C4=B1lmaz?= Date: Wed, 12 Aug 2026 17:04:18 +0300 Subject: [PATCH] fix(tooltip): propagate press and hover event objects to children Tooltip called the wrapped children's onPress, onHoverIn and onHoverOut handlers without any arguments, so the event object was swallowed and parent callbacks received undefined. Forward the original GestureResponderEvent/MouseEvent to the children handlers and type TooltipChildProps accordingly. Closes #5003 Co-Authored-By: Claude Opus 5 --- src/components/Tooltip/Tooltip.tsx | 58 ++++++++++++++--------- src/components/Tooltip/utils.ts | 14 ++++-- src/components/__tests__/Tooltip.test.tsx | 54 ++++++++++++++++++++- 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 8d237d6991..5d19f8e3e4 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -6,7 +6,12 @@ import { Platform, Pressable, } from 'react-native'; -import type { LayoutChangeEvent, ViewStyle } from 'react-native'; +import type { + GestureResponderEvent, + LayoutChangeEvent, + MouseEvent, + ViewStyle, +} from 'react-native'; import { getTooltipPosition } from './utils'; import type { Measurement, TooltipChildProps } from './utils'; @@ -146,29 +151,38 @@ const Tooltip = ({ hideTooltipTimer.current.push(id); }, [leaveTouchDelay]); - const handlePress = React.useCallback(() => { - if (touched.current) { - return null; - } - if (!isValidChild) return null; - const props = children.props as TooltipChildProps; - if (props.disabled) return null; - return props.onPress?.(); - }, [children.props, isValidChild]); + const handlePress = React.useCallback( + (event: GestureResponderEvent) => { + if (touched.current) { + return null; + } + if (!isValidChild) return null; + const props = children.props as TooltipChildProps; + if (props.disabled) return null; + return props.onPress?.(event); + }, + [children.props, isValidChild] + ); - const handleHoverIn = React.useCallback(() => { - handleTouchStart(); - if (isValidChild) { - (children.props as TooltipChildProps).onHoverIn?.(); - } - }, [children.props, handleTouchStart, isValidChild]); + const handleHoverIn = React.useCallback( + (event: MouseEvent) => { + handleTouchStart(); + if (isValidChild) { + (children.props as TooltipChildProps).onHoverIn?.(event); + } + }, + [children.props, handleTouchStart, isValidChild] + ); - const handleHoverOut = React.useCallback(() => { - handleTouchEnd(); - if (isValidChild) { - (children.props as TooltipChildProps).onHoverOut?.(); - } - }, [children.props, handleTouchEnd, isValidChild]); + const handleHoverOut = React.useCallback( + (event: MouseEvent) => { + handleTouchEnd(); + if (isValidChild) { + (children.props as TooltipChildProps).onHoverOut?.(event); + } + }, + [children.props, handleTouchEnd, isValidChild] + ); const handleOnLayout = ({ nativeEvent: { layout } }: LayoutChangeEvent) => { childrenWrapperRef.current?.measure( diff --git a/src/components/Tooltip/utils.ts b/src/components/Tooltip/utils.ts index 43baf684fd..2af335fdc5 100644 --- a/src/components/Tooltip/utils.ts +++ b/src/components/Tooltip/utils.ts @@ -1,5 +1,11 @@ import { Dimensions, StyleSheet } from 'react-native'; -import type { LayoutRectangle, StyleProp, ViewStyle } from 'react-native'; +import type { + GestureResponderEvent, + LayoutRectangle, + MouseEvent, + StyleProp, + ViewStyle, +} from 'react-native'; type ChildrenMeasurement = { width: number; @@ -19,9 +25,9 @@ export type Measurement = { export type TooltipChildProps = { style: StyleProp; disabled?: boolean; - onPress?: () => void; - onHoverIn?: () => void; - onHoverOut?: () => void; + onPress?: (event: GestureResponderEvent) => void; + onHoverIn?: (event: MouseEvent) => void; + onHoverOut?: (event: MouseEvent) => void; }; /** diff --git a/src/components/__tests__/Tooltip.test.tsx b/src/components/__tests__/Tooltip.test.tsx index 75b4a18cf7..2dd2dc3cc7 100644 --- a/src/components/__tests__/Tooltip.test.tsx +++ b/src/components/__tests__/Tooltip.test.tsx @@ -1,6 +1,10 @@ import React from 'react'; import { Dimensions, Text, View, Platform } from 'react-native'; -import type { ViewProps } from 'react-native'; +import type { + GestureResponderEvent, + MouseEvent, + ViewProps, +} from 'react-native'; import { afterAll, @@ -30,6 +34,9 @@ const DummyComponent = ({ ...props }: ViewProps & { ref?: React.RefObject; + onPress?: (event: GestureResponderEvent) => void; + onHoverIn?: (event: MouseEvent) => void; + onHoverOut?: (event: MouseEvent) => void; }) => ( dummy component @@ -159,6 +166,23 @@ describe('Tooltip', () => { }); }); + describe('press', () => { + it("passes the event object to the children's onPress", async () => { + const onPress = jest.fn(); + + const { + wrapper: { getByText }, + } = await setup({ children: }); + + await userEvent.press(getTrigger(getByText)); + + expect(onPress).toHaveBeenCalledTimes(1); + expect(onPress).toHaveBeenCalledWith( + expect.objectContaining({ nativeEvent: expect.anything() }) + ); + }); + }); + describe('pressOut', () => { it('hides the tooltip when the user stop pressing the component', async () => { const { @@ -350,6 +374,19 @@ describe('Tooltip', () => { expect(global.clearTimeout).toHaveBeenCalledTimes(2); }); + + it("passes the event object to the children's onHoverIn", async () => { + const onHoverIn = jest.fn(); + const event = { nativeEvent: {} }; + + const { + wrapper: { getByText }, + } = await setup({ children: }); + + await fireEvent(getTrigger(getByText), 'hoverIn', event); + + expect(onHoverIn).toHaveBeenCalledWith(event); + }); }); describe('hoverOut', () => { @@ -368,6 +405,21 @@ describe('Tooltip', () => { expect(queryByText('some tooltip text')).not.toBeOnTheScreen(); }); + + it("passes the event object to the children's onHoverOut", async () => { + const onHoverOut = jest.fn(); + const event = { nativeEvent: {} }; + + const { + wrapper: { getByText }, + } = await setup({ + children: , + }); + + await fireEvent(getTrigger(getByText), 'hoverOut', event); + + expect(onHoverOut).toHaveBeenCalledWith(event); + }); }); describe('Tooltip position', () => {