diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index d42f5441..4b6a9a06 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -372,8 +372,22 @@ const Tooltip = ({ // debounce handler to prevent call twice when // mouse enter and focus events being triggered toggether - const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true) - const debouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true) + const internalDebouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true) + const internalDebouncedHandleHideTooltip = debounce(handleHideTooltip, 50, true) + // If either of the functions is called while the other is still debounced, + // reset the timeout. Otherwise if there is a sub-50ms (leave A, enter B, leave B) + // sequence of events, the tooltip will stay open because the hide debounce + // from leave A prevented the leave B event from calling it, leaving the + // tooltip visible. + const debouncedHandleShowTooltip = (e?: Event) => { + internalDebouncedHandleHideTooltip.cancel() + internalDebouncedHandleShowTooltip(e) + } + const debouncedHandleHideTooltip = () => { + internalDebouncedHandleShowTooltip.cancel() + internalDebouncedHandleHideTooltip() + } + const updateTooltipPosition = useCallback(() => { const actualPosition = imperativeOptions?.position ?? position if (actualPosition) { diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts index 45f02826..36d7deb4 100644 --- a/src/utils/debounce.ts +++ b/src/utils/debounce.ts @@ -5,10 +5,14 @@ * @param { number } wait Time to wait before execut the function * @param { boolean } immediate Param to define if the function will be executed immediately */ -const debounce = (func: (...args: any[]) => void, wait?: number, immediate?: boolean) => { +const debounce = ( + func: (...args: A) => void, + wait?: number, + immediate?: boolean, +) => { let timeout: NodeJS.Timeout | null = null - return function debounced(this: typeof func, ...args: any[]) { + const debounced = function debounced(this: T, ...args: A): void { const later = () => { timeout = null if (!immediate) { @@ -32,6 +36,16 @@ const debounce = (func: (...args: any[]) => void, wait?: number, immediate?: boo timeout = setTimeout(later, wait) } } + + debounced.cancel = () => { + if (!timeout) { + return + } + clearTimeout(timeout) + timeout = null + } + + return debounced } export default debounce