Skip to content

Commit 23c5ba1

Browse files
authored
Fix combobox closes prematurely (#6461)
* Fix ComboBox closes if input is long and dropdown button is used
1 parent 5548b89 commit 23c5ba1

File tree

4 files changed

+154
-1
lines changed

4 files changed

+154
-1
lines changed

packages/@react-aria/overlays/src/useCloseOnScroll.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ export function useCloseOnScroll(opts: CloseOnScrollOptions) {
4242
return;
4343
}
4444

45+
// Ignore scroll events on any input or textarea as the cursor position can cause it to scroll
46+
// such as in a combobox. Clicking the dropdown button places focus on the input, and if the
47+
// text inside the input extends beyond the 'end', then it will scroll so the cursor is visible at the end.
48+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
49+
return;
50+
}
51+
4552
let onCloseHandler = onClose || onCloseMap.get(triggerRef.current);
4653
if (onCloseHandler) {
4754
onCloseHandler();
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2022 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {Button, ComboBox, Input, Label, ListBox, ListBoxItem, Popover} from 'react-aria-components';
14+
import React from 'react';
15+
import './combobox-reproductions.css';
16+
17+
export default {
18+
title: 'React Aria Components'
19+
};
20+
21+
export const ComboBoxReproductionExample = () => (
22+
<ComboBox>
23+
<Label>Favorite Animal</Label>
24+
<div>
25+
<Input />
26+
<Button></Button>
27+
</div>
28+
<Popover>
29+
<ListBox>
30+
<ListBoxItem>Aardvark</ListBoxItem>
31+
<ListBoxItem>Cat</ListBoxItem>
32+
<ListBoxItem>Dooooooooooooooooooooooooooooooooog</ListBoxItem>
33+
<ListBoxItem>Kangaroo</ListBoxItem>
34+
<ListBoxItem>Panda</ListBoxItem>
35+
<ListBoxItem>Snake</ListBoxItem>
36+
</ListBox>
37+
</Popover>
38+
</ComboBox>
39+
);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
html,
2+
body,
3+
#root {
4+
padding: 0;
5+
margin: 0;
6+
height: 100%;
7+
}
8+
9+
@import "@react-aria/example-theme";
10+
11+
:global(.react-aria-ComboBox) {
12+
color: var(--text-color);
13+
14+
:global(.react-aria-Input) {
15+
margin: 0;
16+
font-size: 1.072rem;
17+
background: var(--field-background);
18+
color: var(--field-text-color);
19+
border: 1px solid var(--border-color);
20+
border-radius: 6px;
21+
padding: 0.286rem 2rem 0.286rem 0.571rem;
22+
vertical-align: middle;
23+
24+
&[data-focused] {
25+
outline: none;
26+
outline: 2px solid var(--focus-ring-color);
27+
outline-offset: -1px;
28+
}
29+
}
30+
31+
:global(.react-aria-Button) {
32+
background: var(--highlight-background);
33+
color: var(--highlight-foreground);
34+
forced-color-adjust: none;
35+
border-radius: 4px;
36+
border: none;
37+
margin-left: -1.714rem;
38+
width: 1.429rem;
39+
height: 1.429rem;
40+
padding: 0;
41+
font-size: 0.857rem;
42+
cursor: default;
43+
44+
&[data-pressed] {
45+
box-shadow: none;
46+
background: var(--highlight-background);
47+
}
48+
}
49+
}
50+
51+
:global(.react-aria-Popover)[data-trigger="ComboBox"] {
52+
width: var(--trigger-width);
53+
54+
:global(.react-aria-ListBox) {
55+
display: block;
56+
width: unset;
57+
max-height: inherit;
58+
min-height: unset;
59+
border: none;
60+
61+
:global(.react-aria-Header) {
62+
padding-left: 1.571rem;
63+
}
64+
}
65+
66+
:global(.react-aria-ListBoxItem) {
67+
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
68+
69+
&[data-focus-visible] {
70+
outline: none;
71+
}
72+
73+
&[data-selected] {
74+
font-weight: 600;
75+
background: unset;
76+
color: var(--text-color);
77+
78+
&::before {
79+
content: "✓";
80+
content: "✓" / "";
81+
alt: " ";
82+
position: absolute;
83+
top: 4px;
84+
left: 4px;
85+
}
86+
}
87+
88+
&[data-focused],
89+
&[data-pressed] {
90+
background: var(--highlight-background);
91+
color: var(--highlight-foreground);
92+
}
93+
}
94+
}

packages/react-aria-components/test/ComboBox.test.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,23 @@ describe('ComboBox', () => {
264264
let {getByRole} = render(<TestComboBox />);
265265

266266
let button = getByRole('button');
267-
await userEvent.click(button);
267+
await user.click(button);
268268
let listbox = getByRole('listbox');
269269
expect(listbox).toBeInTheDocument();
270270
fireEvent.scroll(document.body);
271271
expect(listbox).not.toBeInTheDocument();
272272
});
273+
274+
it('should not close on input scrolling for cursor placement', async () => {
275+
let {getByRole} = render(<TestComboBox />);
276+
277+
let input = getByRole('combobox');
278+
let button = getByRole('button');
279+
await user.click(button);
280+
let listbox = getByRole('listbox');
281+
expect(listbox).toBeInTheDocument();
282+
expect(input).toHaveFocus();
283+
fireEvent.scroll(input); // simulate what happens when the text is long and overflows
284+
expect(listbox).toBeInTheDocument();
285+
});
273286
});

0 commit comments

Comments
 (0)