// Copyright 2022 The Parca Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* eslint-disable react-hooks/refs */ import React, {useEffect, useState} from 'react'; import {flip, offset, shift, useFloating, type VirtualElement} from '@floating-ui/react'; import {pointer} from 'd3-selection'; interface GraphTooltipProps { children: React.ReactNode; contextElement: Element | null; frozen?: boolean; } function createPositionedVirtualElement(contextElement: Element, x = 0, y = 0): VirtualElement { const domRect = contextElement.getBoundingClientRect(); return { getBoundingClientRect: () => ({ width: 0, height: 0, top: domRect.y + y, left: domRect.x + x, right: domRect.x + x, bottom: domRect.y + y, x: domRect.x + x, y: domRect.y + y, toJSON: () => ({}), }), }; } const GraphTooltip = ({ children, contextElement, frozen = false, }: GraphTooltipProps): React.JSX.Element => { 'use no memo'; const [isPositioned, setIsPositioned] = useState(false); const {refs, floatingStyles, update} = useFloating({ placement: 'bottom-start', strategy: 'fixed', middleware: [ offset({ mainAxis: 30, crossAxis: 30, }), flip(), shift({ padding: 20, }), ], whileElementsMounted: undefined, }); // Read the latest `frozen` value from inside the mousemove handler without // re-binding the listener every time it toggles. const frozenRef = React.useRef(frozen); frozenRef.current = frozen; useEffect(() => { if (contextElement === null) return; const onMouseMove: EventListenerOrEventListenerObject = (e: Event) => { if (frozenRef.current) return; // Hold position while ⇧ Shift is held. const rel = pointer(e); const tooltipX = rel[0]; const tooltipY = rel[1]; const virtualElement = createPositionedVirtualElement(contextElement, tooltipX, tooltipY); refs.setReference(virtualElement); setIsPositioned(true); update(); }; contextElement.addEventListener('mousemove', onMouseMove); return () => { contextElement.removeEventListener('mousemove', onMouseMove); }; }, [contextElement, update, refs]); return (
{children}
); }; export default GraphTooltip;