Skip to content
Merged
17 changes: 15 additions & 2 deletions packages/mui-material/src/Tooltip/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,22 @@ const Tooltip = React.forwardRef(function Tooltip(inProps, ref) {

const [, setChildIsFocusVisible] = React.useState(false);
const handleBlur = (event) => {
if (!isFocusVisible(event.target)) {
// Needed for https://github.com/mui/material-ui/issues/45373
const target = event?.target ?? childNode;
if (!target || !isFocusVisible(target)) {
setChildIsFocusVisible(false);
handleMouseLeave(event);

// InputBase can call onBlur() without an event when the input becomes disabled.
// Tooltip must not assume an event object exists.
const closeEvent = event ?? new Event('blur');

// `new Event('blur')` has `target/currentTarget === null`, but Tooltip's close logic
// (and user callbacks like onClose) may expect them to reference the anchor element.
if (!event && target) {
Object.defineProperty(closeEvent, 'target', { value: target });
Object.defineProperty(closeEvent, 'currentTarget', { value: target });
}
handleMouseLeave(closeEvent);
}
};

Expand Down
50 changes: 50 additions & 0 deletions packages/mui-material/test/integration/Tooltip.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from 'chai';
import { createRenderer, screen, isJsdom, act, fireEvent } from '@mui/internal-test-utils';
import Tooltip from '@mui/material/Tooltip';
import Input from '@mui/material/Input';

function focusVisibleSync(element) {
act(() => {
element.blur();
});
fireEvent.keyDown(document.body, { key: 'Tab' });
act(() => {
element.focus();
});
}

describe('<Tooltip> integration', () => {
const { render } = createRenderer();

it.skipIf(isJsdom())(
'does not throw error and closes Tooltip when Input becomes disabled while focused',
() => {
function TestCase({ disabled }) {
return (
<Tooltip
title="Test"
enterDelay={0}
leaveDelay={0}
slotProps={{ transition: { timeout: 0 } }}
>
<Input disabled={disabled} placeholder="click here and wait" />
</Tooltip>
);
}

const { setProps } = render(<TestCase disabled={false} />);

const input = screen.getByRole('textbox');

focusVisibleSync(input);

expect(screen.getByRole('tooltip')).toBeVisible();

expect(() => {
setProps({ disabled: true });
}).not.to.throw();

expect(screen.getByRole('tooltip')).not.toBeVisible();
},
);
});
Loading