From ba2ff4341f342d8dabcdb4b219f778925820fd3d Mon Sep 17 00:00:00 2001 From: Huangshuo Kuang <141250392+kkkhs@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:36:59 +0000 Subject: [PATCH] [material-ui][Select] Prevent opening mouseup auto-selection --- .../mui-material/src/Select/Select.test.js | 30 +++++++++++++++++++ .../mui-material/src/Select/SelectInput.js | 14 +++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/mui-material/src/Select/Select.test.js b/packages/mui-material/src/Select/Select.test.js index f41a83a764943d..c8cc5a36e59f8a 100644 --- a/packages/mui-material/src/Select/Select.test.js +++ b/packages/mui-material/src/Select/Select.test.js @@ -260,6 +260,7 @@ describe('', () => { expect(screen.queryByRole('listbox', { hidden: false })).to.equal(null); }); + it('does not select an option when the opening mouseup lands on it after the drag delay without moving', async () => { + const onChange = spy(); + const { user } = render( + , + ); + + const trigger = screen.getByRole('combobox'); + await user.pointer({ keys: '[MouseLeft>]', target: trigger }); + + await act(async () => { + await sleep(450); + }); + + await user.pointer({ + keys: '[/MouseLeft]', + target: screen.getByRole('option', { name: 'Twenty' }), + }); + + expect(trigger).to.have.text('Ten'); + expect(onChange.callCount).to.equal(0); + expect(screen.queryByRole('listbox', { hidden: false })).not.to.equal(null); + }); + it('selects an option when dragging from the trigger and releasing after the drag delay', async () => { const onChange = spy(); const { user } = render( @@ -286,6 +314,7 @@ describe('', () => { await act(async () => { await sleep(250); }); + await user.pointer({ target: screen.getByRole('option', { name: 'Twenty' }) }); await user.pointer({ keys: '[/MouseLeft]', target: screen.getByRole('option', { name: 'Twenty' }), diff --git a/packages/mui-material/src/Select/SelectInput.js b/packages/mui-material/src/Select/SelectInput.js index 7c6c82f938dd93..2f2c2c7b22ea59 100644 --- a/packages/mui-material/src/Select/SelectInput.js +++ b/packages/mui-material/src/Select/SelectInput.js @@ -193,6 +193,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { const hasSelectedItemInListRef = React.useRef(false); const openingMouseUpListenerCleanupRef = React.useRef(null); const didPointerDownOnItemRef = React.useRef(false); + const didPointerMoveToItemRef = React.useRef(false); const selectionRef = React.useRef({ allowSelectedMouseUp: false, allowUnselectedMouseUp: false, @@ -257,6 +258,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { const resetMouseUpSelection = React.useCallback(() => { clearSelectionTimers(); didPointerDownOnItemRef.current = false; + didPointerMoveToItemRef.current = false; selectionRef.current = { allowSelectedMouseUp: false, allowUnselectedMouseUp: false, @@ -527,6 +529,10 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { return; } + if (!didPointerMoveToItemRef.current) { + return; + } + const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected; const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected; @@ -720,6 +726,14 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) { didPointerDownOnItemRef.current = true; child.props.onPointerDown?.(event); }, + onMouseMove: (event) => { + didPointerMoveToItemRef.current = true; + child.props.onMouseMove?.(event); + }, + onPointerMove: (event) => { + didPointerMoveToItemRef.current = true; + child.props.onPointerMove?.(event); + }, onClick: handleItemClick(child), onMouseUp: handleItemMouseUp(child, selected), onKeyUp: (event) => {