Skip to content

Commit 4f2c48f

Browse files
johannkorgabrieljablonski
authored andcommitted
fix: debounce prevented tooltip from closing
There was an issue where the tooltip would stay open after the cursor had already left the anchor element. The issue would arise if: - there are two tooltip anchors A and B (same tooltip ID) - mouse goes over A to show the tooltip, user waits for tooltip to show - mouse quickly (within 50ms) leaves A, goes over B, then leaves B. The tooltip component applies debouncing on the show and hide events in case e.g. an anchor is simultaneously focused and mouseovered. If the second leave event occurred while that debounce was still active, it was never processed. This commit fixes the issue by making the show and hide debounce functions reset each other. For example, if the hide debounce has an active timeout, calling the debounced show function will now reset the timeout of the hide debounce. This solution still leaves the hide/show debounces effective against double-show/double-hide.
1 parent 2e59f10 commit 4f2c48f

File tree

2 files changed

+27
-3
lines changed

2 files changed

+27
-3
lines changed

src/components/Tooltip/Tooltip.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,22 @@ const Tooltip = ({
372372

373373
// debounce handler to prevent call twice when
374374
// mouse enter and focus events being triggered toggether
375-
const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true)
376-
const debouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true)
375+
const internalDebouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true)
376+
const internalDebouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true)
377+
// If either of the functions is called while the other is still debounced,
378+
// reset the timeout. Otherwise if there is a sub-50ms (leave A, enter B, leave B)
379+
// sequence of events, the tooltip will stay open because the hide debounce
380+
// from leave A prevented the leave B event from calling it, leaving the
381+
// tooltip visible.
382+
const debouncedHandleShowTooltip = (e?: Event) => {
383+
internalDebouncedHandleHideTooltip.reset()
384+
internalDebouncedHandleShowTooltip(e)
385+
}
386+
const debouncedHandleHideTooltip = () => {
387+
internalDebouncedHandleShowTooltip.reset()
388+
internalDebouncedHandleHideTooltip()
389+
}
390+
377391
const updateTooltipPosition = useCallback(() => {
378392
const actualPosition = imperativeOptions?.position ?? position
379393
if (actualPosition) {

src/utils/debounce.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const debounce = <T, A extends any[]>(
1212
) => {
1313
let timeout: NodeJS.Timeout | null = null
1414

15-
return function debounced(this: T, ...args: A): void {
15+
const debounced = function debounced(this: T, ...args: A): void {
1616
const later = () => {
1717
timeout = null
1818
if (!immediate) {
@@ -36,6 +36,16 @@ const debounce = <T, A extends any[]>(
3636
timeout = setTimeout(later, wait)
3737
}
3838
}
39+
40+
debounced.reset = () => {
41+
if (!timeout) {
42+
return
43+
}
44+
clearTimeout(timeout)
45+
timeout = null
46+
}
47+
48+
return debounced
3949
}
4050

4151
export default debounce

0 commit comments

Comments
 (0)