Skip to content

Commit b677593

Browse files
authored
stop propagation of clicks on tooltips (#4479)
* stop propagation of clicks on tooltips
1 parent 93c6b3c commit b677593

File tree

4 files changed

+62
-4
lines changed

4 files changed

+62
-4
lines changed

packages/@react-aria/tooltip/src/useTooltip.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {AriaTooltipProps} from '@react-types/tooltip';
1414
import {DOMAttributes} from '@react-types/shared';
1515
import {filterDOMProps, mergeProps} from '@react-aria/utils';
1616
import {TooltipTriggerState} from '@react-stately/tooltip';
17-
import {useHover} from '@react-aria/interactions';
17+
import {useHover, usePress} from '@react-aria/interactions';
1818

1919
export interface TooltipAria {
2020
/**
@@ -28,14 +28,15 @@ export interface TooltipAria {
2828
*/
2929
export function useTooltip(props: AriaTooltipProps, state?: TooltipTriggerState): TooltipAria {
3030
let domProps = filterDOMProps(props, {labelable: true});
31+
let {pressProps} = usePress({});
3132

3233
let {hoverProps} = useHover({
3334
onHoverStart: () => state?.open(true),
3435
onHoverEnd: () => state?.close()
3536
});
3637

3738
return {
38-
tooltipProps: mergeProps(domProps, hoverProps, {
39+
tooltipProps: mergeProps(domProps, hoverProps, pressProps, {
3940
role: 'tooltip'
4041
})
4142
};

packages/@react-spectrum/tooltip/stories/TooltipTrigger.stories.tsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Delete from '@spectrum-icons/workflow/Delete';
1717
import Edit from '@spectrum-icons/workflow/Edit';
1818
import {Flex} from '@react-spectrum/layout';
1919
import {Link} from '@react-spectrum/link';
20-
import React, {useState} from 'react';
20+
import React, {CSSProperties, useState} from 'react';
2121
import SaveTo from '@spectrum-icons/workflow/SaveTo';
2222
import {SpectrumTooltipTriggerProps} from '@react-types/tooltip';
2323
import {Tooltip, TooltipTrigger} from '../src';
@@ -176,6 +176,25 @@ export const TooltripTriggerInsideActionGroup: TooltipTriggerStory = {
176176
)
177177
};
178178

179+
const TooltipDivRender = (props) => {
180+
const [isDisabled, setIsDisabled] = useState(false);
181+
const wrapperStyle: CSSProperties = {width: '400px', height: '400px', backgroundColor: 'red', position: 'absolute'};
182+
return (
183+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
184+
<div onClick={() => setIsDisabled(!isDisabled)} style={wrapperStyle}>
185+
<TooltipTrigger {...props} isOpen>
186+
<ActionButton isDisabled={isDisabled} aria-label="Edit" UNSAFE_style={{top: '50px'}}>click red to disable</ActionButton>
187+
<Tooltip>Click on tooltip doesn't propagate to parent</Tooltip>
188+
</TooltipTrigger>
189+
</div>
190+
);
191+
};
192+
export const TooltripTriggerInsideDiv: TooltipTriggerStory = {
193+
args: {delay: 0},
194+
render: (args) => <TooltipDivRender {...args} />,
195+
parameters: {description: {data: 'Event handlers are attached to the parent of the tooltip trigger. They should not be called when the tooltip itself is clicked.'}}
196+
};
197+
179198
export const ArrowPositioningAtEdge: TooltipTriggerStory = {
180199
args: {
181200
children: [

packages/@react-spectrum/tooltip/test/Tooltip.test.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import React from 'react';
14-
import {render} from '@react-spectrum/test-utils';
14+
import {render, triggerPress} from '@react-spectrum/test-utils';
1515
import {Tooltip} from '../';
1616

1717
describe('Tooltip', function () {
@@ -40,4 +40,13 @@ describe('Tooltip', function () {
4040
let tooltip = getByRole('tooltip');
4141
expect(ref.current.UNSAFE_getDOMNode()).toBe(tooltip);
4242
});
43+
44+
it('click does not propagate to parent', () => {
45+
let mockClick = jest.fn();
46+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
47+
let {getByRole} = render(<div onClick={mockClick}><Tooltip>This is a tooltip</Tooltip></div>);
48+
let tooltip = getByRole('tooltip');
49+
triggerPress(tooltip);
50+
expect(mockClick).not.toHaveBeenCalled();
51+
});
4352
});

packages/@react-spectrum/tooltip/test/TooltipTrigger.test.js

+29
Original file line numberDiff line numberDiff line change
@@ -976,4 +976,33 @@ describe('TooltipTrigger', function () {
976976
expect(queryByRole('tooltip')).toBeNull();
977977
});
978978
});
979+
980+
it('does not propagate pointer or click through the portal', () => {
981+
let onPointerDown = jest.fn();
982+
let onPointerUp = jest.fn();
983+
let onClick = jest.fn();
984+
985+
let {getByRole} = render(
986+
<Provider theme={theme}>
987+
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
988+
<div onPointerDown={onPointerDown} onPointerUp={onPointerUp} onClick={onClick}>
989+
<TooltipTrigger>
990+
<ActionButton>Trigger</ActionButton>
991+
<Tooltip>Helpful information.</Tooltip>
992+
</TooltipTrigger>
993+
</div>
994+
</Provider>
995+
);
996+
997+
let button = getByRole('button');
998+
act(() => {
999+
button.focus();
1000+
});
1001+
1002+
let tooltip = getByRole('tooltip');
1003+
triggerPress(tooltip);
1004+
expect(onPointerDown).not.toHaveBeenCalled();
1005+
expect(onPointerUp).not.toHaveBeenCalled();
1006+
expect(onClick).not.toHaveBeenCalled();
1007+
});
9791008
});

0 commit comments

Comments
 (0)