diff --git a/packages/@react-aria/calendar/src/useCalendarCell.ts b/packages/@react-aria/calendar/src/useCalendarCell.ts index aabeac2f9a5..9db8185c699 100644 --- a/packages/@react-aria/calendar/src/useCalendarCell.ts +++ b/packages/@react-aria/calendar/src/useCalendarCell.ts @@ -338,7 +338,13 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta // outside the original pressed element. // (JSDOM does not support this) if ('releasePointerCapture' in e.target) { - e.target.releasePointerCapture(e.pointerId); + if ('hasPointerCapture' in e.target) { + if (e.target.hasPointerCapture(e.pointerId)) { + e.target.releasePointerCapture(e.pointerId); + } + } else { + e.target.releasePointerCapture(e.pointerId); + } } }, onContextMenu(e) { diff --git a/packages/@react-aria/interactions/src/usePress.ts b/packages/@react-aria/interactions/src/usePress.ts index 6dc4fd7f757..b9caaec7f6b 100644 --- a/packages/@react-aria/interactions/src/usePress.ts +++ b/packages/@react-aria/interactions/src/usePress.ts @@ -596,7 +596,13 @@ export function usePress(props: PressHookProps): PressResult { // This enables onPointerLeave and onPointerEnter to fire. let target = getEventTarget(e.nativeEvent); if ('releasePointerCapture' in target) { - target.releasePointerCapture(e.pointerId); + if ('hasPointerCapture' in target) { + if (target.hasPointerCapture(e.pointerId)) { + target.releasePointerCapture(e.pointerId); + } + } else { + (target as Element).releasePointerCapture(e.pointerId); + } } } diff --git a/packages/@react-aria/interactions/test/usePress.test.js b/packages/@react-aria/interactions/test/usePress.test.js index 92a07a57c0e..4d4af84ba29 100644 --- a/packages/@react-aria/interactions/test/usePress.test.js +++ b/packages/@react-aria/interactions/test/usePress.test.js @@ -420,8 +420,10 @@ describe('usePress', function () { let el = res.getByText('test'); el.releasePointerCapture = jest.fn(); + el.hasPointerCapture = jest.fn().mockReturnValue(true); fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, pointerType: 'mouse', clientX: 0, clientY: 0})); - expect(el.releasePointerCapture).toHaveBeenCalled(); + expect(el.hasPointerCapture).toHaveBeenCalledWith(1); + expect(el.releasePointerCapture).toHaveBeenCalledWith(1); // react listens for pointerout and pointerover instead of pointerleave and pointerenter... fireEvent(el, pointerEvent('pointerout', {pointerId: 1, pointerType: 'mouse', clientX: 100, clientY: 100})); fireEvent(document, pointerEvent('pointerup', {pointerId: 1, pointerType: 'mouse', clientX: 100, clientY: 100})); @@ -560,6 +562,16 @@ describe('usePress', function () { ]); }); + it('should not call releasePointerCapture when hasPointerCapture returns false', function () { + let res = render(); + let el = res.getByText('test'); + el.releasePointerCapture = jest.fn(); + el.hasPointerCapture = jest.fn().mockReturnValue(false); + fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, pointerType: 'mouse', clientX: 0, clientY: 0})); + expect(el.hasPointerCapture).toHaveBeenCalledWith(1); + expect(el.releasePointerCapture).not.toHaveBeenCalled(); + }); + it('should handle pointer cancel events', function () { let events = []; let addEvent = (e) => events.push(e); @@ -4011,8 +4023,10 @@ describe('usePress', function () { const el = shadowRoot.getElementById('testElement'); el.releasePointerCapture = jest.fn(); + el.hasPointerCapture = jest.fn().mockReturnValue(true); fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, pointerType: 'mouse', clientX: 0, clientY: 0})); - expect(el.releasePointerCapture).toHaveBeenCalled(); + expect(el.hasPointerCapture).toHaveBeenCalledWith(1); + expect(el.releasePointerCapture).toHaveBeenCalledWith(1); // react listens for pointerout and pointerover instead of pointerleave and pointerenter... fireEvent(el, pointerEvent('pointerout', {pointerId: 1, pointerType: 'mouse', clientX: 100, clientY: 100})); fireEvent(document, pointerEvent('pointerup', {pointerId: 1, pointerType: 'mouse', clientX: 100, clientY: 100})); diff --git a/packages/@react-aria/overlays/src/ariaHideOutside.ts b/packages/@react-aria/overlays/src/ariaHideOutside.ts index 753c2a926a3..47217ab4f41 100644 --- a/packages/@react-aria/overlays/src/ariaHideOutside.ts +++ b/packages/@react-aria/overlays/src/ariaHideOutside.ts @@ -147,7 +147,12 @@ export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOpt // If the parent element of the added nodes is not within one of the targets, // and not already inside a hidden node, hide all of the new children. - if (![...visibleNodes, ...hiddenNodes].some(node => node.contains(change.target))) { + if ( + change.target.isConnected && + ![...visibleNodes, ...hiddenNodes].some((node) => + node.contains(change.target) + ) + ) { for (let node of change.addedNodes) { if ( (node instanceof HTMLElement || node instanceof SVGElement) && diff --git a/packages/@react-aria/overlays/test/ariaHideOutside.test.js b/packages/@react-aria/overlays/test/ariaHideOutside.test.js index 0eddc67e0ac..36f921002b5 100644 --- a/packages/@react-aria/overlays/test/ariaHideOutside.test.js +++ b/packages/@react-aria/overlays/test/ariaHideOutside.test.js @@ -12,7 +12,7 @@ import {act, render, waitFor} from '@react-spectrum/test-utils-internal'; import {ariaHideOutside} from '../src'; -import React, {useState} from 'react'; +import React, {useRef, useState} from 'react'; describe('ariaHideOutside', function () { it('should hide everything except the provided element [button]', function () { @@ -275,6 +275,76 @@ describe('ariaHideOutside', function () { expect(() => getByTestId('test')).not.toThrow(); }); + it('should handle when a new element is added and then reparented', async function () { + + let Test = () => { + const ref = useRef(null); + const mutate = () => { + let parent = document.createElement('ul'); + let child = document.createElement('li'); + ref.current.append(parent); + parent.appendChild(child); + parent.remove(); // this results in a mutation record for a disconnected ul with a connected li (through the new ul parent) in `addedNodes` + let newParent = document.createElement('ul'); + newParent.appendChild(child); + ref.current.append(newParent); + }; + + return ( + <> +
+ +
+ + ); + }; + + let {queryByRole, getAllByRole, getByTestId} = render(); + + ariaHideOutside([getByTestId('test')]); + + queryByRole('button').click(); + await Promise.resolve(); // Wait for mutation observer tick + + expect(getAllByRole('listitem')).toHaveLength(1); + }); + + it('should handle when a new element is added and then reparented to a hidden container', async function () { + + let Test = () => { + const ref = useRef(null); + const mutate = () => { + let parent = document.createElement('ul'); + let child = document.createElement('li'); + ref.current.append(parent); + parent.appendChild(child); + parent.remove(); // this results in a mutation record for a disconnected ul with a connected li (through the new ul parent) in `addedNodes` + let newParent = document.createElement('ul'); + newParent.appendChild(child); + ref.current.append(newParent); + }; + + return ( + <> +
+ +
+
+ + ); + }; + + let {queryByRole, queryAllByRole, getByTestId} = render(); + + ariaHideOutside([getByTestId('test')]); + + queryByRole('button').click(); + await Promise.resolve(); // Wait for mutation observer tick + + expect(queryAllByRole('listitem')).toHaveLength(0); + }); + + it('work when called multiple times', function () { let {getByRole, getAllByRole} = render( <> diff --git a/packages/@react-aria/selection/src/useSelectableCollection.ts b/packages/@react-aria/selection/src/useSelectableCollection.ts index f44cb0a123d..b747973a80f 100644 --- a/packages/@react-aria/selection/src/useSelectableCollection.ts +++ b/packages/@react-aria/selection/src/useSelectableCollection.ts @@ -118,7 +118,6 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions disallowTypeAhead = false, shouldUseVirtualFocus, allowsTabNavigation = false, - isVirtualized, // If no scrollRef is provided, assume the collection ref is the scrollable region scrollRef = ref, linkBehavior = 'action' @@ -328,7 +327,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions // Store the scroll position so we can restore it later. /// TODO: should this happen all the time?? let scrollPos = useRef({top: 0, left: 0}); - useEvent(scrollRef, 'scroll', isVirtualized ? undefined : () => { + useEvent(scrollRef, 'scroll', () => { scrollPos.current = { top: scrollRef.current?.scrollTop ?? 0, left: scrollRef.current?.scrollLeft ?? 0 @@ -369,7 +368,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions } else { navigateToKey(manager.firstSelectedKey ?? delegate.getFirstKey?.()); } - } else if (!isVirtualized && scrollRef.current) { + } else if (scrollRef.current) { // Restore the scroll position to what it was before. scrollRef.current.scrollTop = scrollPos.current.top; scrollRef.current.scrollLeft = scrollPos.current.left; @@ -581,7 +580,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions // This will be marshalled to either the first or last item depending on where focus came from. let tabIndex: number | undefined = undefined; if (!shouldUseVirtualFocus) { - tabIndex = manager.focusedKey == null ? 0 : -1; + tabIndex = manager.isFocused ? -1 : 0; } let collectionId = useCollectionId(manager.collection); diff --git a/packages/@react-spectrum/s2/src/ComboBox.tsx b/packages/@react-spectrum/s2/src/ComboBox.tsx index 0f336cd54d2..ab2348328b6 100644 --- a/packages/@react-spectrum/s2/src/ComboBox.tsx +++ b/packages/@react-spectrum/s2/src/ComboBox.tsx @@ -79,7 +79,7 @@ export interface ComboboxStyleProps { size?: 'S' | 'M' | 'L' | 'XL' } export interface ComboBoxProps extends - Omit, 'children' | 'style' | 'className' | 'defaultFilter' | 'allowsEmptyCollection' | keyof GlobalDOMAttributes>, + Omit, 'children' | 'style' | 'className' | 'defaultFilter' | 'allowsEmptyCollection' | 'isTriggerUpWhenOpen' | keyof GlobalDOMAttributes>, ComboboxStyleProps, StyleProps, SpectrumLabelableProps, @@ -354,6 +354,7 @@ export const ComboBox = /*#__PURE__*/ (forwardRef as forwardRefType)(function Co return ( pressScale(buttonRef)(renderProps)} className={renderProps => inputButton({ ...renderProps, diff --git a/packages/@react-spectrum/s2/src/DatePicker.tsx b/packages/@react-spectrum/s2/src/DatePicker.tsx index e707c911ffe..b98a99ef1ad 100644 --- a/packages/@react-spectrum/s2/src/DatePicker.tsx +++ b/packages/@react-spectrum/s2/src/DatePicker.tsx @@ -41,7 +41,7 @@ import {useSpectrumContextProps} from './useSpectrumContextProps'; export interface DatePickerProps extends - Omit, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, + Omit, 'children' | 'className' | 'style' | 'isTriggerUpWhenOpen' | keyof GlobalDOMAttributes>, Pick, 'createCalendar' | 'pageBehavior' | 'firstDayOfWeek' | 'isDateUnavailable'>, Pick, StyleProps, @@ -155,6 +155,7 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function ref={ref} isRequired={isRequired} {...dateFieldProps} + isTriggerUpWhenOpen style={UNSAFE_style} className={(UNSAFE_className || '') + style(field(), getAllowedOverrides())({ isInForm: !!formContext, @@ -277,9 +278,6 @@ export function CalendarButton(props: {isOpen: boolean, size: 'S' | 'M' | 'L' | return ( + }], + [InsideSelectValueContext, true] + ]}> + {defaultChildren} + + ); + }} + + + + router.navigate({...href, ...opts}), - useHref: href => router.buildLocation(href).href + navigate: (href, opts) => { + if (typeof href === "string") return; + return router.navigate({ ...href, ...opts }); + }, + useHref: (href) => { + if (typeof href === "string") return href; + return router.buildLocation(href).href; + } }}> {/*- end highlight -*/} {/* Your app here... */} diff --git a/packages/dev/s2-docs/src/routers.mdx b/packages/dev/s2-docs/src/routers.mdx index 2937f937f29..575effd9017 100644 --- a/packages/dev/s2-docs/src/routers.mdx +++ b/packages/dev/s2-docs/src/routers.mdx @@ -1,7 +1,7 @@ import {Counter} from './Step'; Render a `RouterProvider` at the root of your app to enable React Aria links to use your client side router. This accepts two props: - + 1. `navigate` – a function received from your router for performing a client side navigation programmatically. 2. `useHref` (optional) – converts a router-specific href to a native HTML href, e.g. prepending a base path. @@ -58,9 +58,15 @@ export const Route = createRootRoute({ let router = useRouter(); return ( /*- begin highlight -*/ - router.navigate({...href, ...opts})} - useHref={href => router.buildLocation(href).href}> + { + if (typeof href === "string") return; + return router.navigate({ ...href, ...opts }); + }} + useHref={(href) => { + if (typeof href === "string") return href; + return router.buildLocation(href).href; + }}> {/*- end highlight -*/} {/* Your app here... */} diff --git a/packages/react-aria-components/src/ComboBox.tsx b/packages/react-aria-components/src/ComboBox.tsx index 466c696b5fe..5efbdd26945 100644 --- a/packages/react-aria-components/src/ComboBox.tsx +++ b/packages/react-aria-components/src/ComboBox.tsx @@ -77,7 +77,9 @@ export interface ComboBoxProps extends Omit, HTMLDivElement>>(null); @@ -207,7 +209,7 @@ function ComboBoxInner({props, collection, comboBoxRef: ref}: values={[ [ComboBoxStateContext, state], [LabelContext, {...labelProps, ref: labelRef}], - [ButtonContext, {...buttonProps, ref: buttonRef, isPressed: state.isOpen}], + [ButtonContext, {...buttonProps, ref: buttonRef, isPressed: !props.isTriggerUpWhenOpen && state.isOpen}], [InputContext, {...inputProps, ref: inputRef}], [OverlayTriggerStateContext, state], [PopoverContext, { diff --git a/packages/react-aria-components/src/DatePicker.tsx b/packages/react-aria-components/src/DatePicker.tsx index fe02d5e2b17..6fd1af8fabd 100644 --- a/packages/react-aria-components/src/DatePicker.tsx +++ b/packages/react-aria-components/src/DatePicker.tsx @@ -87,14 +87,18 @@ export interface DatePickerProps extends Omit + className?: ClassNameOrFunction, + /** Whether the trigger is up when the overlay is open. */ + isTriggerUpWhenOpen?: boolean } export interface DateRangePickerProps extends Omit, 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, Pick, 'shouldCloseOnSelect'>, RACValidation, RenderProps, SlotProps, GlobalDOMAttributes { /** * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state. * @default 'react-aria-DateRangePicker' */ - className?: ClassNameOrFunction + className?: ClassNameOrFunction, + /** Whether the trigger is up when the overlay is open. */ + isTriggerUpWhenOpen?: boolean } export const DatePickerContext = createContext, HTMLDivElement>>(null); @@ -174,7 +178,7 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function [DatePickerStateContext, state], [GroupContext, {...groupProps, ref: groupRef, isInvalid: state.isInvalid}], [DateFieldContext, fieldProps], - [ButtonContext, {...buttonProps, isPressed: state.isOpen}], + [ButtonContext, {...buttonProps, isPressed: !props.isTriggerUpWhenOpen && state.isOpen}], [LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}], [CalendarContext, calendarProps], [OverlayTriggerStateContext, state], @@ -283,7 +287,7 @@ export const DateRangePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(func values={[ [DateRangePickerStateContext, state], [GroupContext, {...groupProps, ref: groupRef, isInvalid: state.isInvalid}], - [ButtonContext, {...buttonProps, isPressed: state.isOpen}], + [ButtonContext, {...buttonProps, isPressed: !props.isTriggerUpWhenOpen && state.isOpen}], [LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}], [RangeCalendarContext, calendarProps], [OverlayTriggerStateContext, state], diff --git a/packages/react-aria-components/src/Dialog.tsx b/packages/react-aria-components/src/Dialog.tsx index 5fc9a33cbe6..6cd79de255f 100644 --- a/packages/react-aria-components/src/Dialog.tsx +++ b/packages/react-aria-components/src/Dialog.tsx @@ -22,6 +22,8 @@ import React, {createContext, ForwardedRef, forwardRef, JSX, ReactNode, useCallb import {RootMenuTriggerStateContext} from './Menu'; export interface DialogTriggerProps extends OverlayTriggerProps { + /** Whether the trigger is up when the overlay is open. */ + isTriggerUpWhenOpen?: boolean, children: ReactNode } @@ -86,7 +88,7 @@ export function DialogTrigger(props: DialogTriggerProps): JSX.Element { style: {'--trigger-width': buttonWidth} as React.CSSProperties }] ]}> - + {props.children} diff --git a/packages/react-aria-components/src/Menu.tsx b/packages/react-aria-components/src/Menu.tsx index 187c3884374..6aef1a7d20e 100644 --- a/packages/react-aria-components/src/Menu.tsx +++ b/packages/react-aria-components/src/Menu.tsx @@ -61,6 +61,8 @@ export const RootMenuTriggerStateContext = createContext(null); export interface MenuTriggerProps extends BaseMenuTriggerProps { + /** Whether the trigger is up when the overlay is open. */ + isTriggerUpWhenOpen?: boolean, children: ReactNode } @@ -100,7 +102,7 @@ export function MenuTrigger(props: MenuTriggerProps): JSX.Element { 'aria-labelledby': menuProps['aria-labelledby'] }] ]}> - + {props.children} diff --git a/packages/react-aria-components/src/Select.tsx b/packages/react-aria-components/src/Select.tsx index b2efd1e15c9..e28181646fd 100644 --- a/packages/react-aria-components/src/Select.tsx +++ b/packages/react-aria-components/src/Select.tsx @@ -86,7 +86,9 @@ export interface SelectProps, HTMLDivElement>>(null); @@ -201,7 +203,7 @@ function SelectInner({props, selectRef: ref, collection}: Sele [SelectStateContext, state], [SelectValueContext, valueProps], [LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}], - [ButtonContext, {...triggerProps, ref: buttonRef, isPressed: state.isOpen, autoFocus: props.autoFocus}], + [ButtonContext, {...triggerProps, ref: buttonRef, isPressed: !props.isTriggerUpWhenOpen && state.isOpen, autoFocus: props.autoFocus}], [OverlayTriggerStateContext, state], [PopoverContext, { trigger: 'Select', diff --git a/packages/react-aria-components/stories/DatePicker.stories.tsx b/packages/react-aria-components/stories/DatePicker.stories.tsx index ae079959edf..bc9cde5a648 100644 --- a/packages/react-aria-components/stories/DatePicker.stories.tsx +++ b/packages/react-aria-components/stories/DatePicker.stories.tsx @@ -47,6 +47,9 @@ export default { validationBehavior: { control: 'select', options: ['native', 'aria'] + }, + isTriggerUpWhenOpen: { + control: 'boolean' } } } as Meta; diff --git a/packages/react-aria-components/stories/Select.stories.tsx b/packages/react-aria-components/stories/Select.stories.tsx index c71374de62e..b7bf2204137 100644 --- a/packages/react-aria-components/stories/Select.stories.tsx +++ b/packages/react-aria-components/stories/Select.stories.tsx @@ -31,6 +31,9 @@ export default { selectionMode: { control: 'radio', options: ['single', 'multiple'] + }, + isTriggerUpWhenOpen: { + control: 'boolean' } } } as Meta; diff --git a/packages/react-aria-components/test/ComboBox.test.js b/packages/react-aria-components/test/ComboBox.test.js index 585a893f3df..4e581397905 100644 --- a/packages/react-aria-components/test/ComboBox.test.js +++ b/packages/react-aria-components/test/ComboBox.test.js @@ -109,6 +109,15 @@ describe('ComboBox', () => { expect(button).toHaveAttribute('data-pressed'); }); + it('should not apply isPressed state to button when expanded and isTriggerUpWhenOpen is true', async () => { + let {getByRole} = render(); + let button = getByRole('button'); + + expect(button).not.toHaveAttribute('data-pressed'); + await user.click(button); + expect(button).not.toHaveAttribute('data-pressed'); + }); + it('should support filtering sections', async () => { let tree = render( diff --git a/packages/react-aria-components/test/DatePicker.test.js b/packages/react-aria-components/test/DatePicker.test.js index 5298849b1a5..00f1cc3e402 100644 --- a/packages/react-aria-components/test/DatePicker.test.js +++ b/packages/react-aria-components/test/DatePicker.test.js @@ -107,6 +107,15 @@ describe('DatePicker', () => { expect(button).toHaveAttribute('data-pressed'); }); + it('should not apply isPressed state to button when expanded and isTriggerUpWhenOpen is true', async () => { + let {getByRole} = render(); + let button = getByRole('button'); + + expect(button).not.toHaveAttribute('data-pressed'); + await user.click(button); + expect(button).not.toHaveAttribute('data-pressed'); + }); + it('should support data-open state', async () => { let {getByRole} = render(); let datePicker = document.querySelector('.react-aria-DatePicker'); diff --git a/packages/react-aria-components/test/DateRangePicker.test.js b/packages/react-aria-components/test/DateRangePicker.test.js index e6562776338..83d49783ff4 100644 --- a/packages/react-aria-components/test/DateRangePicker.test.js +++ b/packages/react-aria-components/test/DateRangePicker.test.js @@ -128,6 +128,15 @@ describe('DateRangePicker', () => { await user.click(button); expect(button).toHaveAttribute('data-pressed'); }); + + it('should not apply isPressed state to button when expanded and isTriggerUpWhenOpen is true', async () => { + let {getByRole} = render(); + let button = getByRole('button'); + + expect(button).not.toHaveAttribute('data-pressed'); + await user.click(button); + expect(button).not.toHaveAttribute('data-pressed'); + }); it('should support data-open state', async () => { let {getByRole} = render(); diff --git a/packages/react-aria-components/test/Dialog.test.js b/packages/react-aria-components/test/Dialog.test.js index f21edee1e65..8cad80bef59 100644 --- a/packages/react-aria-components/test/Dialog.test.js +++ b/packages/react-aria-components/test/Dialog.test.js @@ -52,6 +52,23 @@ describe('Dialog', () => { expect(dialog).toHaveAttribute('data-rac'); }); + it('should not apply isPressed state on trigger when expanded and isTriggerUpWhenOpen is true', async () => { + let {getByRole} = render( + + + + Title + + + ); + + let button = getByRole('button'); + expect(button).not.toHaveAttribute('data-pressed'); + + await user.click(button); + expect(button).not.toHaveAttribute('data-pressed'); + }); + it('works with modal', async () => { let {getByRole} = render( diff --git a/packages/react-aria-components/test/Menu.test.tsx b/packages/react-aria-components/test/Menu.test.tsx index 6a3fadaca31..fb0e8f85eb2 100644 --- a/packages/react-aria-components/test/Menu.test.tsx +++ b/packages/react-aria-components/test/Menu.test.tsx @@ -489,6 +489,25 @@ describe('Menu', () => { expect(onAction).toHaveBeenLastCalledWith('rename'); }); + it('should not apply isPressed state on trigger when expanded and isTriggerUpWhenOpen is true', async () => { + let {getByRole} = render( + + + + + Open + + + + ); + + let button = getByRole('button'); + expect(button).not.toHaveAttribute('data-pressed'); + + await user.click(button); + expect(button).not.toHaveAttribute('data-pressed'); + }); + it('should support onScroll', () => { let onScroll = jest.fn(); let {getByRole} = renderMenu({onScroll}); diff --git a/packages/react-aria-components/test/Select.test.js b/packages/react-aria-components/test/Select.test.js index 25ce2caa762..6c9be6b4a76 100644 --- a/packages/react-aria-components/test/Select.test.js +++ b/packages/react-aria-components/test/Select.test.js @@ -393,6 +393,15 @@ describe('Select', () => { expect(trigger).toHaveTextContent('Kangaroo'); }); + it('should not apply isPressed state to button when expanded and isTriggerUpWhenOpen is true', async () => { + let {getByRole} = render(); + let button = getByRole('button'); + + expect(button).not.toHaveAttribute('data-pressed'); + await user.click(button); + expect(button).not.toHaveAttribute('data-pressed'); + }); + describe('typeahead', () => { beforeEach(() => { jest.useFakeTimers();