diff --git a/docs/docs/options.mdx b/docs/docs/options.mdx index 554e8001..c767c4e1 100644 --- a/docs/docs/options.mdx +++ b/docs/docs/options.mdx @@ -112,7 +112,7 @@ import { Tooltip } from 'react-tooltip'; | `noArrow` | `boolean` | no | `false` | `true` `false` | Tooltip arrow will not be shown | | `clickable` | `boolean` | no | `false` | `true` `false` | Allow interaction with elements inside the tooltip. Useful when using buttons and inputs | | `closeOnEsc` | `boolean` | no | `false` | `true` `false` | Pressing escape key will close the tooltip | -| `closeOnScroll` | `boolean` | no | `false` | `true` `false` | Scrolling anywhere on the window will close the tooltip | +| `closeOnScroll` | `boolean` | no | `false` | `true` `false` | Scrolling will close the tooltip (for this to work, scroll element must be either the root html tag, the tooltip parent, or the anchor parent) | | `closeOnEsc` | `boolean` | no | `false` | `true` `false` | Resizing the window will close the tooltip | | `style` | `CSSProperties` | no | | a React inline style | Add inline styles directly to the tooltip | | `position` | `{ x: number; y: number }` | no | | any `number` value for both `x` and `y` | Override the tooltip position on the DOM | diff --git a/docs/docs/troubleshooting.mdx b/docs/docs/troubleshooting.mdx index 08ac2de8..ad3bfba5 100644 --- a/docs/docs/troubleshooting.mdx +++ b/docs/docs/troubleshooting.mdx @@ -64,43 +64,34 @@ If you've imported the default styling and the tooltip is still not showing up w If `data-tooltip-content` and `data-tooltip-html` are both unset (or they have empty values) on the anchor element, and also the `content`, `render`, and `children` props on the tooltip are unset (or have empty values), the tooltip is not shown by default. -## Next.js `TypeError: f is not a function` -This problem seems to be caused by a bug related to the SWC bundler used by Next.js. -The best way to solve this is to upgrade to `next@13.3.0` or later versions. +## The tooltip doesn't move when scrolling -Less ideally, if you're unable to upgrade, you can set `swcMinify: false` on your `next.config.js` file. +If your anchor element is inside a scrolling element, your tooltip might get "stuck" in place when scrolling. +There are two ways to avoid this. -## Next.js `"use client"` error +### Change your CSS (recommended) -This normally happens when you use `react-tooltip` inside a component that is not tagged as client component. For more info, see the [Next.js docs](https://nextjs.org/docs/getting-started/react-essentials#client-components). +For the tooltip to be properly placed inside a scrolling element, the following conditions must be met: -To use `react-tooltip` on Next.js 13 without having to tag your component or page as a client component, just create a new file `ReactTooltip.tsx` (for this example, the file path is `src/components/ReactTooltip.tsx`) and place the following code inside of the created file: +1. The tooltip component has to be inside the scrolling element (placing it as a direct child is **not** required) +2. The `positionStrategy` tooltip prop must be unset, or set to the default (`absolute`) +3. The scrolling element should have the CSS attribute `position: relative` -:::caution +:::info -Avoid naming the file `react-tooltip.tsx` (or with whichever extension your project uses), since it may interfere with your editor's autocomplete funcionality. +The `position: relative` attribute can be set on any element on the DOM structure between the scrolling element and the tooltip. +This means the tooltip component doesn't have to be a direct child of the scrolling element. ::: -```jsx -// src/components/ReactTooltip.tsx -'use client' - -export * from 'react-tooltip' -``` - -And in the place that you are importing React Tooltip: +### Use `closeOnScroll` prop -```jsx -// ❌ Old -import { Tooltip } from 'react-tooltip' +```tsx + ``` -```jsx -// ✅ New -import { Tooltip } from 'components/react-tooltip' -``` +When `closeOnScroll` is set, scrolling will immediately close the tooltip (`closeOnResize` also exists for closing when resizing the window). ## Bad performance @@ -156,3 +147,41 @@ Check the examples for the [`anchorSelect`](./examples/anchor-select) and [`rend ``` + +## Next.js `TypeError: f is not a function` + +This problem seems to be caused by a bug related to the SWC bundler used by Next.js. +The best way to solve this is to upgrade to `next@13.3.0` or later versions. + +Less ideally, if you're unable to upgrade, you can set `swcMinify: false` on your `next.config.js` file. + +## Next.js `"use client"` error + +This normally happens when you use `react-tooltip` inside a component that is not tagged as client component. For more info, see the [Next.js docs](https://nextjs.org/docs/getting-started/react-essentials#client-components). + +To use `react-tooltip` on Next.js 13 without having to tag your component or page as a client component, just create a new file `ReactTooltip.tsx` (for this example, the file path is `src/components/ReactTooltip.tsx`) and place the following code inside of the created file: + +:::caution + +Avoid naming the file `react-tooltip.tsx` (or with whichever extension your project uses), since it may interfere with your editor's autocomplete functionality. + +::: + +```jsx +// src/components/ReactTooltip.tsx +'use client' + +export { Tooltip } from 'react-tooltip' +``` + +And in the place that you are importing React Tooltip: + +```jsx +// ❌ Old +import { Tooltip } from 'react-tooltip' +``` + +```jsx +// ✅ New +import { Tooltip } from 'components/react-tooltip' +``` diff --git a/docs/docs/upgrade-guide/changelog-v4-v5.md b/docs/docs/upgrade-guide/changelog-v4-v5.md index 3b480fcc..1f884742 100644 --- a/docs/docs/upgrade-guide/changelog-v4-v5.md +++ b/docs/docs/upgrade-guide/changelog-v4-v5.md @@ -51,7 +51,7 @@ If you run into any problems with the tooltip not updating after changes are mad - [x] `hidden` - `boolean` - when set, the tooltip will not show - [x] `render` - `function` - can be used to render dynamic content based on the active anchor element (check [the examples](../examples/render.mdx) for more details) - [x] `closeOnEsc` - `boolean` - when set, the tooltip will close after pressing the escape key -- [x] `closeOnScroll` - `boolean` - when set, the tooltip will close when scrolling anywhere on the window (same as V4's `scrollHide`) +- [x] `closeOnScroll` - `boolean` - when set, the tooltip will close when scrolling (similar to V4's `scrollHide`) - [x] `closeOnResize` - `boolean` - when set, the tooltip will close when resizing the window (same as V4's `resizeHide`) ## `V4` props available in `V5` diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 20b39b40..a9760bd1 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -3,7 +3,8 @@ import classNames from 'classnames' import debounce from 'utils/debounce' import { useTooltip } from 'components/TooltipProvider' import useIsomorphicLayoutEffect from 'utils/use-isomorphic-layout-effect' -import { computeTooltipPosition } from '../../utils/compute-positions' +import { getScrollParent } from 'utils/get-scroll-parent' +import { computeTooltipPosition } from 'utils/compute-positions' import styles from './styles.module.css' import type { IPosition, ITooltip, PlacesType } from './TooltipTypes' @@ -278,13 +279,6 @@ const Tooltip = ({ handleShow(false) } - const handleEsc = (event: KeyboardEvent) => { - if (event.key !== 'Escape') { - return - } - handleShow(false) - } - // debounce handler to prevent call twice when // mouse enter and focus events being triggered toggether const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50, true) @@ -302,12 +296,29 @@ const Tooltip = ({ elementRefs.add({ current: anchorById }) } + const handleScrollResize = () => { + handleShow(false) + } + + const anchorScrollParent = getScrollParent(activeAnchor) + const tooltipScrollParent = getScrollParent(tooltipRef.current) + if (closeOnScroll) { - window.addEventListener('scroll', debouncedHandleHideTooltip) + window.addEventListener('scroll', handleScrollResize) + anchorScrollParent?.addEventListener('scroll', handleScrollResize) + tooltipScrollParent?.addEventListener('scroll', handleScrollResize) } if (closeOnResize) { - window.addEventListener('resize', debouncedHandleHideTooltip) + window.addEventListener('resize', handleScrollResize) } + + const handleEsc = (event: KeyboardEvent) => { + if (event.key !== 'Escape') { + return + } + handleShow(false) + } + if (closeOnEsc) { window.addEventListener('keydown', handleEsc) } @@ -353,10 +364,12 @@ const Tooltip = ({ return () => { if (closeOnScroll) { - window.removeEventListener('scroll', debouncedHandleHideTooltip) + window.removeEventListener('scroll', handleScrollResize) + anchorScrollParent?.removeEventListener('scroll', handleScrollResize) + tooltipScrollParent?.removeEventListener('scroll', handleScrollResize) } if (closeOnResize) { - window.removeEventListener('resize', debouncedHandleHideTooltip) + window.removeEventListener('resize', handleScrollResize) } if (shouldOpenOnClick) { window.removeEventListener('click', handleClickOutsideAnchors) diff --git a/src/utils/get-scroll-parent.ts b/src/utils/get-scroll-parent.ts new file mode 100644 index 00000000..b421d572 --- /dev/null +++ b/src/utils/get-scroll-parent.ts @@ -0,0 +1,24 @@ +const isScrollable = (node: Element) => { + if (!(node instanceof HTMLElement || node instanceof SVGElement)) { + return false + } + const style = getComputedStyle(node) + return ['overflow', 'overflow-x', 'overflow-y'].some((propertyName) => { + const value = style.getPropertyValue(propertyName) + return value === 'auto' || value === 'scroll' + }) +} + +export const getScrollParent = (node: Element | null) => { + if (!node) { + return null + } + let currentParent = node.parentElement + while (currentParent) { + if (isScrollable(currentParent)) { + return currentParent + } + currentParent = currentParent.parentElement + } + return document.scrollingElement || document.documentElement +}