-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: controlled tooltips are not close properly #6418
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,7 @@ export function useTooltipTriggerState(props: TooltipTriggerProps = {}): Tooltip | |
let {isOpen, open, close} = useOverlayTriggerState(props); | ||
let id = useMemo(() => `${++tooltipId}`, []); | ||
let closeTimeout = useRef<ReturnType<typeof setTimeout>>(); | ||
let closeCallback = useRef<() => void>(close); | ||
|
||
let ensureTooltipEntry = () => { | ||
tooltips[id] = hideTooltip; | ||
|
@@ -81,11 +82,11 @@ export function useTooltipTriggerState(props: TooltipTriggerProps = {}): Tooltip | |
if (immediate || closeDelay <= 0) { | ||
clearTimeout(closeTimeout.current); | ||
closeTimeout.current = null; | ||
close(); | ||
closeCallback.current(); | ||
} else if (!closeTimeout.current) { | ||
closeTimeout.current = setTimeout(() => { | ||
closeTimeout.current = null; | ||
close(); | ||
closeCallback.current(); | ||
}, closeDelay); | ||
} | ||
|
||
|
@@ -119,6 +120,10 @@ export function useTooltipTriggerState(props: TooltipTriggerProps = {}): Tooltip | |
} | ||
}; | ||
|
||
useEffect(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure, that's a good idea?
we call 'close' not inside effect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we have one from before those limitations were involved and because we have to support it all the way back to react 16, https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/utils/src/useEffectEvent.ts which I think is doing the same thing as you've done here but no worries, can replace later |
||
closeCallback.current = close; | ||
}, [close]); | ||
|
||
// eslint-disable-next-line arrow-body-style | ||
useEffect(() => { | ||
return () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ function TooltipTrigger(props) { | |
|
||
return ( | ||
<span> | ||
<button aria-label="trigger" ref={ref} {...triggerProps}> | ||
<button aria-label={props.label ?? 'trigger'} ref={ref} {...triggerProps}> | ||
{props.children} | ||
</button> | ||
{state.isOpen && | ||
|
@@ -46,6 +46,23 @@ function TooltipTrigger(props) { | |
); | ||
} | ||
|
||
function ManualTooltipTrigger(props) { | ||
let [isOpen, setOpen] = React.useState(false); | ||
|
||
const onOpenChange = (isOpen) => { | ||
props.onOpenChange(isOpen); | ||
setOpen(isOpen); | ||
}; | ||
|
||
return ( | ||
<TooltipTrigger | ||
label={props.label} | ||
onOpenChange={onOpenChange} | ||
isOpen={isOpen} | ||
tooltip={props.tooltip} /> | ||
); | ||
} | ||
|
||
/** | ||
* Most of the tests for useTooltipTriggerState are in the React Spectrum package at | ||
* @react-spectrum/tooltip/test/TooltipTrigger.test.js. The React Spectrum tooltip | ||
|
@@ -196,4 +213,45 @@ describe('useTooltipTriggerState', function () { | |
expect(onOpenChange).toHaveBeenCalledWith(false); | ||
}); | ||
}); | ||
|
||
describe('multiple controlled tooltips', () => { | ||
it('closes previus tooltip when opening a new one', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this test fails without fix and passes after fix |
||
let secondOnOpenChange = jest.fn(); | ||
|
||
let {queryByRole, getByLabelText} = render( | ||
<> | ||
<ManualTooltipTrigger onOpenChange={onOpenChange} tooltip="First tooltip" label="trigger1"> | ||
Trigger 1 | ||
</ManualTooltipTrigger> | ||
|
||
<ManualTooltipTrigger onOpenChange={secondOnOpenChange} tooltip="Second tooltip" label="trigger2"> | ||
Trigger 2 | ||
</ManualTooltipTrigger> | ||
</> | ||
); | ||
|
||
fireEvent.mouseDown(document.body); | ||
fireEvent.mouseUp(document.body); | ||
|
||
let button1 = getByLabelText('trigger1'); | ||
fireEvent.mouseEnter(button1); | ||
fireEvent.mouseMove(button1); | ||
|
||
// run through open timer and confirm that it has opened | ||
act(() => jest.advanceTimersByTime(TOOLTIP_DELAY)); | ||
|
||
expect(onOpenChange).toHaveBeenCalledWith(true); | ||
expect(queryByRole('tooltip')).toBeVisible(); | ||
|
||
let button2 = getByLabelText('trigger2'); | ||
fireEvent.mouseEnter(button2); | ||
fireEvent.mouseMove(button2); | ||
|
||
// run through open timer and confirm that it has opened | ||
act(() => jest.advanceTimersByTime(TOOLTIP_DELAY)); | ||
|
||
expect(onOpenChange).toHaveBeenCalledTimes(2); | ||
expect(onOpenChange).toHaveBeenCalledWith(false); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to change this line of code (it has no bug), but changed it for consistency.
use close callback only from ref