diff --git a/docs/docs/examples/styling.mdx b/docs/docs/examples/styling.mdx index 573fb4a1..baa5f732 100644 --- a/docs/docs/examples/styling.mdx +++ b/docs/docs/examples/styling.mdx @@ -446,14 +446,6 @@ In summary, if you do it correctly you can use CSS specificity instead of `!impo By default, the tooltip has a fade-in/fade-out transition when opening/closing, with a delay of 150ms for both. If you wish to change the delay for any of them, override the following CSS variables: -:::caution - -Do not set `--rt-transition-closing-delay` to `0`. Doing so will result in the tooltip component being stuck (but not visible) on the DOM. This isn't itself a problem, but may lead to performance issues. - -Set to `1ms` or a similar value if you want to disable the fade-out transition when closing. - -::: - ```css :root { --rt-transition-show-delay: 0.15s; diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 3f3b8641..a65afbce 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -6,6 +6,7 @@ import { useTooltip } from 'components/TooltipProvider' import useIsomorphicLayoutEffect from 'utils/use-isomorphic-layout-effect' import { getScrollParent } from 'utils/get-scroll-parent' import { computeTooltipPosition } from 'utils/compute-positions' +import { cssTimeToMs } from 'utils/css-time-to-ms' import coreStyles from './core-styles.module.css' import styles from './styles.module.css' import type { @@ -67,6 +68,7 @@ const Tooltip = ({ const tooltipArrowRef = useRef(null) const tooltipShowDelayTimerRef = useRef(null) const tooltipHideDelayTimerRef = useRef(null) + const missedTransitionTimerRef = useRef(null) const [actualPlacement, setActualPlacement] = useState(place) const [inlineStyles, setInlineStyles] = useState({}) const [inlineArrowStyles, setInlineArrowStyles] = useState({}) @@ -211,6 +213,9 @@ const Tooltip = ({ if (show === wasShowing.current) { return } + if (missedTransitionTimerRef.current) { + clearTimeout(missedTransitionTimerRef.current) + } wasShowing.current = show if (show) { afterShow?.() @@ -218,6 +223,18 @@ const Tooltip = ({ /** * see `onTransitionEnd` on tooltip wrapper */ + const style = getComputedStyle(document.body) + const transitionShowDelay = cssTimeToMs(style.getPropertyValue('--rt-transition-show-delay')) + missedTransitionTimerRef.current = setTimeout(() => { + /** + * if the tooltip switches from `show === true` to `show === false` too fast + * the transition never runs, so `onTransitionEnd` callback never gets fired + */ + setRendered(false) + setImperativeOptions(null) + afterHide?.() + // +25ms just to make sure `onTransitionEnd` (if it gets fired) has time to run + }, transitionShowDelay + 25) } }, [show]) @@ -803,10 +820,9 @@ const Tooltip = ({ clickable && coreStyles['clickable'], )} onTransitionEnd={(event: TransitionEvent) => { - /** - * @warning if `--rt-transition-closing-delay` is set to 0, - * the tooltip will be stuck (but not visible) on the DOM - */ + if (missedTransitionTimerRef.current) { + clearTimeout(missedTransitionTimerRef.current) + } if (show || event.propertyName !== 'opacity') { return } diff --git a/src/utils/css-time-to-ms.ts b/src/utils/css-time-to-ms.ts new file mode 100644 index 00000000..fd038935 --- /dev/null +++ b/src/utils/css-time-to-ms.ts @@ -0,0 +1,11 @@ +export const cssTimeToMs = (time: string): number => { + const match = time.match(/^([\d.]+)(m?s?)$/) + if (!match) { + return 0 + } + const [, amount, unit] = match + if (unit !== 's' && unit !== 'ms') { + return 0 + } + return Number(amount) * (unit === 'ms' ? 1 : 1000) +}