diff --git a/packages/mui-material/src/Tooltip/Tooltip.js b/packages/mui-material/src/Tooltip/Tooltip.js index 475e05372e3bf2..890945aa5215bd 100644 --- a/packages/mui-material/src/Tooltip/Tooltip.js +++ b/packages/mui-material/src/Tooltip/Tooltip.js @@ -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); } }; diff --git a/packages/mui-material/test/integration/Tooltip.test.js b/packages/mui-material/test/integration/Tooltip.test.js new file mode 100644 index 00000000000000..d443b66327a6ae --- /dev/null +++ b/packages/mui-material/test/integration/Tooltip.test.js @@ -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(' integration', () => { + const { render } = createRenderer(); + + it.skipIf(isJsdom())( + 'does not throw error and closes Tooltip when Input becomes disabled while focused', + () => { + function TestCase({ disabled }) { + return ( + + + + ); + } + + const { setProps } = render(); + + 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(); + }, + ); +});