Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/@react-aria/test-utils/src/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ interface SelectTriggerOptionOpts extends SelectOpenOpts {
/**
* The index, text, or node of the option to select. Option nodes can be sourced via `options()`.
*/
option: number | string | HTMLElement
option: number | string | HTMLElement,

shouldStayOpen?: boolean
}

export class SelectTester {
Expand Down Expand Up @@ -164,6 +166,7 @@ export class SelectTester {
async selectOption(opts: SelectTriggerOptionOpts): Promise<void> {
let {
option,
shouldStayOpen = false,
interactionType = this._interactionType
} = opts || {};
let trigger = this.trigger;
Expand Down Expand Up @@ -203,7 +206,7 @@ export class SelectTester {
}
}

if (option?.getAttribute('href') == null) {
if (option?.getAttribute('href') == null && !shouldStayOpen) {
await waitFor(() => {
if (document.activeElement !== this._trigger) {
throw new Error(`Expected the document.activeElement after selecting an option to be the select component trigger but got ${document.activeElement}`);
Expand Down
4 changes: 3 additions & 1 deletion packages/@react-stately/select/src/useSelectState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export function useSelectState<T extends object>(props: SelectStateOptions<T>):
props.onSelectionChange(key);
}

triggerState.close();
if (props.shouldCloseOnSelect ?? true) {
triggerState.close();
}
validationState.commitValidation();
}
});
Expand Down
4 changes: 3 additions & 1 deletion packages/@react-types/select/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export interface SelectProps<T> extends CollectionBase<T>, Omit<InputBase, 'isRe
/** Sets the default open state of the menu. */
defaultOpen?: boolean,
/** Method that is called when the open state of the menu changes. */
onOpenChange?: (isOpen: boolean) => void
onOpenChange?: (isOpen: boolean) => void,
/** Should the menu be closed when an item is selected? */
shouldCloseOnSelect?: boolean
}

export interface AriaSelectProps<T> extends SelectProps<T>, DOMProps, AriaLabelingProps, FocusableDOMProps {
Expand Down
28 changes: 28 additions & 0 deletions packages/react-aria-components/test/Select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,34 @@ describe('Select', () => {
expect(trigger).toHaveTextContent('close');
});

it('should stay open on selecting an option if shouldCloseOnSelect is false', async () => {
let {getByTestId} = render(
<Select data-testid="select" shouldCloseOnSelect={false}>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
</Button>
<Popover>
<ListBox>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Kangaroo</ListBoxItem>
</ListBox>
</Popover>
</Select>
);

let selectTester = testUtilUser.createTester('Select', {root: getByTestId('select')});
let trigger = selectTester.trigger;

await selectTester.open();
expect(trigger).toHaveAttribute('data-pressed', 'true');

await selectTester.selectOption({option: 'Dog', shouldStayOpen: true});
expect(trigger).toHaveTextContent('Dog');
expect(trigger).toHaveAttribute('data-pressed', 'true');
});

it('should send disabled prop to the hidden field', () => {
render(
<TestSelect name="select" isDisabled />
Expand Down