From 7ae545a6c3e3abe5085670e710c77b75bec51a6b Mon Sep 17 00:00:00 2001 From: Jamie Wong Date: Tue, 17 May 2022 13:42:13 -0700 Subject: [PATCH] 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 --- src/views/hovertip.tsx | 55 ++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/views/hovertip.tsx b/src/views/hovertip.tsx index 245867f..e32d1b7 100644 --- a/src/views/hovertip.tsx +++ b/src/views/hovertip.tsx @@ -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 ( -
+
{props.children}
)