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
+}