From 078ca1824075f497dc2f17f6d6c0f87ae3c6a66d Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 4 Feb 2021 11:27:52 -0800 Subject: [PATCH] new(tooltip, xychart): force update TooltipInPortal bounds (#1045) * new(tooltip/useTooltipInPortal): expose forceRefreshBounds * new(xychart/Tooltip): force update tooltip parent bounds when tooltip becomes visible * test(xychart/Tooltip): fix test --- .../src/hooks/useTooltipInPortal.tsx | 4 +++- .../visx-xychart/src/components/Tooltip.tsx | 17 +++++++++++++++-- .../test/components/Tooltip.test.tsx | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/visx-tooltip/src/hooks/useTooltipInPortal.tsx b/packages/visx-tooltip/src/hooks/useTooltipInPortal.tsx index 7dd4c112d..d5ff55c28 100644 --- a/packages/visx-tooltip/src/hooks/useTooltipInPortal.tsx +++ b/packages/visx-tooltip/src/hooks/useTooltipInPortal.tsx @@ -10,6 +10,7 @@ export type TooltipInPortalProps = TooltipProps & Pick void; containerBounds: RectReadOnly; + forceRefreshBounds: () => void; TooltipInPortal: React.FC; }; @@ -32,7 +33,7 @@ export default function useTooltipInPortal({ detectBounds: detectBoundsOption = true, ...useMeasureOptions }: UseTooltipPortalOptions | undefined = {}): UseTooltipInPortal { - const [containerRef, containerBounds] = useMeasure(useMeasureOptions); + const [containerRef, containerBounds, forceRefreshBounds] = useMeasure(useMeasureOptions); const TooltipInPortal = useMemo( () => ({ @@ -61,6 +62,7 @@ export default function useTooltipInPortal({ // @ts-ignore fixed here https://github.com/react-spring/react-use-measure/pull/17 containerRef, containerBounds, + forceRefreshBounds, TooltipInPortal, }; } diff --git a/packages/visx-xychart/src/components/Tooltip.tsx b/packages/visx-xychart/src/components/Tooltip.tsx index 49c468e27..cf8da7289 100644 --- a/packages/visx-xychart/src/components/Tooltip.tsx +++ b/packages/visx-xychart/src/components/Tooltip.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useEffect, useRef } from 'react'; import { useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { TooltipProps as BaseTooltipProps } from '@visx/tooltip/lib/tooltips/Tooltip'; import { PickD3Scale } from '@visx/scale'; @@ -93,7 +93,7 @@ export default function Tooltip({ const { colorScale, theme, innerHeight, innerWidth, margin, xScale, yScale, dataRegistry } = useContext(DataContext) || {}; const tooltipContext = useContext(TooltipContext) as TooltipContextType; - const { containerRef, TooltipInPortal } = useTooltipInPortal({ + const { containerRef, TooltipInPortal, forceRefreshBounds } = useTooltipInPortal({ debounce, detectBounds, polyfill: resizeObserverPolyfill, @@ -115,6 +115,19 @@ export default function Tooltip({ const showTooltip = tooltipContext?.tooltipOpen && tooltipContent != null; + // useTooltipInPortal is powered by react-use-measure and will update portal positions upon + // resize and page scroll. however it **cannot** detect when a chart container moves on a + // page due to animation or drag-and-drop, etc. + // therefore we force refresh the bounds any time we transition from a hidden tooltip to + // one that is visible. + const lastShowTooltip = useRef(false); + useEffect(() => { + if (showTooltip && !lastShowTooltip.current) { + forceRefreshBounds(); + } + lastShowTooltip.current = showTooltip; + }, [showTooltip, forceRefreshBounds]); + let tooltipLeft = tooltipContext?.tooltipLeft; let tooltipTop = tooltipContext?.tooltipTop; diff --git a/packages/visx-xychart/test/components/Tooltip.test.tsx b/packages/visx-xychart/test/components/Tooltip.test.tsx index f51550e39..23e0dc957 100644 --- a/packages/visx-xychart/test/components/Tooltip.test.tsx +++ b/packages/visx-xychart/test/components/Tooltip.test.tsx @@ -77,7 +77,7 @@ describe('', () => { props: { renderTooltip }, context: { tooltipOpen: true }, }); - expect(renderTooltip).toHaveBeenCalledTimes(1); + expect(renderTooltip).toHaveBeenCalled(); // may be invoked more than once due to forceRefreshBounds invocation }); it('should render a vertical crosshair if showVerticalCrossHair=true', () => {