diff --git a/docs/data/charts/tooltip/CustomAxisTooltip.js b/docs/data/charts/tooltip/CustomAxisTooltip.js index c8b63c7f6ceca..b65f7d3df4b23 100644 --- a/docs/data/charts/tooltip/CustomAxisTooltip.js +++ b/docs/data/charts/tooltip/CustomAxisTooltip.js @@ -5,12 +5,11 @@ import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; import { useAxisTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef } from '@mui/x-charts/hooks'; -import { generateVirtualElement } from './generateVirtualElement'; function usePointer() { const svgRef = useSvgRef(); const popperRef = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); // Use a ref to avoid rerendering on every mousemove event. const [pointer, setPointer] = React.useState({ @@ -43,10 +42,10 @@ function usePointer() { }; const handleMove = (event) => { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; @@ -61,7 +60,23 @@ function usePointer() { }; }, [svgRef]); - return { ...pointer, popperRef, anchorEl: virtualElement.current }; + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; } export function CustomAxisTooltip() { diff --git a/docs/data/charts/tooltip/CustomAxisTooltip.tsx b/docs/data/charts/tooltip/CustomAxisTooltip.tsx index 841d740c49ba7..010af780809fe 100644 --- a/docs/data/charts/tooltip/CustomAxisTooltip.tsx +++ b/docs/data/charts/tooltip/CustomAxisTooltip.tsx @@ -5,7 +5,6 @@ import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; import { useAxisTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef } from '@mui/x-charts/hooks'; -import { generateVirtualElement } from './generateVirtualElement'; type PointerState = { isActive: boolean; @@ -16,7 +15,7 @@ type PointerState = { function usePointer(): PointerState & Pick { const svgRef = useSvgRef(); const popperRef: PopperProps['popperRef'] = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); // Use a ref to avoid rerendering on every mousemove event. const [pointer, setPointer] = React.useState({ @@ -49,10 +48,10 @@ function usePointer(): PointerState & Pick { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; @@ -67,7 +66,23 @@ function usePointer(): PointerState & Pick ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; } export function CustomAxisTooltip() { diff --git a/docs/data/charts/tooltip/CustomItemTooltip.js b/docs/data/charts/tooltip/CustomItemTooltip.js index c41324f0d3dfe..2f7c2310c5d53 100644 --- a/docs/data/charts/tooltip/CustomItemTooltip.js +++ b/docs/data/charts/tooltip/CustomItemTooltip.js @@ -6,12 +6,11 @@ import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef } from '@mui/x-charts/hooks'; -import { generateVirtualElement } from './generateVirtualElement'; function usePointer() { const svgRef = useSvgRef(); const popperRef = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); // Use a ref to avoid rerendering on every mousemove event. const [pointer, setPointer] = React.useState({ @@ -44,10 +43,10 @@ function usePointer() { }; const handleMove = (event) => { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; @@ -62,7 +61,23 @@ function usePointer() { }; }, [svgRef]); - return { ...pointer, popperRef, anchorEl: virtualElement.current }; + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; } export function CustomItemTooltip() { diff --git a/docs/data/charts/tooltip/CustomItemTooltip.tsx b/docs/data/charts/tooltip/CustomItemTooltip.tsx index 926b4fd3179d0..077243411c5d3 100644 --- a/docs/data/charts/tooltip/CustomItemTooltip.tsx +++ b/docs/data/charts/tooltip/CustomItemTooltip.tsx @@ -6,7 +6,6 @@ import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef } from '@mui/x-charts/hooks'; -import { generateVirtualElement } from './generateVirtualElement'; type PointerState = { isActive: boolean; @@ -17,7 +16,7 @@ type PointerState = { function usePointer(): PointerState & Pick { const svgRef = useSvgRef(); const popperRef: PopperProps['popperRef'] = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); // Use a ref to avoid rerendering on every mousemove event. const [pointer, setPointer] = React.useState({ @@ -50,10 +49,10 @@ function usePointer(): PointerState & Pick { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; @@ -68,7 +67,23 @@ function usePointer(): PointerState & Pick ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; } export function CustomItemTooltip() { diff --git a/docs/data/charts/tooltip/ItemTooltip.js b/docs/data/charts/tooltip/ItemTooltip.js index cb2a90370e5e9..4e3b52476e277 100644 --- a/docs/data/charts/tooltip/ItemTooltip.js +++ b/docs/data/charts/tooltip/ItemTooltip.js @@ -4,12 +4,11 @@ import Popper from '@mui/material/Popper'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; function usePointer() { const svgRef = useSvgRef(); const popperRef = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); // Use a ref to avoid rerendering on every mousemove event. const [pointer, setPointer] = React.useState({ @@ -42,10 +41,10 @@ function usePointer() { }; const handleMove = (event) => { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; @@ -60,7 +59,23 @@ function usePointer() { }; }, [svgRef]); - return { ...pointer, popperRef, anchorEl: virtualElement.current }; + return { + ...pointer, + popperRef, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; } export function ItemTooltip() { diff --git a/docs/data/charts/tooltip/ItemTooltip.tsx b/docs/data/charts/tooltip/ItemTooltip.tsx index 5069ad32df3df..1f4277d578c7f 100644 --- a/docs/data/charts/tooltip/ItemTooltip.tsx +++ b/docs/data/charts/tooltip/ItemTooltip.tsx @@ -4,7 +4,6 @@ import Popper, { PopperProps } from '@mui/material/Popper'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; type PointerState = { isActive: boolean; @@ -15,7 +14,7 @@ type PointerState = { function usePointer(): PointerState & Pick { const svgRef = useSvgRef(); const popperRef: PopperProps['popperRef'] = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); // Use a ref to avoid rerendering on every mousemove event. const [pointer, setPointer] = React.useState({ @@ -48,10 +47,10 @@ function usePointer(): PointerState & Pick { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; @@ -66,7 +65,23 @@ function usePointer(): PointerState & Pick ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, + }; } export function ItemTooltip() { diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.js b/docs/data/charts/tooltip/ItemTooltipFixedY.js index 11bfb8fe86686..aa11e750dc8dd 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.js +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.js @@ -4,7 +4,6 @@ import Popper from '@mui/material/Popper'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; function usePointer() { const svgRef = useSvgRef(); @@ -56,7 +55,7 @@ export function ItemTooltipFixedY() { const { isActive } = usePointer(); const popperRef = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); const svgRef = useSvgRef(); // Get the ref of the component. const drawingArea = useDrawingArea(); // Get the dimensions of the chart inside the . @@ -67,11 +66,10 @@ export function ItemTooltipFixedY() { } const handleMove = (event) => { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, - // Add the y-coordinate of the to the to margin between the and the drawing area - y: svgRef.current.getBoundingClientRect().top + drawingArea.top, - }); + y: event.clientY, + }; popperRef.current?.update(); }; @@ -96,7 +94,19 @@ export function ItemTooltipFixedY() { }} open placement="top" - anchorEl={virtualElement.current} + anchorEl={{ + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} popperRef={popperRef} > diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx index b6feefe896f41..925b27453789c 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx @@ -4,7 +4,6 @@ import Popper, { PopperProps } from '@mui/material/Popper'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; type PointerState = { isActive: boolean; @@ -62,7 +61,7 @@ export function ItemTooltipFixedY() { const { isActive } = usePointer(); const popperRef: PopperProps['popperRef'] = React.useRef(null); - const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + const positionRef = React.useRef({ x: 0, y: 0 }); const svgRef = useSvgRef(); // Get the ref of the component. const drawingArea = useDrawingArea(); // Get the dimensions of the chart inside the . @@ -73,11 +72,10 @@ export function ItemTooltipFixedY() { } const handleMove = (event: PointerEvent) => { - virtualElement.current = generateVirtualElement({ + positionRef.current = { x: event.clientX, - // Add the y-coordinate of the to the to margin between the and the drawing area - y: svgRef.current.getBoundingClientRect().top + drawingArea.top, - }); + y: event.clientY, + }; popperRef.current?.update(); }; @@ -102,7 +100,19 @@ export function ItemTooltipFixedY() { }} open placement="top" - anchorEl={virtualElement.current} + anchorEl={{ + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} popperRef={popperRef} > diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.js b/docs/data/charts/tooltip/ItemTooltipTopElement.js index a3b52483984e4..538a729564c9b 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.js +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.js @@ -5,7 +5,6 @@ import Popper from '@mui/material/Popper'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef, useXAxis, useXScale, useYScale } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; function usePointer() { const svgRef = useSvgRef(); @@ -102,7 +101,19 @@ export function ItemTooltipTopElement() { }} open placement="top" - anchorEl={generateVirtualElement(tooltipPosition)} + anchorEl={{ + getBoundingClientRect: () => ({ + x: tooltipPosition.x, + y: tooltipPosition.y, + top: tooltipPosition.y, + left: tooltipPosition.x, + right: tooltipPosition.x, + bottom: tooltipPosition.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} > diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx index a84a45cdc39b1..9c73fa1d43831 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx @@ -5,7 +5,6 @@ import Popper from '@mui/material/Popper'; import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef, useXAxis, useXScale, useYScale } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement } from './generateVirtualElement'; type PointerState = { isActive: boolean; @@ -110,7 +109,19 @@ export function ItemTooltipTopElement() { }} open placement="top" - anchorEl={generateVirtualElement(tooltipPosition)} + anchorEl={{ + getBoundingClientRect: () => ({ + x: tooltipPosition.x, + y: tooltipPosition.y, + top: tooltipPosition.y, + left: tooltipPosition.x, + right: tooltipPosition.x, + bottom: tooltipPosition.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }} > diff --git a/docs/data/charts/tooltip/generateVirtualElement.ts b/docs/data/charts/tooltip/generateVirtualElement.ts deleted file mode 100644 index 8052c84a24e14..0000000000000 --- a/docs/data/charts/tooltip/generateVirtualElement.ts +++ /dev/null @@ -1,44 +0,0 @@ -export type MousePosition = { - x: number; - y: number; - pointerType: 'mouse' | 'touch' | 'pen'; - height: number; -}; - -/** - * Helper faking an element bounding box for the Popper. - */ -export function generateVirtualElement(mousePosition: { x: number; y: number } | null) { - if (mousePosition === null) { - return { - getBoundingClientRect: () => ({ - width: 0, - height: 0, - x: 0, - y: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - toJSON: () => '', - }), - }; - } - const { x, y } = mousePosition; - const boundingBox = { - width: 0, - height: 0, - x, - y, - top: y, - right: x, - bottom: y, - left: x, - }; - return { - getBoundingClientRect: () => ({ - ...boundingBox, - toJSON: () => JSON.stringify(boundingBox), - }), - }; -} diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx index 51c770bbb827c..560a83ccc9a44 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; +import useLazyRef from '@mui/utils/useLazyRef'; import { styled, useThemeProps, SxProps, Theme } from '@mui/material/styles'; import Popper, { PopperProps as BasePopperProps } from '@mui/material/Popper'; import NoSsr from '@mui/material/NoSsr'; @@ -12,13 +13,7 @@ import { ItemInteractionData, } from '../context/InteractionProvider'; import { useSvgRef } from '../hooks/useSvgRef'; -import { - generateVirtualElement, - getTooltipHasData, - TriggerOptions, - usePointerType, - VirtualElement, -} from './utils'; +import { getTooltipHasData, TriggerOptions, usePointerType } from './utils'; import { ChartSeriesType } from '../models/seriesType/config'; import { ChartsItemContentProps, ChartsItemTooltipContent } from './ChartsItemTooltipContent'; import { ChartsAxisContentProps, ChartsAxisTooltipContent } from './ChartsAxisTooltipContent'; @@ -140,10 +135,7 @@ function ChartsTooltip(inProps: ChartsTooltipProps const popperRef: PopperProps['popperRef'] = React.useRef(null); - const virtualElement = React.useRef(null); - if (virtualElement.current === null) { - virtualElement.current = generateVirtualElement(null); - } + const positionRef = useLazyRef(() => ({ x: 0, y: 0 })); const { item, axis } = React.useContext(InteractionContext); @@ -162,7 +154,19 @@ function ChartsTooltip(inProps: ChartsTooltipProps open: popperOpen, placement: pointerType?.pointerType === 'mouse' ? ('right-start' as const) : ('top' as const), popperRef, - anchorEl: virtualElement.current, + anchorEl: { + getBoundingClientRect: () => ({ + x: positionRef.current.x, + y: positionRef.current.y, + top: positionRef.current.y, + left: positionRef.current.x, + right: positionRef.current.x, + bottom: positionRef.current.y, + width: 0, + height: 0, + toJSON: () => '', + }), + }, modifiers: [ { name: 'offset', @@ -182,10 +186,11 @@ function ChartsTooltip(inProps: ChartsTooltipProps } const handleMove = (event: PointerEvent) => { - virtualElement.current = generateVirtualElement({ + // eslint-disable-next-line react-compiler/react-compiler + positionRef.current = { x: event.clientX, y: event.clientY, - }); + }; popperRef.current?.update(); }; element.addEventListener('pointermove', handleMove); @@ -193,7 +198,7 @@ function ChartsTooltip(inProps: ChartsTooltipProps return () => { element.removeEventListener('pointermove', handleMove); }; - }, [svgRef]); + }, [svgRef, positionRef]); if (trigger === 'none') { return null; diff --git a/packages/x-charts/src/ChartsTooltip/utils.tsx b/packages/x-charts/src/ChartsTooltip/utils.tsx index 24e2e2220391b..227c7f58e24ea 100644 --- a/packages/x-charts/src/ChartsTooltip/utils.tsx +++ b/packages/x-charts/src/ChartsTooltip/utils.tsx @@ -10,61 +10,6 @@ type MousePosition = { height: number; }; -export type VirtualElement = { - getBoundingClientRect: () => { - width: number; - height: number; - x: number; - y: number; - top: number; - right: number; - bottom: number; - left: number; - toJSON: () => string; - }; -}; -/** - * Generate a virtual element for the tooltip. - * Default to (0, 0) is the argument is not provided, or null. - * @param mousePosition { x: number, y: number} - */ -export function generateVirtualElement( - mousePosition?: Pick | null, -): VirtualElement { - if (!mousePosition) { - return { - getBoundingClientRect: () => ({ - width: 0, - height: 0, - x: 0, - y: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - toJSON: () => '', - }), - }; - } - const { x, y } = mousePosition; - const boundingBox = { - width: 0, - height: 0, - x, - y, - top: y, - right: x, - bottom: y, - left: x, - }; - return { - getBoundingClientRect: () => ({ - ...boundingBox, - toJSON: () => JSON.stringify(boundingBox), - }), - }; -} - export type UseMouseTrackerReturnValue = null | MousePosition; /**