Repository: mui/mui-x
Pull request #17733: [charts] Add zoom slider tooltip
Author: login: bernardobelchior
Labels: name: type: new feature
description: Expand the scope of the product to solve a new problem.
name: scope: charts
description: Changes related to the charts.
Created: 2025-05-07T07:46:38Z | Merged: 2025-05-20T08:03:53Z

Part of https://github.com/mui/mui-x/issues/15383.


https://github.com/user-attachments/assets/cd8a4e01-bc41-4650-8eaf-e98d1cb2c119




Diff:
diff --git a/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx b/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx
index b2a55c8d4d0e3..efbcf545cb94b 100644
--- a/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx
+++ b/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx
@@ -16,6 +16,7 @@ import {
 import { styled } from '@mui/material/styles';
 import { useXAxes, useYAxes } from '@mui/x-charts/hooks';
 import { rafThrottle } from '@mui/x-internals/rafThrottle';
+import { ChartsTooltipZoomSliderValue } from './ChartsTooltipZoomSliderValue';
 import {
   selectorChartAxisZoomData,
   UseChartProZoomSignature,
@@ -81,11 +82,12 @@ export function ChartAxisZoomSlider({ axisDirection, axisId }: ChartZoomSliderPr
   let x: number;
   let y: number;
   let reverse: boolean;
+  let axisPosition: 'top' | 'bottom' | 'left' | 'right';
 
   if (axisDirection === 'x') {
     const axis = xAxis[axisId];
 
-    if (!axis) {
+    if (!axis || axis.position === 'none') {
       return null;
     }
 
@@ -97,10 +99,11 @@ export function ChartAxisZoomSlider({ axisDirection, axisId }: ChartZoomSliderPr
         ? drawingArea.top + drawingArea.height + axis.offset + axisSize + ZOOM_SLIDER_MARGIN
         : drawingArea.top - axis.offset - axisSize - ZOOM_SLIDER_SIZE - ZOOM_SLIDER_MARGIN;
     reverse = axis.reverse ?? false;
+    axisPosition = axis.position ?? 'bottom';
   } else {
     const axis = yAxis[axisId];
 
-    if (!axis) {
+    if (!axis || axis.position === 'none') {
       return null;
     }
 
@@ -112,6 +115,7 @@ export function ChartAxisZoomSlider({ axisDirection, axisId }: ChartZoomSliderPr
         : drawingArea.left - axis.offset - axisSize - ZOOM_SLIDER_SIZE - ZOOM_SLIDER_MARGIN;
     y = drawingArea.top;
     reverse = axis.reverse ?? false;
+    axisPosition = axis.position ?? 'left';
   }
 
   const backgroundRectOffset = (ZOOM_SLIDER_SIZE - ZOOM_SLIDER_BACKGROUND_SIZE) / 2;
@@ -129,6 +133,7 @@ export function ChartAxisZoomSlider({ axisDirection, axisId }: ChartZoomSliderPr
       <ChartAxisZoomSliderSpan
         zoomData={zoomData}
         axisId={axisId}
+        axisPosition={axisPosition}
         axisDirection={axisDirection}
         reverse={reverse}
       />
@@ -136,21 +141,32 @@ export function ChartAxisZoomSlider({ axisDirection, axisId }: ChartZoomSliderPr
   );
 }
 
+const formatter = Intl.NumberFormat(undefined, { maximumFractionDigits: 0 });
+const zoomValueFormatter = (value: number) => formatter.format(value);
+
 function ChartAxisZoomSliderSpan({
   axisId,
   axisDirection,
+  axisPosition,
   zoomData,
   reverse,
+  valueFormatter = zoomValueFormatter,
 }: {
   axisId: AxisId;
   axisDirection: 'x' | 'y';
+  axisPosition: 'top' | 'bottom' | 'left' | 'right';
   zoomData: ZoomData;
   reverse: boolean;
+  valueFormatter?: (value: number) => string;
 }) {
   const { instance, svgRef } = useChartContext<[UseChartProZoomSignature]>();
   const store = useStore<[UseChartProZoomSignature]>();
   const drawingArea = useDrawingArea();
   const activePreviewRectRef = React.useRef<SVGRectElement>(null);
+  const [startHandleEl, setStartHandleEl] = React.useState<SVGRectElement | null>(null);
+  const [endHandleEl, setEndHandleEl] = React.useState<SVGRectElement | null>(null);
+  const [showTooltip, setShowTooltip] = React.useState<null | 'start' | 'end' | 'both'>(null);
+
   const previewHandleWidth =
     axisDirection === 'x' ? ZOOM_SLIDER_HANDLE_WIDTH : ZOOM_SLIDER_HANDLE_HEIGHT;
   const previewHandleHeight =
@@ -201,6 +217,7 @@ function ChartAxisZoomSliderSpan({
     const onPointerUp = () => {
       activePreviewRect.removeEventListener('pointermove', onPointerMove);
       activePreviewRect.removeEventListener('pointerup', onPointerUp);
+      setShowTooltip(null);
     };
 
     const onPointerDown = (event: PointerEvent) => {
@@ -234,6 +251,7 @@ function ChartAxisZoomSliderSpan({
       pointerZoomMin = pointerDownZoom - axisZoomData.start;
       pointerZoomMax = 100 - (axisZoomData.end - pointerDownZoom);
 
+      setShowTooltip('both');
       activePreviewRect.addEventListener('pointerup', onPointerUp);
       activePreviewRect.addEventListener('pointermove', onPointerMove);
     };
@@ -247,7 +265,7 @@ function ChartAxisZoomSliderSpan({
     };
   }, [axisDirection, axisId, instance, reverse, store, svgRef]);
 
-  const onResizeStart = (event: PointerEvent) => {
+  const onStartHandleMove = (event: PointerEvent) => {
     const element = svgRef.current;
 
     if (!element) {
@@ -286,7 +304,7 @@ function ChartAxisZoomSliderSpan({
     });
   };
 
-  const onResizeEnd = (event: PointerEvent) => {
+  const onEndHandleMove = (event: PointerEvent) => {
     const element = svgRef.current;
 
     if (!element) {
@@ -386,28 +404,47 @@ function ChartAxisZoomSliderSpan({
         y={previewY + (axisDirection === 'x' ? previewOffset : 0)}
         width={previewWidth}
         height={previewHeight}
+        onPointerEnter={() => setShowTooltip('both')}
+        onPointerLeave={() => setShowTooltip(null)}
       />
-      {
-        // TODO: In RTL languages, should we start from the right?
-      }
       <ChartAxisZoomSliderHandle
+        ref={setStartHandleEl}
         x={startHandleX}
         y={startHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeStart}
+        onMove={onStartHandleMove}
+        onPointerEnter={() => setShowTooltip('start')}
+        onPointerLeave={() => setShowTooltip(null)}
         placement="start"
       />
       <ChartAxisZoomSliderHandle
+        ref={setEndHandleEl}
         x={endHandleX}
         y={endHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeEnd}
+        onMove={onEndHandleMove}
+        onPointerEnter={() => setShowTooltip('end')}
+        onPointerLeave={() => setShowTooltip(null)}
         placement="end"
       />
+      <ChartsTooltipZoomSliderValue
+        anchorEl={startHandleEl}
+        open={showTooltip === 'start' || showTooltip === 'both'}
+        placement={axisPosition}
+      >
+        {valueFormatter(zoomData.start)}
+      </ChartsTooltipZoomSliderValue>
+      <ChartsTooltipZoomSliderValue
+        anchorEl={endHandleEl}
+        open={showTooltip === 'end' || showTooltip === 'both'}
+        placement={axisPosition}
+      >
+        {valueFormatter(zoomData.end)}
+      </ChartsTooltipZoomSliderValue>
     </React.Fragment>
   );
 }
diff --git a/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSliderHandle.tsx b/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSliderHandle.tsx
index 73fa68f8b8db9..619152ff5553a 100644
--- a/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSliderHandle.tsx
+++ b/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSliderHandle.tsx
@@ -5,6 +5,7 @@ import { styled } from '@mui/material/styles';
 import useForkRef from '@mui/utils/useForkRef';
 import useEventCallback from '@mui/utils/useEventCallback';
 import { rafThrottle } from '@mui/x-internals/rafThrottle';
+import clsx from 'clsx';
 import {
   chartAxisZoomSliderHandleClasses,
   useUtilityClasses,
@@ -30,13 +31,13 @@ const Rect = styled('rect')(({ theme }) => ({
 }));
 
 export interface ChartZoomSliderHandleOwnerState {
-  onResize: (event: PointerEvent) => void;
+  onMove: (event: PointerEvent) => void;
   orientation: 'horizontal' | 'vertical';
   placement: 'start' | 'end';
 }
 
 export interface ChartZoomSliderHandleProps
-  extends Pick<React.ComponentProps<'rect'>, 'x' | 'y' | 'width' | 'height' | 'rx' | 'ry'>,
+  extends Omit<React.ComponentProps<'rect'>, 'orientation'>,
     ChartZoomSliderHandleOwnerState {}
 
 /**
@@ -47,15 +48,15 @@ export const ChartAxisZoomSliderHandle = React.forwardRef<
   SVGRectElement,
   ChartZoomSliderHandleProps
 >(function ChartPreviewHandle(
-  { x, y, width, height, onResize, orientation, placement, rx = 4, ry = 4 },
+  { className, onMove, orientation, placement, rx = 4, ry = 4, ...rest },
   forwardedRef,
 ) {
-  const classes = useUtilityClasses({ onResize, orientation, placement });
+  const classes = useUtilityClasses({ onMove, orientation, placement });
 
   const handleRef = React.useRef<SVGRectElement>(null);
   const ref = useForkRef(handleRef, forwardedRef);
 
-  const onResizeEvent = useEventCallback(onResize);
+  const onMoveEvent = useEventCallback(onMove);
 
   React.useEffect(() => {
     const handle = handleRef.current;
@@ -65,7 +66,7 @@ export const ChartAxisZoomSliderHandle = React.forwardRef<
     }
 
     const onPointerMove = rafThrottle((event: PointerEvent) => {
-      onResizeEvent(event);
+      onMoveEvent(event);
     });
 
     const onPointerUp = () => {
@@ -89,18 +90,7 @@ export const ChartAxisZoomSliderHandle = React.forwardRef<
       handle.removeEventListener('pointerdown', onPointerDown);
       onPointerMove.clear();
     };
-  }, [onResizeEvent, orientation]);
+  }, [onMoveEvent, orientation]);
 
-  return (
-    <Rect
-      className={classes.root}
-      ref={ref}
-      x={x}
-      y={y}
-      width={width}
-      height={height}
-      rx={rx}
-      ry={ry}
-    />
-  );
+  return <Rect className={clsx(classes.root, className)} ref={ref} rx={rx} ry={ry} {...rest} />;
 });
diff --git a/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartsTooltipZoomSliderValue.tsx b/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartsTooltipZoomSliderValue.tsx
new file mode 100644
index 0000000000000..0858353a8e1c7
--- /dev/null
+++ b/packages/x-charts-pro/src/ChartZoomSlider/internals/ChartsTooltipZoomSliderValue.tsx
@@ -0,0 +1,54 @@
+import * as React from 'react';
+import NoSsr from '@mui/material/NoSsr';
+import Popper, { PopperProps } from '@mui/material/Popper';
+import { ChartsTooltipPaper } from '@mui/x-charts/ChartsTooltip';
+import Typography from '@mui/material/Typography';
+import { styled } from '@mui/material/styles';
+
+/**
+ * The root component of the zoom slider tooltip.
+ * @ignore - internal component.
+ */
+const ChartsZoomSliderTooltipRoot = styled(Popper, {
+  name: 'MuiChartsZoomSliderTooltip',
+  slot: 'Root',
+})(({ theme }) => ({
+  pointerEvents: 'none',
+  zIndex: theme.zIndex.modal,
+}));
+
+const MODIFIERS = [
+  {
+    name: 'offset',
+    options: { offset: [0, 4] },
+  },
+];
+
+interface ChartsTooltipZoomSliderValueProps
+  extends Pick<PopperProps, 'anchorEl' | 'open' | 'modifiers' | 'placement'>,
+    React.PropsWithChildren {}
+
+export function ChartsTooltipZoomSliderValue({
+  anchorEl,
+  open,
+  placement,
+  modifiers = MODIFIERS,
+  children,
+}: ChartsTooltipZoomSliderValueProps) {
+  return (
+    <NoSsr>
+      {open ? (
+        <ChartsZoomSliderTooltipRoot
+          open={open}
+          anchorEl={anchorEl}
+          placement={placement}
+          modifiers={modifiers}
+        >
+          <ChartsTooltipPaper sx={{ paddingX: 0.5 }}>
+            <Typography variant="caption">{children}</Typography>
+          </ChartsTooltipPaper>
+        </ChartsZoomSliderTooltipRoot>
+      ) : null}
+    </NoSsr>
+  );
+}


Reviews:
user: login: alexfauquette
body: About the DX how could we let user modify this behavior.

Could it be something like `showTooltip?: 'never' |'always' | 'isMoving'| 'someIsMoving'`

- isMoving: show start if start is moving, but not the end tooltip
- someIsMoving: should start tootlip if either start or end is moving

I assume such things will be easier by storing in state `isStartHandleMoving` as a boolean (same for the end) instead of `showTooltip: null | 'start' | 'end'| 'both'`
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T11:51:47Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T20:44:09Z
user: login: JCQuintas
body: Left a comment, otherwise looks good
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T07:12:57Z
user: login: bernardobelchior
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T08:02:50Z

Review comments:
diff_hunk: @@ -0,0 +1,14 @@
+import { styled } from '@mui/material/styles';
+import Popper from '@mui/material/Popper';
+
+/**
+ * The root component of the tooltip.
+ * @ignore - internal component.
+ */
+export const ChartsTooltipRoot = styled(Popper, {
+  name: 'MuiChartsTooltip',
+  slot: 'Root',
+})(({ theme }) => ({
+  pointerEvents: 'none',
+  zIndex: theme.zIndex.modal,
+}));
path: packages/x-charts/src/ChartsTooltip/ChartsTooltipRoot.tsx
user: login: alexfauquette
body: I think duplicating this component is needed. 

Because this code allows to use the MUI theme with `styleOverrides`.

For now the `MuiChartsTooltip` is identified as the main tooltip. By sharing this component the following customization will impact both the main tooltip and the sliders

```js
MuiChartsTooltip: {
  styleOverrides: {
    root: { backgroundColor: 'red' }
  }
}
```

The issue is that `MuiChartsTooltip` naming that is also used in `MuiChartsTooltip.table`, `MuiChartsTooltip.row`, ...

I'm in favor of duplicating to keep `MuiChartsTooltip` as a reference to the main tooltip. And in v8 have a more precise naming to distinguish the tooltip from its content
created_at: 2025-05-13T11:35:36Z
author_association: MEMBER
diff_hunk: @@ -387,27 +405,44 @@ function ChartAxisZoomSliderSpan({
         width={previewWidth}
         height={previewHeight}
       />
-      {
-        // TODO: In RTL languages, should we start from the right?
-      }
       <ChartAxisZoomSliderHandle
+        ref={setStartHandleEl}
         x={startHandleX}
         y={startHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeStart}
+        onMove={onResizeStart}
path: packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx
user: login: alexfauquette
body: I just noticed the `onResize[ Start | End ]` is confusing because I can be:
- Modify the start/end position (the correct one)
- start/end modifying positions (like `onDraggStart`)

```suggestion
        onMove={onMoveStartHandle}
```
created_at: 2025-05-13T11:41:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,14 @@
+import { styled } from '@mui/material/styles';
+import Popper from '@mui/material/Popper';
+
+/**
+ * The root component of the tooltip.
+ * @ignore - internal component.
+ */
+export const ChartsTooltipRoot = styled(Popper, {
+  name: 'MuiChartsTooltip',
+  slot: 'Root',
+})(({ theme }) => ({
+  pointerEvents: 'none',
+  zIndex: theme.zIndex.modal,
+}));
path: packages/x-charts/src/ChartsTooltip/ChartsTooltipRoot.tsx
user: login: bernardobelchior
body: Makes sense 👍 
created_at: 2025-05-14T20:44:08Z
author_association: MEMBER
diff_hunk: @@ -386,28 +404,47 @@ function ChartAxisZoomSliderSpan({
         y={previewY + (axisDirection === 'x' ? previewOffset : 0)}
         width={previewWidth}
         height={previewHeight}
+        onPointerEnter={() => setShowTooltip('both')}
+        onPointerLeave={() => setShowTooltip(null)}
       />
-      {
-        // TODO: In RTL languages, should we start from the right?
-      }
       <ChartAxisZoomSliderHandle
+        ref={setStartHandleEl}
         x={startHandleX}
         y={startHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeStart}
+        onMove={onStartHandleMove}
+        onPointerEnter={() => setShowTooltip('start')}
+        onPointerLeave={() => setShowTooltip(null)}
         placement="start"
       />
       <ChartAxisZoomSliderHandle
+        ref={setEndHandleEl}
         x={endHandleX}
         y={endHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeEnd}
+        onMove={onEndHandleMove}
+        onPointerEnter={() => setShowTooltip('end')}
+        onPointerLeave={() => setShowTooltip(null)}
         placement="end"
       />
+      <ChartsTooltipZoomSliderValue
+        anchorEl={startHandleEl}
+        open={showTooltip === 'start' || showTooltip === 'both'}
path: packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx
user: login: JCQuintas
body: Should we move to have when a tooltip appears being configurable? 
created_at: 2025-05-20T07:12:44Z
author_association: MEMBER
diff_hunk: @@ -386,28 +404,47 @@ function ChartAxisZoomSliderSpan({
         y={previewY + (axisDirection === 'x' ? previewOffset : 0)}
         width={previewWidth}
         height={previewHeight}
+        onPointerEnter={() => setShowTooltip('both')}
+        onPointerLeave={() => setShowTooltip(null)}
       />
-      {
-        // TODO: In RTL languages, should we start from the right?
-      }
       <ChartAxisZoomSliderHandle
+        ref={setStartHandleEl}
         x={startHandleX}
         y={startHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeStart}
+        onMove={onStartHandleMove}
+        onPointerEnter={() => setShowTooltip('start')}
+        onPointerLeave={() => setShowTooltip(null)}
         placement="start"
       />
       <ChartAxisZoomSliderHandle
+        ref={setEndHandleEl}
         x={endHandleX}
         y={endHandleY}
         width={previewHandleWidth}
         height={previewHandleHeight}
         orientation={axisDirection === 'x' ? 'horizontal' : 'vertical'}
-        onResize={onResizeEnd}
+        onMove={onEndHandleMove}
+        onPointerEnter={() => setShowTooltip('end')}
+        onPointerLeave={() => setShowTooltip(null)}
         placement="end"
       />
+      <ChartsTooltipZoomSliderValue
+        anchorEl={startHandleEl}
+        open={showTooltip === 'start' || showTooltip === 'both'}
path: packages/x-charts-pro/src/ChartZoomSlider/internals/ChartAxisZoomSlider.tsx
user: login: bernardobelchior
body: Yes! [Alex had already suggested it](https://github.com/mui/mui-x/pull/17733#issuecomment-2881524756). I'm doing that as a follow-up.
created_at: 2025-05-20T08:02:49Z
author_association: MEMBER

PR comments:
user: login: mui-bot
created_at: 2025-05-07T07:50:12Z
body: 
<!--
  0 failure: 
  0 warning: 
  
  1 markdown notices
  DangerID: danger-id-Danger;
-->



**Deploy preview:** <a href="https://deploy-preview-17733--material-ui-x.netlify.app/">https://deploy-preview-17733--material-ui-x.netlify.app/</a>
<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against 1452791146d73333a3ee94be8789c3855c89bc51
</p>

author_association: NONE
user: login: codspeed-hq[bot]
created_at: 2025-05-07T07:54:31Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/mui/mui-x/branches/bernardobelchior%3Aadd-zoom-slider-tooltip)

### Merging #17733 will **not alter performance**

<sub>Comparing <code>bernardobelchior:add-zoom-slider-tooltip</code> (1452791) with <code>master</code> (e8b415c)</sub>



### Summary

`✅ 9` untouched benchmarks  




author_association: NONE
user: login: bernardobelchior
created_at: 2025-05-07T08:54:38Z
body: @noraleonte should we show both tooltips when hovering over the slider track? Currently, we only show the tooltip when hovering over the handle.
author_association: MEMBER
user: login: TheOneTheOnlyJJ
created_at: 2025-05-08T07:32:34Z
body: > @noraleonte should we show both tooltips when hovering over the slider track? Currently, we only show the tooltip when hovering over the handle.

Could this be controlled through a dedicated prop? It may be desirable to use either one.
author_association: NONE
user: login: bernardobelchior
created_at: 2025-05-08T07:55:11Z
body: > > @noraleonte should we show both tooltips when hovering over the slider track? Currently, we only show the tooltip when hovering over the handle.
> 
> Could this be controlled through a dedicated prop? It may be desirable to use either one.

We could consider supporting that. Could you describe your use case?
author_association: MEMBER
user: login: TheOneTheOnlyJJ
created_at: 2025-05-08T14:06:11Z
body: > We could consider supporting that. Could you describe your use case?

I have not explored any particular use case, but granular control is always a plus if it does not cost too many resources.
I could see this being useful if the range ends could be "fixed" (disabled).


author_association: NONE
user: login: bernardobelchior
created_at: 2025-05-14T20:45:45Z
body: > About the DX how could we let user modify this behavior.
> 
> Could it be something like `showTooltip?: 'never' |'always' | 'isMoving'| 'someIsMoving'`
> 
> * isMoving: show start if start is moving, but not the end tooltip
> * someIsMoving: should start tootlip if either start or end is moving
> 
> I assume such things will be easier by storing in state `isStartHandleMoving` as a boolean (same for the end) instead of `showTooltip: null | 'start' | 'end'| 'both'`

Yeah, customization makes sense. 

I'll tackle it in a follow-up PR.

----


@noraleonte I find it weird that hovering over the handle shows the tooltip, but hovering over the track doesn't. Would it make sense to show both tooltips in that case as well? 


author_association: MEMBER
user: login: noraleonte
created_at: 2025-05-19T10:12:31Z
body: > I find it weird that hovering over the handle shows the tooltip, but hovering over the track doesn't. Would it make sense to show both tooltips in that case as well?

@bernardobelchior Yes, I think that makes sense. 👌 
author_association: CONTRIBUTOR
user: login: bernardobelchior
created_at: 2025-05-28T08:29:37Z
body: > About the DX how could we let user modify this behavior.
> 
> Could it be something like `showTooltip?: 'never' |'always' | 'isMoving'| 'someIsMoving'`
> 
> * isMoving: show start if start is moving, but not the end tooltip
> * someIsMoving: should start tootlip if either start or end is moving
> 
> I assume such things will be easier by storing in state `isStartHandleMoving` as a boolean (same for the end) instead of `showTooltip: null | 'start' | 'end'| 'both'`

[Here it is](https://github.com/mui/mui-x/pull/18030), @alexfauquette 😄 
author_association: MEMBER