From b0b2ba0157edfc6b0d66eec7d46eb3da007bdebf Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 15 Mar 2021 18:08:43 +0100 Subject: [PATCH] TooltipContainer - use resize observer instead of getClientBoundingRect (#31937) * TooltipContainer - use resize observer instead of getClientBoundingRect * Well, let's screw Safari, shall we? * Memoize resize observer * Make ts happy happy happy --- packages/grafana-data/src/types/geometry.ts | 7 ++++ .../src/components/Chart/TooltipContainer.tsx | 39 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/grafana-data/src/types/geometry.ts b/packages/grafana-data/src/types/geometry.ts index a4a4558af8e..9bb223e56fe 100644 --- a/packages/grafana-data/src/types/geometry.ts +++ b/packages/grafana-data/src/types/geometry.ts @@ -5,3 +5,10 @@ export interface CartesianCoords2D { x: number; y: number; } +/** + * 2d object dimensions. + */ +export interface Dimensions2D { + width: number; + height: number; +} diff --git a/packages/grafana-ui/src/components/Chart/TooltipContainer.tsx b/packages/grafana-ui/src/components/Chart/TooltipContainer.tsx index 3efedc5a846..6ba4d183b4e 100644 --- a/packages/grafana-ui/src/components/Chart/TooltipContainer.tsx +++ b/packages/grafana-ui/src/components/Chart/TooltipContainer.tsx @@ -1,9 +1,9 @@ -import React, { useState, useLayoutEffect, useRef, HTMLAttributes } from 'react'; +import React, { useState, useLayoutEffect, useRef, HTMLAttributes, useMemo } from 'react'; import { stylesFactory } from '../../themes/stylesFactory'; import { css, cx } from 'emotion'; import { useTheme } from '../../themes/ThemeContext'; import useWindowSize from 'react-use/lib/useWindowSize'; -import { GrafanaTheme } from '@grafana/data'; +import { Dimensions2D, GrafanaTheme } from '@grafana/data'; interface TooltipContainerProps extends HTMLAttributes { position: { x: number; y: number }; @@ -20,18 +20,49 @@ export const TooltipContainer: React.FC = ({ }) => { const theme = useTheme(); const tooltipRef = useRef(null); + const tooltipMeasurementRef = useRef({ width: 0, height: 0 }); const { width, height } = useWindowSize(); const [placement, setPlacement] = useState({ x: positionX + offsetX, y: positionY + offsetY, }); + const resizeObserver = useMemo( + () => + // TS has hard time playing games with @types/resize-observer-browser, hence the ignore + // @ts-ignore + new ResizeObserver((entries) => { + for (let entry of entries) { + const tW = Math.floor(entry.contentRect.width + 2 * 8); // adding padding until Safari supports borderBoxSize + const tH = Math.floor(entry.contentRect.height + 2 * 8); + + if (tooltipMeasurementRef.current.width !== tW || tooltipMeasurementRef.current.height !== tH) { + tooltipMeasurementRef.current = { + width: tW, + height: tH, + }; + } + } + }), + [] + ); + + useLayoutEffect(() => { + if (tooltipRef.current) { + resizeObserver.observe(tooltipRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [resizeObserver]); + // Make sure tooltip does not overflow window useLayoutEffect(() => { let xO = 0, yO = 0; if (tooltipRef && tooltipRef.current) { - const measurement = tooltipRef.current.getBoundingClientRect(); + const measurement = tooltipMeasurementRef.current; const xOverflow = width - (positionX + measurement.width); const yOverflow = height - (positionY + measurement.height); if (xOverflow < 0) { @@ -47,7 +78,7 @@ export const TooltipContainer: React.FC = ({ x: positionX + offsetX - xO, y: positionY + offsetY - yO, }); - }, [tooltipRef, width, height, positionX, offsetX, positionY, offsetY]); + }, [width, height, positionX, offsetX, positionY, offsetY]); const styles = getTooltipContainerStyles(theme);