diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js index ffbc0c53770756..d2245f932d1d5b 100644 --- a/packages/mui-material/src/Select/Select.test.js +++ b/packages/mui-material/src/Select/Select.test.js @@ -34,6 +34,75 @@ describe(' + none + Ten + , + ); + const trigger = screen.getByRole('combobox'); + + // Open the menu + fireEvent.mouseDown(trigger); + expect(screen.getByRole('listbox')).not.to.equal(null); + + // Simulate mouse up outside the menu. The mouseup target is the backdrop when the menu is opened. + fireEvent.mouseUp(screen.getByTestId('backdrop'), { clientX: 60, clientY: 10 }); + + // Menu should be closed now + expect(screen.queryByRole('listbox', { hidden: false })).to.equal(null); + }); + + it('should not close the menu when mouse is inside the trigger', () => { + render( + , + ); + const trigger = screen.getByRole('combobox'); + + // Open the menu + fireEvent.mouseDown(trigger); + expect(screen.getByRole('listbox')).not.to.equal(null); + + // Simulate mouse up inside the trigger + fireEvent.mouseUp(trigger, { clientX: 20, clientY: 20 }); + + // Menu should still be open + expect(screen.queryByRole('listbox', { hidden: false })).not.to.equal(null); + }); + + it('should not close the menu when releasing on a menu item', () => { + render( + , + ); + const trigger = screen.getByRole('combobox'); + + // Open the menu + fireEvent.mouseDown(trigger); + const options = screen.getAllByRole('option'); + + // Simulate mouse up on a menu item + fireEvent.mouseUp(options[0]); + + // Menu should still be open + expect(screen.getByRole('listbox')).not.to.equal(null); + }); + }); + describe('prop: inputProps', () => { it('should be able to provide a custom classes property', () => { render( diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js index 0b272bb98b1131..641bb5fb5ddb82 100644 --- a/packages/mui-material/src/Select/SelectInput.js +++ b/packages/mui-material/src/Select/SelectInput.js @@ -146,6 +146,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { const inputRef = React.useRef(null); const displayRef = React.useRef(null); + const paperRef = React.useRef(null); const [displayNode, setDisplayNode] = React.useState(null); const { current: isOpenControlled } = React.useRef(openProp != null); const [menuMinWidthState, setMenuMinWidthState] = React.useState(); @@ -233,6 +234,36 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { event.preventDefault(); displayRef.current.focus(); + const doc = ownerDocument(event.currentTarget); + + function handleMouseUp(mouseEvent) { + if (!displayRef.current) { + return; + } + + // mouse is over the options/menuitem, don't close the menu + if (paperRef.current.contains(mouseEvent.target)) { + return; + } + + const triggerElement = displayRef.current.getBoundingClientRect(); + + // mouse is inside the trigger, don't close the menu + if ( + mouseEvent.clientX >= triggerElement.left && + mouseEvent.clientX <= triggerElement.right && + mouseEvent.clientY >= triggerElement.top && + mouseEvent.clientY <= triggerElement.bottom + ) { + return; + } + + // close the menu + update(false, mouseEvent); + } + + doc.addEventListener('mouseup', handleMouseUp, { once: true }); + update(true, event); }; @@ -571,6 +602,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { ...listProps, }, paper: { + ref: paperRef, ...paperProps, style: { minWidth: menuMinWidth,