diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js
index cede32e7240910..f0ffb0c969f66a 100644
--- a/packages/mui-material/src/Tooltip/Tooltip.js
+++ b/packages/mui-material/src/Tooltip/Tooltip.js
@@ -357,6 +357,9 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {
);
const handleMouseOver = (event) => {
+ if (childNode?.disabled) {
+ return;
+ }
if (ignoreNonTouchEvents.current && event.type !== 'touchstart') {
return;
}
diff --git a/packages/mui-material/src/Tooltip/Tooltip.test.js b/packages/mui-material/src/Tooltip/Tooltip.test.js
index 213c2906a67982..9f4bbd3e9f2372 100644
--- a/packages/mui-material/src/Tooltip/Tooltip.test.js
+++ b/packages/mui-material/src/Tooltip/Tooltip.test.js
@@ -1132,6 +1132,53 @@ describe('', () => {
expect(handleClose.callCount).to.equal(1);
});
+ it('stays closed when a stray mouseover lands while the disabled child is closing', async () => {
+ // Deterministic regression test for the flaky "stuck open" tooltip:
+ // when the focused child becomes disabled the close is scheduled via the React
+ // #9142 native-blur workaround, but a layout-shift `mouseover` on the interactive
+ // popper used to cancel that pending close and reopen the tooltip. A disabled
+ // anchor must never (re)open. `leaveDelay` opens a deterministic window in which to
+ // dispatch the stray `mouseover` before the close fires.
+ clock.restore();
+ const handleClose = spy();
+
+ function TestCase() {
+ const [disabled, setDisabled] = React.useState(false);
+ return (
+
+
+
+ );
+ }
+
+ const { user } = render();
+
+ await user.tab();
+ await waitFor(() => {
+ expect(screen.getByRole('tooltip')).toBeVisible();
+ });
+
+ // Disabling the focused child schedules the close (leaveDelay window still pending).
+ await user.keyboard('{Enter}');
+
+ // A stray `mouseover` reaches the interactive popper before the close fires.
+ fireEvent.mouseOver(screen.getByRole('tooltip'));
+
+ // The disabled anchor must still close (and not reopen).
+ await waitFor(() => {
+ expect(screen.queryByRole('tooltip')).to.equal(null);
+ });
+ expect(handleClose.callCount).to.equal(1);
+ });
+
it('closes on blur', async () => {
const eventLog = [];
const transitionTimeout = 0;