Skip to content

closeOnScroll improvements #1045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/docs/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
77 changes: 53 additions & 24 deletions docs/docs/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 `[email protected]` 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
<Tooltip closeOnScroll={true} />
```

```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

Expand Down Expand Up @@ -156,3 +147,41 @@ Check the examples for the [`anchorSelect`](./examples/anchor-select) and [`rend
</div>
<Tooltip id="my-tooltip" />
```

## 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 `[email protected]` 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'
```
2 changes: 1 addition & 1 deletion docs/docs/upgrade-guide/changelog-v4-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
37 changes: 25 additions & 12 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions src/utils/get-scroll-parent.ts
Original file line number Diff line number Diff line change
@@ -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
}