Improve HoverTip placement logic (#395)

This changes the HoverTip placement logic to use measurements from the actual DOM node rather than basing everything on the maximum sizes.

This avoids some counter-intuitive behaviour, most importantly situations where the label would overflow off the left side of the screen for no obvious reason.

Fixes #394
Fixes #256
This commit is contained in:
Jamie Wong 2022-05-17 13:42:13 -07:00 committed by GitHub
parent 48d692c2a3
commit 7ae545a6c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,6 +3,7 @@ import {Sizes, FontSize, FontFamily, ZIndex} from './style'
import {css, StyleSheet} from 'aphrodite'
import {ComponentChildren, h} from 'preact'
import {useTheme, withTheme} from './themes/theme'
import { useCallback } from 'preact/hooks'
interface HovertipProps {
containerSize: Vec2
@ -14,26 +15,50 @@ export function Hovertip(props: HovertipProps) {
const style = getStyle(useTheme())
const {containerSize, offset} = props
const width = containerSize.x
const height = containerSize.y
const positionStyle: {[key: string]: number} = {}
const containerWidth = containerSize.x
const containerHeight = containerSize.y
const OFFSET_FROM_MOUSE = 7
if (offset.x + OFFSET_FROM_MOUSE + Sizes.TOOLTIP_WIDTH_MAX < width) {
positionStyle.left = offset.x + OFFSET_FROM_MOUSE
} else {
positionStyle.right = width - offset.x + 1
}
if (offset.y + OFFSET_FROM_MOUSE + Sizes.TOOLTIP_HEIGHT_MAX < height) {
positionStyle.top = offset.y + OFFSET_FROM_MOUSE
} else {
positionStyle.bottom = height - offset.y + 1
const updateLocation = useCallback((el: HTMLDivElement | null) => {
if (!el) return
const clientRect = el.getBoundingClientRect()
// Place the hovertip to the right of the cursor.
let leftEdgeX = offset.x + OFFSET_FROM_MOUSE
// If this would cause it to overflow the container, align the right
// edge of the hovertip with the right edge of the container.
if (leftEdgeX + clientRect.width > containerWidth - 1) {
leftEdgeX = containerWidth - clientRect.width - 1
// If aligning the right edge overflows the container, align the left edge
// of the hovertip with the left edge of the container.
if (leftEdgeX < 1) { leftEdgeX = 1 }
}
el.style.left = `${leftEdgeX}px`
// Place the tooltip below the cursor
let topEdgeY = offset.y + OFFSET_FROM_MOUSE
// If this would cause it to overflow the container, place the hovertip
// above the cursor instead. This intentionally differs from the horizontal
// axis logic to avoid the cursor being in the middle of a hovertip when
// possible.
if (topEdgeY + clientRect.height > containerHeight - 1) {
topEdgeY = offset.y - clientRect.height - 1
// If placing the hovertip above the cursor overflows the container, align
// the top edge of the hovertip with the top edge of the container.
if (topEdgeY < 1) { topEdgeY = 1 }
}
el.style.top = `${topEdgeY}px`
}, [containerWidth, containerHeight, offset.x, offset.y])
return (
<div className={css(style.hoverTip)} style={positionStyle}>
<div className={css(style.hoverTip)} ref={updateLocation}>
<div className={css(style.hoverTipRow)}>{props.children}</div>
</div>
)