diff --git a/packages/react/src/select/trigger/SelectTrigger.test.tsx b/packages/react/src/select/trigger/SelectTrigger.test.tsx index 5dd8283a515..cd58c74953b 100644 --- a/packages/react/src/select/trigger/SelectTrigger.test.tsx +++ b/packages/react/src/select/trigger/SelectTrigger.test.tsx @@ -102,13 +102,19 @@ describe('', () => { }); describe('releasing the opening press', () => { - async function pressTrigger(container?: ShadowRoot) { + async function pressTrigger( + options: { + container?: ShadowRoot; + onOpenChange?: Select.Root.Props['onOpenChange']; + release?: boolean; + } = {}, + ) { await render( - + - + a @@ -124,15 +130,33 @@ describe('', () => { fireEvent.pointerDown(trigger, { pointerType: 'mouse', button: 0 }); fireEvent.mouseDown(trigger, { button: 0 }); + if (options.release) { + fireEvent.mouseUp(trigger, { button: 0, clientX: 150, clientY: 120 }); + } await waitFor(() => { expect(trigger).toHaveAttribute('aria-expanded', 'true'); }); - // The press is held long enough for the trigger to start watching its release. + return trigger; + } + + it('ignores a later release when the opening press was released immediately', async () => { + const onOpenChange = vi.fn(); + const trigger = await pressTrigger({ onOpenChange, release: true }); await act(async () => { await wait(20); }); - return trigger; - } + onOpenChange.mockClear(); + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + + // e.g. a press that started inside the popup and was dragged off it + fireEvent.mouseUp(document.body, { button: 0, clientX: 10, clientY: 10 }); + await act(async () => { + await wait(20); + }); + + expect(trigger).toHaveAttribute('aria-expanded', 'true'); + expect(onOpenChange).not.toHaveBeenCalled(); + }); it('keeps the popup open when the press slips up to 5px off the trigger', async () => { const trigger = await pressTrigger(); @@ -161,7 +185,7 @@ describe('', () => { const shadowRoot = host.attachShadow({ mode: 'open' }); try { - const trigger = await pressTrigger(shadowRoot); + const trigger = await pressTrigger({ container: shadowRoot }); const popup = shadowRoot.querySelector('[role="listbox"]'); expect(popup).not.toBe(null); diff --git a/packages/react/src/select/trigger/SelectTrigger.tsx b/packages/react/src/select/trigger/SelectTrigger.tsx index 7a290c95eaf..59e9d7f8280 100644 --- a/packages/react/src/select/trigger/SelectTrigger.tsx +++ b/packages/react/src/select/trigger/SelectTrigger.tsx @@ -99,7 +99,6 @@ export const SelectTrigger = React.forwardRef(function SelectTrigger( const setTriggerElement = store.useStateSetter('triggerElement'); const timeoutFocus = useTimeout(); - const timeoutMouseDown = useTimeout(); const selectedDelayTimeout = useTimeout(); React.useEffect(() => { @@ -124,10 +123,8 @@ export const SelectTrigger = React.forwardRef(function SelectTrigger( dragY: 0, }; - timeoutMouseDown.clear(); - return undefined; - }, [open, store, timeoutMouseDown, selectedDelayTimeout]); + }, [open, store, selectedDelayTimeout]); const mergedProps: HTMLProps = mergeProps<'button'>( triggerProps, @@ -201,10 +198,7 @@ export const SelectTrigger = React.forwardRef(function SelectTrigger( store.setOpen(false, createChangeEventDetails(REASONS.cancelOpen, mouseEvent)); } - // Firefox can fire this upon mousedown - timeoutMouseDown.start(0, () => { - doc.addEventListener('mouseup', handleMouseUp, { once: true }); - }); + doc.addEventListener('mouseup', handleMouseUp, { once: true }); }, }, elementProps,